成员变量和成员函数是分开存储的,每一个非静态成员函数只会有一个函数实例,也就是多个同类型的对象公用一块代码
C++通过this指针区分是哪个对象调用了这个函数
this指针是隐含在每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用就行
this指针的本质时一个指针常量
this指针的用途
在形参和成员变量同名时,可以用this指针区分
在类的非静态成员函数中返回对象本身,可以使用return *this
class Person {
public:
// this区分不同的属性
Person(int age) {
this->age = age;
}
// 返回对象本身
Person PersonAddage(Person p) {
return *this;
}
string name;
int age;
};
class Person {
public:
// this区分不同的属性
Person(int age) {
this->age = age;
}
// 返回对象本身
void showPerson() {
cout << age << endl;
}
void showPerson2() {
cout << this->age << endl;
}
void showPerson3() {
if (this == NULL) {
return; // 解决空指针访问报错问题
}
cout << this->age << endl;
}
string name;
int age;
};
int main() {
Person* p = NULL;
p->showPerson(); // 不会报错
p->showPerson2(); // 存在this指针访问成员,会报错
p->showPerson3(); // 不会报错
}
常函数:
成员函数后加const后我们称为这个函数为常函数
常函数内不可以修改成员属性
成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:
声明对象前加const称该对象为常对象
常对象只能调用常函数
友元:让一个函数或者类访问另一个类中的私有成员
友元的三种实现:
全局函数做友元
类做友元
成员函数做友元
class Person {
//让全局函数boy函数能够访问Person中的私有成员
friend void boy(Person *p);
public:
int score;
private:
string name;
int age;
};
void boy(Person *p) {
}
class Person {
// boy类可以访问本类的私有成员
friend class boy;
public:
int score;
private:
string name;
int age;
};
class boy {
};
class Person {
// boy类的A成员函数可以访问本类的私有成员
friend void boy::A();
public:
int score;
private:
string name;
int age;
};
class boy {
public:
void A() {
}
};
class Person {
public:
int score;
private:
string name;
int age;
};
// boy继承Person
class boy:public Person {
public:
void A() {
}
};
公共继承 public:父类中公共和保护权限都不变
公共继承 protected:父类中公共和保护权限都变成保护权限
公共继承 private:父类中公共和保护权限都变成私有权限
父类中的私有成员在继承时会被子类继承,但是会被编译器隐藏,无法访问
父类中的非静态成员都会被继承
继承中的构造和析构顺序:
先构造父类在构造子类
先析构子类再析构父类
继承中同名成员以子类为主
访问父类的成员用作用域
class Person {
public:
int score = 10;
};
class Person1 {
public:
int score=20;
};
// boy继承Person
class boy:public Person, public Person1{
public:
int score=20;
};
int main() {
boy b;
// 访问子类的成员
cout << b.score << endl;
// 访问父类的成员
cout << b.Person::score << endl;
}
静态多态:函数重载和运算符重载 属于静态多态,复用函数名
动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态的区别
静态多态的函数地址早绑定——编译阶段确定函数地址
动态多态的函数地址晚绑定——运行阶段确定函数地址
多态的优点
代码组织结构清晰
可读性强
利于前期和后期的扩展以及维护
class Person {
public:
void speak() {
}
virtual void eat() {
}
};
// boy继承Person
class boy:public Person{
public:
void speak() {
}
// 地址晚绑定,
void eat() {
}
};
int main() {
// 地址早绑定,在编译阶段绑定,执行时Person的方法生效
//virtual修饰的为地址晚绑定,调用时boy的方法生效
//子类要重写父类的虚函数
//重写:函数的返回值类型,函数名,参数列表完全一样
Person p = boy();
}
多态中,父类中的虚函数是毫无意义的,主要都是调用子类重写的内容,可以把虚函数写成纯虚函数
纯虚函数:virtual 返回值类型 函数名(参数列表)=0;
当类中存在纯虚函数,这个类成为抽象类
抽象类的特点
无法实例化对象
子类必须重写抽象类中的纯虚函数,否则也属于抽象类
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构的共性
可以解决父类指针释放子类对象
都需要有具体的函数实现
虚析构和纯虚析构的区别
如果是纯虚析构,该类属于抽象类,无法实例化对象
不是所有类都要虚析构或者纯虚析构,不需要执行子类的虚构的程序就不需要
class Person {
public:
// 析构函数如果不是虚析构,父类指针在虚构的时候不会调用子类中虚构函数,导致子类如果有堆区属性出现内存泄露
virtual ~Person() {
}
// 纯虚析构,纯虚析构要实现
virtual ~Person() = 0;
};
// 纯虚析构的实现
Person::~Person() {
}
// boy继承Person
class boy:public Person{
public:
boy(string name) {
// 创建一个指针指向堆中数据
p_name = new string(name);
}
string* p_name;
~boy() {
}
};