类与对象
C++面向对象的三大特性:封装、继承、多态
C++中,任何事物都为对象 ,对象有其属性和行为
具有相同性质 的对象,我们可以抽象称为类
封装
封装的实现
意义:
将属性和行为作为一个整体,表现生活中的事物
将属性和行为加以权限控制
语法:class <类名>{ <访问权限>: <属性/行为>}
一些名词:
成员:类中的属性和行为的统称
成员属性(成员变量):类中的属性
成员函数(成员方法):类中的函数
e.g.1 设计一个圆类,求其周长
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> using namespace std;const double PI = 3.14 ;class circle {public : int r; double calC () { return 2 *PI*r; } }; int main () { circle c1; c1.r = 10 ; cout << "r=" << c1.calC ()<<endl; return 0 ; }
e.g.2 设计一个学生类,属性有姓名和学号,可以赋值并显示姓名和学号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <iostream> #include <string> using namespace std;class student {public : string name; int id; void display () { cout << "name:" << name<<endl; cout << "id:" << id <<endl; } void set_info (string s_name, int s_id) { name = s_name; id = s_id; } }; int main () { student stu1; student stu2; stu1.name = "Jack" ; stu1.id = 1 ; stu1.display (); stu2.set_info ("Mary" , 2 ); stu2.display (); return 0 ; }
权限
类可以将属性和方法放在不同权限下:
public:公共权限,成员类内可以访问,类外也可以访问
protected:保护权限,成员类内可以访问,类外不可以访问,子类可以访问父类的保护成员
private:私有权限,成员类内可以访问,类外不可以访问,
子类不可访问父类的私有成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> #include <string> using namespace std;class person {public : string name; protected : string car; private : int password; public : void f () { name = "Jack" ; car = "xiaomisu7" ; password = 123456 ; } }; int main () { person p; p.name = "Mary" ; return 0 ; }
struct与class的区别
C++中class与struct的唯一区别在于默认的访问权限不同
成员属性私有化
优点:
基本思路:用公有方法对私有属性进行操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <iostream> #include <string> using namespace std;class person {private : string name; int age = 18 ; string hobby; public : void setName (string s) { name = s; } void getName () { cout <<"name:" <<name<<endl; } void getAge () { cout <<"age:" <<age<<endl; } void setHobby (string s) { hobby = s; } }; int main () { person p; p.setName ("Jack" ); p.getAge (); p.setHobby ("jerk" ); return 0 ; }
对象的初始化和清理
对象的初始化和清理是很重要的安全问题:
一个对象或变量没有初始状态,对其使用后果未知
使用完一个对象或变量,没有及时清理,也会造成安全问题
构造函数与析构函数
C++提供了这两种函数,被编译器自动调用,完成对象初始化与清理工作
这两个工作是强制执行的,若我们不提供,编译器会提供自己的构造函数与析构函数,他们是空实现
构造函数: 用于创建对象时为对象成员赋值
语法:<类名>(){}
无返回值,不用写void
函数名与类名相同
构造函数可以有参数,可以发生重载
调用对象时会自动调用构造,只会调用一次
析构函数:用于对象销毁前自动调用,执行清理操作
语法:~<类名>(){}
无返回值,不用写void
函数名与类名相同,名前面加~
析构函数不可以有参数,不可发生重载
程序在对象销毁前会自动调用析构。只会调用一次
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <iostream> using namespace std;class person { public : person (){ cout << "构造函数被调用" << endl; } ~person (){ cout << "析构函数被调用" << endl; } }; int main () { person p; return 0 ; }
构造函数的分类与调用
分类:
参数分:有参构造与无参构造(默认构造)
类型分:普通构造与拷贝构造
调用:
注意:
无参构造时不需要加括号,否则编译器会认为是一个函数声明
显式声明中,单纯调用对象<类名>([值])称为匿名对象 ,特点是当前行执行后就会被立即回收
不要利用拷贝构造函数初始化匿名对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <iostream> using namespace std;class person { public : person (){ cout << "无参构造函数被调用" << endl; } person (int a){ age = a; cout << "有参构造函数被调用" << endl; } person (const person &p){ age = p.age; cout << "拷贝构造函数被调用" << endl; } ~person (){ cout << "析构函数被调用" << endl; } int age; }; int main () { person p; person p1 (10 ) ; person p2 (p1) ; cout << "age of p1:" << p1.age << endl; cout << "age of p2:" << p2.age << endl; person p3 = person (20 ); person p4 = person (p3); person p5 = 10 ; person p6 = p5; return 0 ; }
拷贝构造函数调用时机
拷贝构造调用的三种情况:
使用一个已经创建完毕的对象来初始化一个新对象
值传递的方式给函数参数传值
以值方式返回局部对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <iostream> using namespace std;class person { public : person (){ cout << "无参构造函数被调用" << endl; } person (int a){ age = a; cout << "有参构造函数被调用" << endl; } person (const person &p){ age = p.age; cout << "拷贝构造函数被调用" << endl; } ~person (){ cout << "析构函数被调用" << endl; } int age; }; void test1 () { person p1 (20 ) ; person p2 (p1) ; } void test2 (person p) { return ; } person test3 () { person p1; return p1; } int main () { person p1; test1 (); test2 (p1); person p2 = test3 (); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 无参构造函数被调用 ---------------- 有参构造函数被调用 拷贝构造函数被调用 析构函数被调用 析构函数被调用 ---------------- 拷贝构造函数被调用 析构函数被调用 ---------------- 无参构造函数被调用 析构函数被调用 析构函数被调用
构造函数调用规则
默认下,编译器至少给一个类添加三个函数:
默认构造函数(无参,函数体为空)
默认析构函数(无参,函数体为空)
默认拷贝构造函数,对属性值拷贝
调用规则:
若用户定义有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝构造
如果用户定义拷贝构造函数 ,C++不再提供其他构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> using namespace std;class person { public : person (){ cout << "无参构造函数被调用" << endl; } person (int a){ age = a; cout << "有参构造函数被调用" << endl; } ~person (){ cout << "析构函数被调用" << endl; } int age; }; int main () { person p; p.age = 18 ; person p2 (p) ; cout << "p2的年龄是:" << p2.age << endl; return 0 ; }
此种情况下,编译器使用了默认拷贝构造,进行了值传递
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <iostream> using namespace std;class person { public : person (int a){ age = a; cout << "有参构造函数被调用" << endl; } ~person (){ cout << "析构函数被调用" << endl; } int age; }; int main () { person p; p.age = 18 ; person p2 (p) ; cout << "p2的年龄是:" << p2.age << endl; return 0 ; }
此种情况下,由于有有参函数,编译器不会提供默认无参函数,同时会报错没有默认无参函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> using namespace std;class person { public : person (const person &p){ age = p.age; cout << "拷贝构造函数被调用" << endl; } ~person (){ cout << "析构函数被调用" << endl; } int age; }; int main () { person p; p.age = 18 ; person p2 (p) ; cout << "p2的年龄是:" << p2.age << endl; return 0 ; }
此种情况下,提供了拷贝函数,则编译器不再提供任何构造函数,同时报错没有默认构造函数
深拷贝与浅拷贝
浅拷贝:简单的赋值操作(默认拷贝构造即为浅拷贝 )
深拷贝:在堆区重新申请空间,进行拷贝操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <iostream> using namespace std;class person { public : person (){ cout << "无参构造函数被调用" << endl; } person (int a, int iheight){ age = a; height = new int (iheight); cout << "有参构造函数被调用" << endl; } ~person (){ if (height!=NULL ){ delete height; height = NULL ; } cout << "析构函数被调用" << endl; } int age; int * height; }; int main () { person p1 (18 ,160 ) ; cout << "p1的年龄是:" << p1.age << endl; cout << "p1的身高是:" << *p1.height << endl; person p2 (p1) ; cout << "p2的年龄是:" << p2.age << endl; cout << "p2的身高是:" << *p2.height << endl; return 0 ; }
该代码会卡住,原因是利用拷贝构造只进行了值传递,p1与p2执行完毕后,均执行析构函数,对height指向的空间进行释放,但是其中一个已经执行释放后,另一个再次释放会产生非法操作,故出错
这也就是浅拷贝的缺点:只进行了值传递,我们需要利用深拷贝来结局
1 2 3 4 5 6 7 person (const person &p){ age = p.age; height = new int (*p.height); cout << "拷贝构造函数被调用" << endl; }
即在拷贝构造中,重新给拷贝的指针变量申请一块空间
如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝出现问题
初始化列表
C++提供了初始化列表来初始化属性
语法:<构造函数>(): <属性1>(<值1>), <属性2>(<值2>) ... {}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <iostream> using namespace std;class person { public : int a; int b; int c; person (int a, int b, int c):a (10 ), b (20 ), c (30 ){ } }; int main () { person p; cout << "a:" << p.a << endl; cout << "b:" << p.b << endl; cout << "c:" << p.c << endl; return 0 ; }
类对象作为类成员
C++中的成员可以是另一个类的对象,称为对象成员
如
1 2 3 4 class A {};class B { A a; };
该B类中有对象A作为成员,则A为对象成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <iostream> #include <string> using namespace std;class phone {public : string brand; float price; phone (string pname){ brand = pname; cout << "phone构造函数被调用" << endl; } ~phone (){ cout << "phone析构函数被调用" << endl; } }; class person { public : string name; phone iphone; person (string pname, string pbrand):name (pname),iphone (pbrand){ cout<< "person构造函数被调用" << endl; } ~person (){ cout << "person析构函数被调用" << endl; } }; int main () { person p ("小明" ,"苹果" ) ; cout << "姓名:" << p.name << endl; cout << "手机品牌:" << p.iphone.brand << endl; return 0 ; }
1 2 3 4 5 6 phone构造函数被调用 person构造函数被调用 姓名:小明 手机品牌:苹果 person析构函数被调用 phone析构函数被调用
注意构造函数的调用顺序:
当其他类对象作为本类成员,构造时先构造类对象,在构造自身
当其他类对象作为本类成员,析构时先析构自身,再析构类对象
静态成员
在成员变量与成员函数前加上关键字static,称为静态成员
分类:
静态成员变量
不属于某个对象,所有对象共享一份数据
编译阶段分配内存
类内声明,类外初始化
静态成员函数
所有成员共享同一个函数
静态成员函数只能访问静态成员变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <iostream> using namespace std;class person { public : static int a; int c; static void f () { cout << "静态成员函数f调用" <<endl; a = 100 ; } private : static int b; }; int person::a = 100 ; int person::b = 100 ; int main () { person p; cout << p.a<<endl; person p2; p2.a = 200 ; cout << p.a<<endl; cout << person::a<<endl; p.f (); person::f (); return 0 ; }
应用:实现单例对象,即一个类只有一个类对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <iostream> using namespace std;class Singleton {private : static Singleton* m_insntance; Singleton (){} ~Singleton (){} public : Singleton (const Singleton& obj) = delete ; Singleton& operator =(const Singleton&) = delete ; static Singleton* getInstance () { if (!m_insntance){ m_insntance = new Singleton (); } return m_insntance; } private : string m_name; public : void setName (const string& name) { m_name = name; } const string& getName () { return m_name; } }; Singleton* Singleton::m_insntance = NULL ; int main () { Singleton* s1 = Singleton::getInstance (); cout << s1->getName ()<<endl; Singleton* s2 = Singleton::getInstance (); cout << s1->getName ()<<endl; }
C++对象模型与this指针
成员变量与成员函数分开存储
c++中,类内成员变量与成员函数分开存储
只有非静态成员变量才属于类的对象上
对于空对象,编译器会给每个空对象分配一个字节空间,是为了区分空对象占内存的位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <iostream> using namespace std;class person { }; class person2 { int a; }; class person3 { int a; static int b; void f () {} }; int main () { person p; person2 p2; person3 p3; cout << "size of empty class: " << sizeof (p)<<endl; cout << "size of class person2: " << sizeof (p2)<<endl; cout << "size of class person3: " << sizeof (p3)<<endl; return 0 ; }
this指针
C++通过提供this指针解决了区分多个对象调用同一块代码分不清的问题
this指针指向被调用的成员函数所属的对象 ,不需要定义,直接使用
用途:
形参和成员变量同名时,可以用this指针来区分
在类的非静态成员函数中返回对象本身,可使用retrun *this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <iostream> using namespace std;class person { public : int age; person (int age){ this ->age = age; } person& addage (person &p) { this ->age+=p.age; return *this ; } }; int main () { person p (18 ) ; person p2 (10 ) ; cout <<"age:" <<p.age<<endl; p2.addage (p).addage (p).addage (p); cout <<"age of p2:" <<p2.age<<endl; return 0 ; }
空指针访问成员函数
C++允许空指针调用成员函数,但是要注意有没有用到this指针
如果用到this指针,要注意保证代码的鲁棒性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <iostream> using namespace std;class person { public : int age; void show () { cout <<"this is person class" <<endl; } void showage () { if (this ==NULL ){ return ; } cout << "age = " <<age <<endl; } }; int main () { person *p = NULL ; p->show (); p->showage (); return 0 ; }
const修饰成员函数
常函数:
成员函数后加const称该函数为常函数
常函数内不可以修改成员属性
成员属性声明时加关键字mutable后,在常函数内依然可以修改
常对象:
声明对象前加const称该对象为常对象
常对象构造时需要对内部成员变量初始化(构造函数)
常对象只能调用常函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <iostream> using namespace std;class person { public : int age; mutable int b; void show () const { this ->b = 100 ; } void f () {} person (){ } }; int main () { const person p; p.b = 100 ; p.show (); return 0 ; }
类的特殊成员函数
特殊成员函数包括默认构造函数、拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符、析构函数,他们均可以用以下两个关键字修饰
可平凡复制类
C++中,各种数据类型及他们的数组、指针、枚举等在内存中是连续存储的,可以使用memcpy()函数进行拷贝,称为可平凡复制类型
一个类若满足以下条件,被称为可平凡复制类,也是一种可平凡复制类型
没有虚函数
没有用户自定义的特殊成员函数
有自动生成的析构函数
所有非静态成员变量也是可平凡复制类型
拷贝、移动构造函数,拷贝、移动赋值运算符中至少有一个是未被删除的
对于继承类,其父类要满足上述条件
可以使用<type_traits>库中的is_trivially_copyable<类>::value来判断是否可拷贝
标准布局类
C++中,所有标量类型都是标准布局类型
对一个非继承类,满足以下条件被称为标准布局类
没有虚函数
所有类成员都具有相同访问属性
所有类成员都是标准布局类型
若类是继承类,则满足该类和父类中只有一个类有非静态数据成员,且类型与访问控制满足上述条件
可以使用<type_traits>库中的is_standard_;leyout<类>::value来判断是否可拷贝
友元
友元可以让一个函数或者类访问另一个类中的私有成员
友元的关键字为friend
全局函数做友元
在class中声明对应全局函数,并在最前面加friend关键词,可以声明友元
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> using namespace std;class building { friend void fir (building &building) ; public : string sitting_room; private : string bedroom; public : building (){ sitting_room = "客厅" ; bedroom = "卧室" ; } }; void fir (building &building) { cout << "访问公共属性:" <<building.sitting_room<<endl; cout << "访问私有属性:" <<building.bedroom<<endl; } int main () { building b; fir (b); }
类做友元
在class中声明对应类,并在最前面加friend关键词,可以声明友元,被声明的类可以访问原类中的私有属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <iostream> using namespace std;class buildings ;class fri {public : buildings* building; fri (); void visit () ; }; class buildings { friend class fri ; public : string sitting_room; buildings (); private : string bedroom; }; buildings::buildings (){ sitting_room = "客厅" ; bedroom = "卧室" ; } fri::fri (){ building = new buildings; } void fri::visit () { cout <<"访问公共属性:" <<building->sitting_room<<endl; cout <<"访问私有属性:" <<building->bedroom<<endl; } int main () { fri f; f.visit (); }
成员函数做友元
在class中声明对应类中的成员函数,并在最前面加friend关键词,可以声明友元,被声明的成员函数可以访问原类中的私有属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <iostream> using namespace std;class buildings ;class fri {public : buildings* building; fri (){ building = new buildings; } void visit () ; }; class buildings { friend void fri::visit () ; public : string sitting_room; buildings (){ sitting_room = "客厅" ; bedroom = "卧室" ; } private : string bedroom; }; void fri::visit () { cout <<"访问公共属性:" <<building->sitting_room<<endl; cout <<"访问私有属性:" <<building->bedroom<<endl; } int main () { fri f; f.visit (); }
运算符重载
运算符重载可以对已有的运算符重新定义,赋予另一种功能以适用于不同的数据类型
如我们通过写成员函数来实现两个类相加属性后返回新的对象
可以通过编译器提供的通用名称operator来重载运算符以简化运算
重载的本质调用为(以person类为例):
成员函数:person p3 = p1.operator<运算符>(p2)
全局函数:person p3 = p1.operator<运算符>(p1, p2)
注意:
运算符重载也可以实现函数重载
对于内置数据类型的表达式的运算符是不可以重载的
不要滥用运算符重载
加号运算符重载
成员函数:
1 2 3 4 5 6 person operator +(person &p){ person temp; temp.a = this ->a+p.a; temp.b = this ->b+p.b; return temp; }
全局函数:
1 2 3 4 5 6 person operator +(person &p1, person &p2){ person temp; temp.a = p1.a+p2.a; temp.b = p1.b+p2.b; return temp; }
左移运算符重载
1 2 3 4 5 6 7 8 ostream & operator <<(ostream &cout, person &p){ cout << "a=" << p.a << endl << "b=" << p.b; return cout; }
递增运算符重载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 myint& operator ++(){ num++; return *this ; } myint operator ++(int ){ myint temp = *this ; num++; return temp; }
赋值运算符重载
C++编译器会额外给一个类添加赋值运算符的重载函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> using namespace std;class person {public : int *age; person (int age){ this ->age = new int (age); } ~person (){ if (age!=NULL ){ delete age; age = NULL ; } } }; int main () { person p1 (1 ) ; cout << "p1's age:" << *p1.age << endl; person p2 (2 ) ; p2 = p1; cout <<"p2's age:" <<*p2.age<<endl; }
该代码出错,由于age开到了同一块堆区内存,故在p1赋值给p2时,两者的age指针都指向同一块区域
则当p2执行完毕,执行析构函数时,将该堆区内存释放;再等p1执行完毕,执行析构函数时,将已经释放的内存重复释放了,故报错
我们需要通过重载赋值运算符来实现深拷贝
1 2 3 4 5 6 7 8 9 10 person& operator =(const person &p){ if (age!=NULL ){ delete age; age = NULL ; } age = new int (*p.age); return *this ; }
关系运算符重载
实现自定义数据类型的比较
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 bool operator ==(const person &p){ if (this ->name==p.name && this ->age==p.age){ return true ; } else return false ; } bool operator !=(const person &p){ if (this ->name==p.name && this ->age==p.age){ return false ; } else return true ; }
大于小于类似
函数调用运算符重载
函数调用运算符即()
重载后的使用方式很像函数的调用,故也称为仿函数
仿函数没有固定写法
1 2 3 4 5 6 7 8 9 10 11 12 13 class print {public : void operator () (string s) { cout << s << endl; } }; class add {public : int operator () (int n1, int n2) { return n1+n2; } };
也可以用匿名函数对象进行调用
继承
有些类与其他类之间除了有共性,还有自己的特性,我们可以用继承的技术来减少重复代码优点:减少重复代码
语法:<子类>: <继承方式> <父类>
其中子类也称作派生类,父类也称基类
子类中的成员包含两部分:
一类是从基类继承过来的,一类是自己增加的成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class page {public : void f1 () { cout << "f1" <<endl; } void f2 () { cout << "f2" <<endl; } void f3 () { cout << "f3" << endl; } }; class page2 :public page{public : void s1 () { cout << "s1" <<endl; } };
继承方式
即:
私有成员子类均不可访问
公共继承下:各成员权限不变
保护继承下:public变为protected
私有继承下:public与protected变为private
继承中的对象模型
父类中所有非静态成员属性都会被子类继承
而私有属性被编译器隐藏,故访问不到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> using namespace std;class fa { public : int a; protected : int b; private : int c; }; class son :public fa{ public : int d; }; int main () { cout << sizeof (son)<<endl; }
继承中的构造与析构顺序
子类继承父类后,创建子类对象时,也会调用父类的构造函数
顺序:先构造父类,再构造子类,析构相反
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #include <iostream> using namespace std;class base { public : base (){ cout <<"base构造函数" <<endl; } ~base (){ cout <<"base析构函数" <<endl; } }; class son :public base{ public : son (){ cout <<"son构造函数" <<endl; } ~son (){ cout <<"son析构函数" <<endl; } }; int main () { son s; }
1 2 3 4 base构造函数 son构造函数 son析构函数 base析构函数
同名成员处理方式
当子类和父类出现同名成员:子类的同名成员会隐藏掉所有父类中同名成员(函数重载也会被隐藏)
访问子类同名成员:直接访问
访问父类同名成员:加作用域
静态与非静态处理方式一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include <iostream> using namespace std;class fa {public : int a; static int b; fa (){ a = 100 ; } void f () { cout <<"f in fa" <<endl; } static void ff () { cout <<"ff in fa" <<endl; } }; class son :public fa{public : int a; static int b; son (){ a = 200 ; } void f () { cout <<"f in son" <<endl; } static void ff () { cout <<"ff in son" <<endl; } }; int fa::b = 10 ;int son::b = 20 ;int main () { son s; cout << "a in son:" <<s.a<<endl; cout << "a in fa:" <<s.fa::a<<endl; s.f (); s.fa::f (); cout <<"static b in son:" <<s.b<<endl; cout <<"static b in fa:" <<s.fa::b<<endl; s.ff (); s.fa::ff (); cout <<"static b in son:" <<son::b <<endl; cout <<"static b in fa:" <<son::fa::b<<endl; son::ff (); son::fa::ff (); }
多继承
C++允许一个类继承多个类
语法:<子类>: <继承方式> <父类1>, <继承方式> <父类2>...
同名成员出现时需要加作用域区分
实际开发时不建议使用多继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <iostream> using namespace std;class fa {public : int a; fa (){ a = 100 ; } void f () { cout <<"f in fa" <<endl; } }; class fa2 {public : int a; fa2 (){ a= 200 ; } void f () { cout <<"f in fa" <<endl; } }; class son :public fa, public fa2{public : int c; int d; son (){ c = 300 ; d = 400 ; } }; int main () { cout << "size of son:" <<sizeof (son)<<endl; son s; cout <<"a in fa:" <<s.fa::a<<endl; cout <<"a in fa2:" <<s.fa2::a<<endl; }
菱形继承
概念:两个派生类继承同一个基类,又有某个类同时继承两个派生类
菱形继承时,两个父类拥有相同数据,需要加作用域区分
但该数据对于最后的子类来说存在两份,会浪费空间,需要利用虚继承 来解决问题
在需要虚继承的部分的访问权限前添加关键字vritual即可,此时对应父类称为虚基类
原理:此时的父类继承的是虚基类指针(vbptr),指向一个虚基类表,该表存储了指针位置以及对应偏移量,根据偏移量便可以找到继承的那唯一一份数据
缺点:虚继承占用内存很大
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <iostream> using namespace std;class animal {public : int age; }; class sheep : virtual public animal{};class camel : virtual public animal{};class alpaca : public sheep, public camel{};int main () { alpaca st; st.sheep::age = 18 ; st.camel::age = 20 ; cout << "age of sheep: " <<st.sheep::age<<endl; cout << "age of camel: " <<st.camel::age<<endl; cout << "age of alpaca: " <<st.alpaca::age<<endl; }
多态
分类
静态多态:函数和运算符重载属于静态多态,复用函数名;函数地址在编译阶段确定
动态多态:派生类和虚函数实现运行时多态;函数地址在运行阶段确定
### 动态多态
满足条件:
使用:父类的指针或者引用指向子类对象
原理:父类记录的也是虚基类指针,指向对应虚基类表。当子类重写后,对应指向的虚基类表被现子类覆盖,得到对应参数
优点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <iostream> using namespace std;class animal {public : virtual void speak () { cout <<"speaking" <<endl; } }; class cat : public animal{public : void speak () { cout <<"cat is speaking" <<endl; } }; void dospeak (animal &ani) { ani.speak (); } int main () { cat cat; dospeak(cat); }
在开发时,提倡开闭原则:即对扩展进行开放,对修改进行关闭
纯虚函数与抽象类
多态中,通常父类中的虚函数实现是毫无意义的,都是调用子类中的重写内容
因此可以将虚函数改为纯虚函数
语法:virtual <返回值类型> <函数名>([参数列表]) = 0;
此时这个类称为抽象类
抽象类无法实例化对象
子类必须重写抽象类中的纯虚函数,否则也属于抽象类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <iostream> using namespace std;class base {public : virtual void func () = 0 ; }; class son :public base{public : virtual void func () { cout <<"func调用" <<endl; } }; int main () { base *b = new son; b->func (); }
虚析构与纯虚析构
多态使用时,如果子类中有属性开辟到堆区,则父类指针在释放时就无法调用到子类的虚构代码,此时需要将父类中的析构函数改为虚析构或纯虚析构
可以解决父类指针释放子类对象
都需要有函数具体实现
其中,纯虚析构需要类外实现
有纯虚析构后该类便成为了抽象类
语法:virtual ~<类名>(){}或virtual ~<类名>()=0;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <iostream> using namespace std;class animal {public : virtual void speak () = 0 ; virtual ~animal (){ cout <<"animal析构调用" <<endl; } }; class cat : public animal{public : cat (string name){ cname = new string (name); } virtual void speak () { cout <<*cname<<"'s speaking" <<endl; } ~cat (){ if (cname !=NULL ){ cout <<"cat析构调用" <<endl; delete cname; cname = NULL ; } } string *cname; }; int main () { animal * ani = new cat ("Tom" ); ani->speak (); delete ani; }
杂项
override与final限定符
在基类是虚函数的情况下,子类对该虚函数进行重写,在要重写的函数后面加上override可以防止因为子类的拼写错误,数据类型错误等问题,导致无法调用到子类而直接调用了基类
(若不相同会直接报错)
在虚函数声明的结尾加上final可以防止该虚函数被子类重写,在类声明的后面加上final可以防止该类被继承
注意这两个限定符不是保留关键字
const成员函数
在成员函数声明和定义的后面加上关键字const
const成员函数不能修改其成员变量,不能调用非const成员函数,不能通过const类型的对象调用非const成员函数
即常量对象只可以调用常量方法