C++入门笔记
-
1.注释
单行注释://
多行注释:/* 内容 */// 注释一行 /* 注 释 很 多 行 */
-
2.数据类型
1.整型 int
2.短整型 short
3.长整型 long
4.长长整型 long long
5.单精度浮点型 float(保留6位有效数字)
6.双精度浮点型 doublt(保留15位有效数字)
7.字符型 char
8.字符串型 string(可能需要导入头文件 <string>)
9.布尔型 bool -
3.查看变量占用内存空间大小
sizeof(变量) -
4.算数运算符
±*/% 加减乘除取模
++ – 递增递减
a++ 等价于 a = a + 1
b = a++ 等价于 b = a; a = a + 1
b = ++a 等价于 a = a + 1; b = a -
5.逻辑运算符
! 非
&& 与
|| 或 -
6.选择结构 switch
如果case匹配不成功时,执行default的内容。int score = 1; switch(score){ case 1:.... case 2:... .... default:... }
注意:如果case内部没有break,当匹配成功后,会继续往下执行所有case的代码。要匹配成功后跳出,必须加break。
-
7.跳转语句
break 跳出
continue 跳过下面部分for(int i = 1; i < 10; i ++){ 代码部分1 if(....){ 跳转语句 } 代码部分2 }
如果跳转语句是break,当if成立后,直接跳出循环;如果是continue,当if成立后,代码部分2不执行,i+1继续执行循环。
-
8.数组
1.一维数组初始化int arr[5]; int arr[] = {1,2,3,4,5};
2.二维数组初始化
int arr[2]\[3]; int arr[2]\[3] = {{1,2,3},{2,3,4}}; int arr[2]\[3] = {1,2,3,2,3,4}; int arr[]\[3] = {1,2,3,2,3,4};
注意:二维数组初始化时,如果有直接给数组赋值,那么数组行数可以省略,但列数不行。
-
9.函数
定义
返回值类型 函数名(变量){
函数体
} -
10.指针
- 指针也是一个变量,用来存储变量的内存地址。
/* 初始化: int *p; 赋值: p = &变量; 取值(指针指向的变量的值):*p; */ cout << "指针" << endl; int a = 123; int * p; p = &a; cout << "a的地址:" << &a << ",p的值" << p << "," << "*p=" << *p <<endl; // &a == p;*p == a *p = 122; cout << "a = " << a << ",*p = " << *p << endl; //因为p指向的是a的内存地址,所以修改*p等价于修改a的值,因此此时a == 122
-
11.const修饰指针
- 常量指针
int a = 123; const int * p = &a; //常量指针可以修改指向,但无法修改所指向的变量的值。
- 指针常量
int a = 123; int * const p = &a; //指针常量可以修改所指向变量的值,但无法修改指向。
- const既修饰指针也修饰变量
int a = 123; const int * const p = &a;
- 常量指针
-
12.指针与函数
void swap1(int a,int b){ int tmp; tmp = a; a = b; b = tmp; } void swap2(int *p1,int *p2){ int tmp; tmp = *p1; *p1 = *p2; *p2 = tmp; } int main(){ int a = 1; int b = 2; swap1(a,b); swap2(&a,&b); //调用swap1 a,b的值不变,调用swap2,此时传入的是变量的内存地址,a,b的值会改变。 } //
-
13.结构体
- struct 结构体名{
定义属性
};
//结构体创建,struct不可省略 struct Student{ int age; int score; string name; };//注意有个分号 //结构体初始化,struct可省略 //法1 struct Student s1; s1.age = 12; s1.ascore = 100; s1.name = "xiaoming"; //法2 struct Student s2 = {12,100,"xiaoming"}; //结构体数组 struct Student stuArr[3]; strArr[0] = {13,99,"xiaohong"}; strArr[1] = {14,98,"xiaolan"}; strArr[2] = {14,98,"xiaoqing"};
- 结构体指针
struct Student *p; struct Student s = {12,100,"xiaoming"}; p = &s; //结构体指针取值用 -> cout << "年龄:" << p -> age << endl; p -> age = 15; cout << "年龄:" << p -> age << endl;
- 结构体做函数参数
void printStu(Student std){ cout << "年龄:" << std.age << endl; std.age = 1222;//不修改s的age值。 } //使用指针,与函数的指针一样 void printStu(Student *std){ cout << "年龄:" << std -> age << endl; std -> age = 1222;//修改了s的ageg值。 } int main(){ struct Student s = {14,98,"xiaoqing"}; printStu(s); printStu2(&s); }
- const修饰结构体
void printStu(const struct Student *stu){ // 加入const修饰,则stu的属性只能读取,无法修改 stu -> age = 123;//错误 }
- struct 结构体名{
-
14.引用
- 引用的作用:给变量取别名,用&修饰
int a = 123; int &b = a; //引用在创建时必须直接初始化(指定引用对象),且初始化后无法修改引用对象。 cout << b >> endl; //输出123 int c = 12; b = c //该操作是修改a或者说b的值,而不是将b引用向c
- 引用的底层:引用的本质是指针常量(int * const p)
int a = 123; int &b = a; a = 124; cout << b << endl; //输出 124 b = 125; cout << a << endl; //输出125
- 引用做函数参数
void swap(int &a,int &b){ //调用该函数,实参也会被修改,因为引用的本质就是指针 int temp; a = temp; a = b; b = temp; } int main(){ int a = 12; int b = 21; swap(a,b) }
- 引用做函数的返回值
- 不要返回局部变量的引用
int & f(){ //错误示例:返回局部变量的引用 int a = 10;//局部变量保存在栈区,f执行完可能就被释放了(视编译器不同,可能不会立即释放,会保留一次) return a; } int g_a = 123;//全局变量存放在全局区,程序运行完才会被清除 int & f2(){ //正确示例:返回全局变量的引用 return g_a; } int & f3(){ static int a = 10;//静态变量存放在全局区,程序结束后由系统释放。 return a; } int main(){ int &res = f(); cout << res << endl; //第一次输出的res是正确的,编译器会对局部变量做一次保留。(并不一定能正确输出,与编译器有关) cout << res << endl; //第二次输出的re是错误的,因为局部变量此时已经被清除了。 int &res2 = f2() cout << res2 << endl; cout << res2 << endl; cout << res2 << endl; //全局变量在程序运行中不会被清除,因此返回全局变量的引用是合法的。 int &res3 = f3() cout << res3 << endl; cout << res3 << endl; cout << res3 << endl; //静态变量在程序运行中不会被清除,因此返回静态变量的引用是合法的。 return 0;
- 函数的调用可以作为左值
int &f(){ static a = 10; return a; int main(){ int &a = f(); f() = 123; cout << a << endl; //输出123 }
- 引用的测试实例
int &f(){ static int a = 123; return a; } int main(){ int &a = f(); int &b = f(); f() = 12345; cout << a << endl; cout << b << endl; //a=12345,b=12345 a = 222; cout << a << endl; cout << b << endl; // a = 222,b = 222 //该结果再次说明,引用的本质是指针,所有的修改都是基于地址的。 }
- 常量引用
int main(){ int a = 10; int &aa = a; //合法 int &bb = 10;//非法 const int &cc = 10;//合法,编译器会自动生成一个临时变量存放10,然后将cc指向临时变量 }
常量引用主要用于防止误操作
void f(const int &a){ //防止误操作,修改了a cout << a << endl; }
-
15.函数默认参数
- 默认参数即给参数指定默认值
- 函数的定义和函数的声明只能有一方指定默认值
//声明 int f(int a,int b) //定义 int f(int a,int b = 10){ return a + b; } //或者 //声明 int f(int a,int b = 10) //定义 int f(int a,int b){ return a + b; }
- 函数的默认参数必须放在函数参数的尾部
int f(int a,int b,int c = 123,int d = 234){ return a + b + c + d; } //正确 int f(int a,int b = 10,int c,int d = 234){ return a + b + c + d; } //错误
-
16.函数的占位参数
void f(int a,int) int main(){ f(10,10); }
-
17.函数的重载
- 重载:多个功能不同的函数使用同一个函数名,但能根据调用时传入的参数让编译器识别出具体调用的是同名函数中的哪个函数。
- 重载需要满足:
- 相同作用域
- 函数名相同
- 参数不同(参数个数或参数类型或参数顺序不同,目的是让编译器在调用时能唯一识别)
void f(int a){ cout << "int" << endl; } void f(float a){ cout << "float" << endl; } void f(double a){ cout << "double" << endl; } void f(float a,int b){ cout << "float int" << endl; } void f(int a,float b){ cout << "int float" << endl; } int main(){ f(1); f(1.2f); f(1.2); f(1.2,1); f(1,1.2); }
- 函数重载,引用作为条件
void f(int &a){ cout << "&" << endl; } void f(const int &a){ cout << "const &" << endl; } int main(){ int a = 10; f(a); const int aa = 10; f(aa);
- 函数重载,默认参数情况
void f(int a,int b = 10){ ... } void f(int a){ ... } int main(){ f(10);//非法操作,此时的参数形式满足上述两个函数 f(10,10);//合法操作,此时能区分调用哪个f }
-
18.类和对象
- 面向对象的三大特性:封装、继承、多态
- C++中万物皆对象
class 类名{ 访问权限 属性 方法 };//此处需要一个分号 class Car{ public: string car_name = "Tesla"; void show_car(){ cout << "car_name:" << car_name << endl; } };
- 权限
- public(类内可以访问,类外可以访问)
- protected(类内可以访问,类外可以访问)
- private(类内可以访问,类外可以访问)
- protected 子类可以访问父类的保护内容,private 子类无法访问父类的私有内容
- struct和class的区别
- struct默认访问权限为public
- class默认访问权限为private
struct p1{ int age = 10; void show(){ cout << "age:" << age << endl; } }; class p2{ int age = 10; void show(){ cout << "age:" << age << endl; } }; int main(){ p1 p11; p2 p22; p11.ag;//合法操作 p22.age;//非法操作 }
- 权限私有化的好处
- 控制读写权限
- 控制写入内容的有效性
class Person{ private: //属性设为私有 int age; string name; public: //通过编写getter和setter函数来对外提供访问和修改接口 void setName(string new_name){ //setter name = new_name; } string getName(){ //getter return name; } void setAge(int new_age){ //控制修改数据的合法性 if(new_age <0 or new_age > 100){ cout << "非法数据" << endl; }else{ age = new_age; } }
- 类中的静态变量
- 类中的静态变量为共享变量,同一个类共享所有静态变量
class Person{ public: int a = 10; static int b; }; int Person::b = 100; int main(){ Person p1; Person p2; cout << p1.b << "," << p2.b << endl; p1.a = 123; cout << p1.a << "," << p2.a << endl; //p1.a = 123,p2.a = 10 p1.b = 1220; cout << p1.b << "," << p2.b << endl; //p1.b = 1220,p2.b = 1220,p1和p2共享静态变量b
}
```
-
19.对象的初始化和清理
- 构造函数:实现初始化,在创建时为对象的成员属性赋值,编译器会自动调用。
- 语法:
类名(){}- 无需定义返回值类型
- 函数名与类名一致
- 可以有参数,可以重载
- 程序自动调用且只调用一次,无需手动调用
- 语法:
- 析构函数:在对象销毁前执行清理工作,由系统自动调用。
- 语法:
~类名(){}- 无需定义返回值类型
- 函数名与类名一致,并在类名前加~
- 不可以有参数,无法重载
- 程序自动调用且只调用一次,无需手动调用
class Person{ private: //属性设为私有 int age; string name = ""; public: void setName(string new_name){ name = new_name; } string getName(){ return name; } //构造函数,执行在所有代码前 Person(){ cout << "构造函数测试" << endl; } //析构函数,最后(对象销毁前)执行 ~Person(){ cout << "析构函数测试" << endl; } }; int main(){ Person p; p.setName("coco"); cout << p.getName() << endl; //输出顺序 "构造函数测试" "coco" "析构函数测试" }
- 语法:
- 构造函数的分类
- 按参数: 有参构造和无参构造
- 按类型: 普通构造和拷贝构造
class Person{ public: Person(){ //无参构造为默认的构造函数 cout << "无参构造函数测试" << endl; } Person(int a){ //有参构造函数 cout << "有参构造函数测试" << endl; } Person(const Person &p){ cout << "拷贝构造函数测试" << endl;
- 构造函数的调用
- 括号法
- 显示法
- 隐式转换法
//括号法 Person p1;//无参,无参构造不能加(),否则会当成函数声明 Person p2(1);//有参 Person p3(p2);//拷贝 //显示法 Person p1; Person p2 = Person(10); Person p3 = Person(p2); Person(10); //匿名对象,当前 行执行完后,对象立即被回收。拷贝构造函数无法创建匿名对象。 //隐式转换法 Person p4 = 10;//等价于 Person p4 = Person(10); Person p5 = p4;等价于 Person p5 = Person(p4);
- 构造函数的调用规则
- 默认情况下,编译器会给类添加三个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性值进行拷贝
class Person{ public: int age = 10; string name = "A"; //当我们未定义构造和析构函数时,编译器会自动给类添加以下三个函数 Person{ //无参构造函数,函数体为空 } Person(const Person &p){ //拷贝构造函数, age = p.age; name = p.name; } ~Person{ //无参析构函数,函数体为空 } };
- 如果我们提供了有参构造函数,则编译器不提供无参构造函数,但仍提供拷贝构造函数
class Person{ public: int age = 10; string name = "A"; Person(int a){ //有参构造函数,函数体为空 } //编译器不提供无参构造函数,但仍提供拷贝构造函 Person(const Person &p){ //拷贝构造函数, age = p.age; name = p.name; } ~Person{ //无参析构函数,函数体为空 } };
- 如果我们提供了拷贝构造函数,则编译器不在提供其它构造函数
- 默认情况下,编译器会给类添加三个函数
- 构造函数:实现初始化,在创建时为对象的成员属性赋值,编译器会自动调用。
-
20.浅拷贝与深拷贝
- 浅拷贝是将变量A的地址拷贝给变量B,A发生修改则B也发生修改;B发生修改则A也发生修改。
- 深拷贝是重新开辟内存空间,并将A的值赋值给B,两者之间的修改互不影响。
- C++对于指针的赋值操作默认是浅拷贝
int a = 12; int b = a; //深拷贝,a和b的内存地址不一样 int p = 12; int *p1; p1 = &p; int *p2 = p1; //浅拷贝,p1和p2指向相同内存地址 //要对指针进行深拷贝,需要自己编写拷贝代码 int pp = 123; int *pp1; pp1 = &pp; int *pp2 = new int(*pp1);
-
21.类中静态成员
- 静态成员变量
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
- 静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
class Person{ public: static void f(){ cout << "static void f" << endl; a = 123;//正确,静态成员函数可以访问静态成员变量 b = 123;//错误,静态成员函数无法访问非静态成员变量 } static int a;//静态成员变量,类内声明 int b;//非静态成员变量 } int Person::a = 12;//类外初始化 int main(){ //访问静态成员函数方法1,通过对象访问 Person p1; p1.f(); //访问静态成员函数方法二,通过类名访问 Person::f(); Person p2; p1.a = 123; //此时p1.a == 123,p2.a == 123,说明所有对象共享一份静态变量数据 }
- 成员变量和成员函数分开存储
class Person{ int a;//非静态成员变量,属于类的对象上 static int b;//静态成员变量,不属于类对象 void func() {} //非静态成员函数,不属于类对象 static void func1(){} //静态成员函数,不属于类对象 } int main(){ Peroson p1; cout << sizeof(p1) << endl; // sizeof(p1) == 4,因为只有a变量属于类 }
- 静态成员变量
-
22.this指针
- 每一个非静态成员函数只会保存一份函数,多个同类对象公用一块代码,通过this指着来解决不同对象的调用问题
- this指针是隐含在每一个非静态成员函数内的一种指针,它指向被调用的成员函数所属的对象
- this实际上是一个指针常量
class Person{ int a; void Person(int a){ a = a;//错误操作 this -> a = a;//正确操作,this指向对象本身 } }
-
22.const修饰成员函数
class Person{ public: int a = 10; void f() const { //const修饰this,称为常函数,此时无法修改 this -> a = 123;//错误操作 } class Person{ public: mutable int a = 10; void f() const { //const修饰this,再加mutable,此时可以修改this属性 this -> a = 123;//正确操作 } }; int main(){ const Person p1;//常对象,只能调用常函数 }
-
23.友元
- 作用:让一个类访问令一个类中的私有成员
- 关键词:friend
- 三种实现
- 全局函数做友元
- 类做友元
- 成员函数做友元
//全局函数做友元,仅需在类首添加friend+全局函数声明 class Person{ friend main();//全局函数main做友元 private: int b = 123; }; int main(){ Person p1; p1.b = 1234; cout << p1.b <<endl; }
//类做友元,仅需在类首添加friend+类声明 class Person1{ friend class Person2;//类Person2做友元 private: int b = 123; }; class Person2{ friend void f();//全局函数f做友元 private: Person1 *p; Person2(){ p = new Person1; cout << p -> b << endl; } private: int x = 123; }; void f(){ Person2 p2; cout << p2.x << endl; } int main(){ f(); }
//成员函数做友元示例 //成员函数需类内声明,类外实现,否则编译通不过 //因为C++编译过程是一行一行扫的,如果在类内直接实现函数,则实现函数时可能会用到之后才定义的函数或类,造成编译失败 //错误示范 class p2; class p1{ public: p2 *p; p1(){ p = new p2();//编译器找不到p2的具体实现 } }; class p2{ public: int a = 123; }; int main() { p1 p; } //正确 class p2; class p1{ public: p2 *p; p1(); }; class p2{ public: int a = 123; }; p1::p1(){ p = new p2();//此时编译器已经知道p2的具体实现 } int main() { p1 p; } //********* class Building; class goodGuy{ public: goodGuy(); void visit(); private: Building *building; }; class Building{ friend void goodGuy::visit();//成员函数做友元 public: Building(); string sittingname; private: string bedroom; }; Building::Building() { this -> sittingname = "A"; this -> bedroom = "B"; } goodGuy::goodGuy(){ building = new Building; } void goodGuy::visit(){ cout << building -> sittingname << endl; cout << building -> bedroom << endl; } void test01(){ goodGuy gg; gg.visit(); } int main(){ test01(); }
-
24.运算符重载
- 对已有的运算符进行重新定义,以实现新的功能
- 比如,实现对象1+对象2
- 类似函数重载
- 关键词operator+运算符
- 以成员函数方法实现+重载
//以成员函数方式重载+ class Person{ public: Person(){ } Person(int a,int b){ this -> a = a; this -> b = a; } int a; int b; Person operator+(Person p){ Person pp; pp.a = this -> a + p.a; pp.b = this -> b + p.b; return pp; } }; int main() { Person p1(1,2); Person p2(2,3); Person p3 = p1 + p2; cout << p3.a << ";" << p3.b << endl; }
- 以全局函数方法实现+重载
class Person{ public: Person(){ } int a; int b; }; //通过全局函数重载+ Person operator+(Person p1,Person p2){ Person p; p.a = p1.a + p2.a; p.b = p1.b + p2.b; return p; } int main() { Person p1(1,2); Person p2(2,3); Person p3 = p1 + p2; cout << p3.a << ";" << p3.b << endl; }
-
25.继承
- 语法
class 子类名: 继承方式 继承类名{
…
};
class Person1 : public Person2{ }; //Person1继承Person2
- 继承方式为public
父类权限 子类继承得到的权限 public public protected protected private 不可得 - 继承方式为protected
父类权限 子类继承得到的权限 public protected protected protected private 不可得 - 继承方式为private
父类权限 子类继承得到的权限 public private protected private private 不可得 - 继承中同名处理
直接访问是自身,添加作用域可以访问父类
class father{ public: int a = 123; void f(){ cout << "父类" << endl; } }; class son:public father{ public: int a = 12; void f(){ cout << "子类" << endl; } }; int main(){ son s; cout << s.a << endl;//12 cout << s.father::a << endl;//123 s.f();//子类 s.father::f()//父类 son::father::f()//直接用类名访问 }
- 子类调用父类的有参构造函数
#include <iostream> using namespace std; #include <string> class Person{ public: Person(){cout << "aaaaa" << endl;}; Person(int age,string name){ this -> age = age; this -> name = name; cout << "aacccca" << endl; } int age = 1; string name = "a"; }; class Person1:public Person{ public: Person1(){}; Person1(int a,string b):Person(a,b){ } void f(){ cout << this -> age << "," << this -> name << endl; } }; int main(){ Person1 p1(12,"a"); p1.f(); return 0; }
- 多继承
class 子类:继承方式 父类1,继承方式 父类2…{
};class Person1{ public: int a = 1; }; class Person2{ public: int a = 2; }; class Person:public Person1,public Person2{ public: int a = 3; }; int main() { Person p1; cout << p1.a << endl;//输出3 //重名,加作用域 cout << p1.Person2::a << endl;//输出2 cout << p1.Person1::a << endl;//输出1 }
- 多继承之棱形继承
如上图所示,羊类和驼类均继承了动物类,而草泥马又同时继承了羊类和驼类,该种继承方式称为棱形继承。
棱形继承会带来数据二义性的问题,即草泥马同时继承了动物类中的两份数据,导致数据重复并且这两份数据不一定一致class Animal{ public: int age; }; class Sheep:public Animal{ public: Sheep(){ this -> age = 1; } }; class Camel:public Animal{ public: Camel(){ this -> age = 2; } }; class Alpaca:public Sheep,public Camel{ }; int main() { Alpaca A; //重名,加作用域 cout << A.Sheep::age << endl;//输出1 cout << A.Camel::age << endl;//输出2 //添加作用域可以解决重名问题,但是仍会造成资源浪费(存在两份数据),且会产生歧义 }
解决办法:虚继承 virtual
class Animal{ public: int age; }; class Sheep:virtual public Animal{ public: Sheep(){ this -> age = 1; } }; class Camel:virtual public Animal{ public: Camel(){ this -> age = 2; } }; class Alpaca:public Sheep,public Camel{ }; int main() { Alpaca A; //虚继承,共享Animal类的一份数据 cout << A.Sheep::age << endl;//输出2 cout << A.Camel::age << endl;//输出2 cout << A.age << endl; //可以直接访问 A.Sheep::age = 123; cout << A.Sheep::age << endl;//输出123 cout << A.Camel::age << endl;//输出123 cout << A.age << endl; //输出123 A.age = 12; cout << A.Sheep::age << endl;//输出12 cout << A.Camel::age << endl;//输出12 cout << A.age << endl; //输出12 return 0; }
- 语法
-
26.多态
- 多种形态
- 分类
- 静态多态:函数重载,运算符重载
- 动态多态:派生类和虚函数实现运行时多态
- 区别
- 静态多态的函数地址在编译阶段已确定
- 动态多态的函数地址在运行阶段才确定
- 动态多态满足条件
- 有继承关系
- 子类重写父类的虚函数(重写:参数、返回值类型与原函数一致;重载不需要满足这些条件)
- 动态多态的使用
- 父类的指针或引用作为形参,传入子类对象作为实参
//示例 class Animal{ public: void speak(){ cout << "动物在说话" << endl; } }; class Dog:public Animal{ public: void speak(){ cout << "狗在汪汪叫" << endl; } }; //地址早绑定,在编译阶段就确定了地址,因此即便传入的参数为Animal的子类,执行的speak函数也是Animal的 void f(Animal &animal){ animal.speak(); } int main(){ Dog dog; f(dog); //编译前即绑定speak函数地址,因此输出动物在说话 } //添加virtual关键字,将Animal类中speak转为虚函数即可实现动态多态 class Animal{ public: virtual void speak(){ cout << "动物在说话" << endl; } }; int main(){ Dog dog; f(dog); //运行时才绑定speak函数地址,因此输出狗在汪汪叫 }
-
27.纯虚函数和抽象类
- 纯虚函数
class Animal{ public: virtual void speak() = 0;//纯虚函数 //语法:virtual 返回值类型 函数名(参数列表) = 0; };
- 抽象类
- 含有纯虚函数的类称为抽象类,抽象类类似其它语言的接口
- 抽象类的特点
- 无法实例化,只能作为其它类的父类使用
- 抽象类的子类必须重写父类的纯虚函数,否则仍属于抽象类
//具有纯虚函数speak,因此是抽象类 class Animal{ public: virtual void speak() = 0; }; int main(){ Animal animal;//错误操作,抽象类无法实例化 } //具有纯虚函数speak,因此是抽象类 class Animal{ public: virtual void speak() = 0; }; class Dog:public Animal{ //重写纯虚函数 void speak(){ cout << "dog dog" <<endl; } }; int main(){ Dog dog; }
-
28.虚析构和纯虚析构
- 多态使用时,如果子类中有属性开辟到堆区,父类指针在释放时无法调用到子类的析构代码。
class Animal{ public: Animal(){ cout << "Animal诞生" << endl; } virtual void f() = 0; ~Animal(){ cout << "Animal退出" << endl; } }; class Dog:public Animal{ public: void f(){ cout << "Dog " << endl; } Dog(){ cout << "Dog诞生" << endl; x = new int(12); } int *x; ~Dog(){ if( x != NULL){ //未被访问 cout << "Dog退出" << endl; delete x; x = NULL; } } }; int main(){ Animal * animal = new Dog; animal -> f(); delete animal; /*输出: Animal诞生 Dog诞生 Dog Animal退出 /* //父类指针指向子类,父类指针在析构时不会调用子类的析构函数,因此如果子类在堆区中有数据,会出现内存泄漏问题 }
- 将父类中的析构函数改为虚析构或纯虚析构可以解决这个问题。
//虚函数版本 class Animal{ public: Animal(){ cout << "Animal诞生" << endl; } virtual void f() = 0; //仅在此处添加virtual即可 virtual ~Animal(){ cout << "Animal退出" << endl; } }; class Dog:public Animal{ public: void f(){ cout << "Dog " << endl; } Dog(){ cout << "Dog诞生" << endl; x = new int(12); } int *x; ~Dog(){ if( x != NULL){ cout << "Dog退出" << endl; delete x; x = NULL; } } }; int main(){ Animal * animal = new Dog; animal -> f(); delete animal; /*输出: Animal诞生 Dog诞生 Dog Dog退出 Animal退出 */ }
//纯虚析构版本 class Animal{ public: Animal(){ cout << "Animal诞生" << endl; } virtual void f() = 0; //纯虚析构函数 类内声明 virtual ~Animal() = 0; }; class Dog:public Animal{ public: void f(){ cout << "Dog " << endl; } Dog(){ cout << "Dog诞生" << endl; x = new int(12); } int *x; ~Dog(){ if( x != NULL){ cout << "Dog退出" << endl; delete x; x = NULL; } } }; //纯虚析构函数 类外访问 Animal::~Animal(){ cout << "Animal退出" << endl; } int main(){ Animal * animal = new Dog; animal -> f(); delete animal; /*输出: Animal诞生 Dog诞生 Dog Dog退出 Animal退出 */ }
- 具有纯虚析构函数的类也为抽象类