C++面向对象的三大特性为:封装、继承、多态
C++认为万事万物都皆为对象,对象上有其属性和行为,和java特别相似
语法:class 类名{ 访问权限: 属性 / 行为 };
例子:
#include<iostream>
using namespace std;
#define PI 3.14
//圆类
class Circle {
//声明访问权限
public:
//圆的半径
int r;
//计算圆的周长
double computePerimeter() {
return 2 * PI * r;
}
};
int main() {
Circle c;
c.r = 20;
double per = c.computePerimeter();
cout << per << endl;
return 0;
}
cpp中修饰类成员的修饰符一共有三个:public、private、protected,没有java中的缺省,如果不写,默认是private
public:公有成员在程序中类的外部是可访问的
private:私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员。
protected:受保护成员变量或函数与私有成员十分相似,但有一点不同,protected(受保护)成员在派生类(即子类)中是可访问的。
例子:
class MyClass {
int a; //默认是private
private: // 私有
int b;
protected: //受保护
int c;
public: //公共
void func() {
}
};
在C++中 struct和class唯一的区别就在于 默认的访问权限不同
区别:
C++中的面向对象来源于生活,每个对象也都会有初始设置以及对象销毁前的清理数据的设置
对象的初始化和清理也是两个非常重要的安全问题
1、一个对象或者变量没有初始状态,对其使用后果是未知
2、同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
c++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。
对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现。
语法:类名(){}
语法:~类名(){}
例子:
#include<iostream>
using namespace std;
class Person {
public:
Person() {
cout << "无参构造函数" << endl;
}
Person(int a) {
cout << "有参构造函数" << endl;
}
~Person() {
cout << "析构函数,在对象销毁前调用" << endl;
}
};
int main() {
//调用无参构造
Person p1;
//调用有参构造
Person p2(10);
system("pause");
return 0;
}
例子:
#include<iostream>
using namespace std;
class Person {
private:
int age;
public:
//无参(默认)构造
Person() {
cout << "无参构造函数" << endl;
}
//有参构造
Person(int a) {
age = a;
cout << "有参构造函数" << endl;
}
//拷贝构造
Person(const Person & p) {
age = p.age;
cout << "拷贝构造函数" << endl;
}
int getAge() {
return age;
}
};
int main() {
//调用无参构造
Person p1;
//调用有参构造,括号法调用
Person p2(18);
//调用拷贝构造,显式法
Person p3 = Person(p2);
//调用拷贝构造,隐式转换法
Person p4 = p2;
cout << p3.getAge() << endl;
system("pause");
return 0;
}
默认情况下,c++编译器至少给一个类添加3个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
如果用户定义拷贝构造函数,c++不会再提供其他构造函数
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
例子:
#include<iostream>
using namespace std;
class Person {
public:
//静态变量也是有权限修饰符的,而且在类内只可以进行声明
static int TYPE;
//静态函数也是由权限修饰符,但是可以在类内进行初始化
static void func() {
cout << "静态函数" << endl;
}
};
//类外进行静态变量初始化,使用作用域运算符::,需要声明变量类型
int Person::TYPE = 20;
int main() {
//进行调用,使用作用域运算符::
cout << Person::TYPE << endl;
Person::func();
system("pause");
return 0;
}
在C++中,类内的成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上
class Person {
public:
Person() {
mA = 0;
}
//非静态成员变量占对象空间
int mA;
//静态成员变量不占对象空间
static int mB;
//函数也不占对象空间,所有函数共享一个函数实例
void func() {
cout << "mA:" << this->mA << endl;
}
//静态成员函数也不占对象空间
static void sfunc() {
}
};
int main() {
cout << sizeof(Person) << endl;
system("pause");
return 0;
}
在C++中成员变量和成员函数是分开存储的,每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码,那么问题是:这一块代码是如何区分那个对象调用自己的呢?
c++通过提供特殊的对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针,本质是指针常量,也就是指针的指向是不可以修改的
this指针不需要定义,直接使用即可,和java中的this意义一样,只不过使用方式不同
this指针的用途:
语法:this -> 非静态成员
例子:
#include<iostream>
#include<string>
using namespace std;
class Person {
private:
int age;
string name;
public:
//和java中的getter、setter一样
void setAge(int age) {
//this->代表,当前对象的
this->age = age;
}
void setName(string name) {
this->name = name;
}
int getAge() {
return age;
}
string getName() {
return name;
}
string getInfo() {
return "姓名:" + name + ",年龄:" + to_string(age);
}
};
int main() {
Person person;
person.setAge(18);
person.setName("Lucy");
cout << person.getInfo() << endl; //姓名:Lucy,年龄:18
system("pause");
return 0;
}
C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针
如果用到this指针,需要加以判断保证代码的健壮性
例子:
#include<iostream>
#include<string>
using namespace std;
class Person {
private:
int age = 12;
string name = "Lucy";
public:
void showMsg() {
cout << "你好" << endl;
}
void showName() {
cout << this->name << endl;
}
void showAge() {
cout << age << endl;
}
};
int main() {
Person * person = NULL;
person->showMsg(); // 正常运行,因为函数为调用this的成员
person->showAge(); // 报错,因为this是NULL
person->showName(); // 报错,因为this是NULL
system("pause");
return 0;
}
例子:
#include<iostream>
#include<string>
using namespace std;
class Person {
private:
int a;
mutable int b;
public:
void test1() const{
//a = 10; 常函数中,普通的成员变量不可以进行修改
//只有添加mutable的成员变量,才可以在常函数中进行修改
b = 10;
cout << b << endl;
}
void test2() {
cout << "不是常函数" << endl;
}
};
int main() {
//常对象
const Person person;
person.test1();
//person.test2(); 常对象只可以调用常函数
system("pause");
return 0;
}
在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术
友元的关键字为 :friend
友元的三种实现
#include<iostream>
using namespace std;
class Person {
//声明了全局函数test,可以访问Person的私有属性
friend void test(Person * person);
private:
int a = 10;
int b = 20;
};
//全局函数test
void test(Person * person) {
//可以访问Person对象的私有成员
cout << person->a << endl;
cout << person->b << endl;
}
int main() {
Person person;
test(&person);
return 0;
}
#include<iostream>
using namespace std;
class Person {
//PersonFriend类作为Person类的友元
friend class PersonFriend;
private:
int a = 10;
int b = 20;
};
class PersonFriend {
private:
Person person;
public:
PersonFriend(Person & person) {
this->person = person;
}
void showPerson() {
//PersonFriend类中可以访问Person对象的私有成员
cout << person.a << endl;
cout << person.b << endl;
}
};
int main() {
Person person;
PersonFriend f(person);
f.showPerson();
return 0;
}
这个相对麻烦,顺序需要限制
#include<iostream>
using namespace std;
//必须按照这个顺序进行,先声明Person
class Person;
//然后声明友元函数所在类,其中友元函数只声明,不定义
class PersonFriend{
public:
void showPerson();
};
//定义Person类,以及友元
class Person {
friend void PersonFriend::showPerson();
private:
int a = 10;
int b = 20;
};
//定义友元函数
void PersonFriend::showPerson() {
Person* person = new Person;
cout << person->a << endl;
}
int main() {
PersonFriend f;
f.showPerson();
return 0;
}
我们发现,定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。
这个时候我们就可以考虑利用继承的技术,减少重复代码
class A : public B;
A 类称为子类 或 派生类
B 类称为父类 或 基类
例子:
#include<iostream>
using namespace std;
//人类
class Person {
public:
void personMsg() {
cout << "人可以吃饭、睡觉" << endl;
}
};
//老师类,继承于人类
class Teacher : public Person {
public:
void teacherMsg() {
cout << "老师可以教书育人" << endl;
}
};
int main() {
Teacher t;
//老师类对象可以调用人类对象的方法
t.personMsg();
t.teacherMsg();
return 0;
}
继承方式一共有三种:
无论哪种继承方式,父类中私有的成员,子类都无法访问
#include<iostream>
using namespace std;
class A {
public:
int a = 10;
protected:
int b = 20;
private:
int c = 30;
};
class B : public A {
public:
int d = 40;
};
int main() {
//也就是说,父类里面的所有非静态的成员都会被子类继承
//虽然父类中private的属性子类无法访问,只是被编译器隐藏了,但是确实被继承下去了
cout << sizeof(B) << endl; //16
return 0;
}
打开工具窗口后,定位到当前CPP文件的位置
然后输入: cl /d1 reportSingleClassLayout查看的类名
所属文件名
例如,查看demo1.cpp中的B类
构造:先父类后子类
析构:先子类后父类
#include<iostream>
using namespace std;
class A {
public:
A() {
cout << "父类A的构造函数" << endl;
}
~A() {
cout << "父类A的析构函数" << endl;
}
};
class B : public A {
public:
B() {
cout << "子类B的构造函数" << endl;
}
~B() {
cout << "子类B的析构函数" << endl;
}
};
int main() {
//创建子类B的对象
B b;
/*
父类A的构造函数
子类B的构造函数
子类B的析构函数
父类A的析构函数
*/
return 0;
}
子类对象.父类::父类成员
#include<iostream>
using namespace std;
class A {
public:
int num = 10;
void test() {
cout << "父类的test" << endl;
}
};
class B : public A {
public:
int num = 20;
void test() {
cout << "子类的test" << endl;
}
};
int main() {
B b;
//访问子类中的num
cout << b.num << endl;
//访问父类中的num
cout << b.A::num << endl;
//访问子类的test()
b.test();
//访问父类的test()
b.A::test();
return 0;
}
C++允许一个类继承多个类
语法: class 子类 :继承方式 父类1 , 继承方式 父类2...
多继承可能会引发父类中有同名成员出现,需要加作用域区分
C++实际开发中不建议用多继承
例子:
#include<iostream>
using namespace std;
class A {
public:
int num = 10;
};
class B {
public:
int num = 20;
};
class C : public A , public B{
public:
int num = 30;
};
int main() {
C c;
cout << c.num << endl;
cout << c.A::num << endl;
cout << c.B::num << endl;
return 0;
}
菱形继承概念:
两个派生类继承同一个基类
又有某个类同时继承者两个派生类
这种继承被称为菱形继承,或者钻石继承
菱形继承问题:
羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。
利用工具,可以看到,YangTuo类继承了两份age
#include<iostream>
using namespace std;
//动物
class Animal {
public:
int age;
};
//羊
class Yang : public Animal {
};
//驼
class Tuo : public Animal {
};
//羊驼
class YangTuo : public Yang, public Tuo {
};
int main() {
YangTuo y;
//问题1: 报错,age不明确
//y.age = 10;
//问题2:虽然可以解决问题1,但是一个羊驼不可能有两个变量
y.Yang::age = 18;
y.Tuo::age = 28;
cout << y.Yang::age << endl; //18
cout << y.Tuo::age << endl; //28
return 0;
}
#include<iostream>
using namespace std;
//动物
class Animal {
public:
int age;
};
//继承前加virtual关键字后,变为虚继承
//此时公共的父类Animal称为虚基类
//羊
class Yang :virtual public Animal {
};
//驼
class Tuo :virtual public Animal {
};
//羊驼
class YangTuo : public Yang, public Tuo {
};
int main() {
YangTuo y;
y.age = 18;
cout << y.Yang::age << endl;
cout << y.Tuo::age << endl;
cout << y.age << endl;
return 0;
}
age只有一份了
此时vbptr(v-virtual;b-base;ptr-pointer):虚基类指针,指向vbtable(虚基类表),即下方显示的,该指针的偏移量4,刚好就是这个表里面的唯一的数据
多态分为两类
静态多态和动态多态区别:
早绑定:
#include<iostream>
using namespace std;
//动物
class Animal {
public:
void eat() {
cout << "动物在吃饭" << endl;
}
};
//猫,继承于动物
class Cat : public Animal{
public:
void eat() {
cout << "猫吃猫粮" << endl;
}
};
//狗,继承于动物
class Dog : public Animal {
public:
void eat() {
cout << "狗吃狗粮" << endl;
}
};
//使用父类引用指向子类对象
void eatTest(Animal & animal) {
animal.eat();
}
int main() {
Cat cat;
eatTest(cat); //动物在吃饭,原因是eatTest()中函数的地址是早绑定,在编译时已经确定
return 0;
}
晚绑定,在函数前添加virtual
关键字:
#include<iostream>
using namespace std;
//动物
class Animal {
public:
//添加关键字virtual,可以使函数虚绑定,也就是在运行时,绑定函数
virtual void eat() {
cout << "动物在吃饭" << endl;
}
};
//猫,继承于动物
class Cat : public Animal{
public:
void eat() {
cout << "猫吃猫粮" << endl;
}
};
//狗,继承于动物
class Dog : public Animal {
public:
void eat() {
cout << "狗吃狗粮" << endl;
}
};
//使用父类引用指向子类对象
void eatTest(Animal & animal) {
animal.eat();
}
int main() {
Cat cat;
eatTest(cat); //猫吃猫粮
Dog dog;
eatTest(dog); //狗吃狗粮
return 0;
}
函数返回值类型 、函数名、参数列表、完全一致称为重写
#include<iostream>
using namespace std;
class A {
public:
void test() {
}
};
class B {
public:
virtual void test() {
}
};
int main() {
//普通的成员函数是不和类放在一起的,所以A只有类的占位空间1
cout << sizeof(A) << endl; //1
//对于虚函数,类中记录的虚函数的指针地址,所以占4
cout << sizeof(B) << endl; //4
return 0;
}
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
例子:
#include<iostream>
using namespace std;
class Person {
public:
int a = 10;
virtual void eat() = 0;
};
class Student : public Person {
public:
void eat() {
cout << "学生吃饭" << endl;
}
};
int main() {
Student s;
cout << s.a << endl;
s.eat();
}
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
3. 拥有纯虚析构函数的类也属于抽象类
虚析构和纯虚析构共性:
虚析构和纯虚析构区别:
虚析构语法:virtual ~类名(){}
纯虚析构语法:virtual ~类名() = 0;
然后再定义:类名::~类名(){}
#include<iostream>
#include<string>
using namespace std;
class A {
public:
// 虚析构
virtual ~A() {
};
virtual void msg() {
};
};
class B : public A {
public:
string * name = new string("haha");
//子类析构,清空堆区的name
~B() {
cout << "子类B的析构函数被调用" << endl;
if (name != NULL) {
delete(name);
}
}
void msg() {
cout << "重写的msg函数" << endl;
}
};
int main() {
// 父类指针,指向子类
A * a = new B;
a->msg();
delete a;
system("pause");
return 0;
}
必须要声明并定义纯虚析构,因为父类也有可能有属性在堆区
#include<iostream>
#include<string>
using namespace std;
class A {
public:
// 纯虚析构
virtual ~A() = 0;
virtual void msg() = 0;
};
//定义纯虚析构
A::~A(){}
class B : public A {
public:
string * name = new string("haha");
//子类析构,清空堆区的name
~B() {
cout << "子类B的析构函数被调用" << endl;
if (name != NULL) {
delete(name);
}
}
void msg() {
cout << "重写的msg函数" << endl;
}
};
int main() {
// 父类指针,指向子类
A * a = new B;
a->msg();
delete a;
system("pause");
return 0;
}