函数分文件编写 1 2 3 4 创建.h后缀名头文件 创建.cpp后缀名源文件 在头文件中写函数的声明 在源文件中写函数的定义
指针在32位机器下(×86)通通占4个字节,不管是什么数据类型 指针在64位机器下通通占8个字节,不管是什么数据类型 1 2 3 4 5 6 7 8 9 10 int main () { int a=10 ; int *p=&a; cout<<"sizeof(int *)=" sizeof (int *)<<endl; cout<<"sizeof(float *)=" sizeof (float *)<<endl; cout<<"sizeof(double *)=" sizeof (double *)<<endl; cout<<"sizeof(char *)=" sizeof (char *)<<endl; }
空指针和野指针
总结:空指针和野指针都不是我们申请访问的空间,因此不要访问
const修饰指针 1.const修饰指针——-常量指针 指针指向可以改,指针的指向值不可以改 2.const修饰常量——-指针常量 指针指向不可以改,指针的指向值可以改 引用的本质在C++内部实现是一个指针常量(引用一旦初始化就不能发生改变,变成别的变量的别名)所有的指针操作都是编译器做的 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void func (int & ref) { ref=100 ; } main (){ int a=10 ; int & ref=a; ref=20 ; cout<<"a:" <<a<<endl; cout<<"ref:" <<ref<<endl; fun (a); return 0 ; }
3.const既可以修饰指针也可以修饰常量 指针指向和指针的指向都不可以改 1 2 3 4 5 6 7 8 9 10 11 12 13 14 const int *p=&a;*p=20 ; p=&b; int * const p=&a;*p=20 ; p=&b; const int * const p=&a;*p=20 ; p=&b;
结构体中const的使用场景(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 31 32 33 34 struct student (){ string name; int age; int score; }; void printStudent (const student *s) { cout<<"姓名" <<s.name<<"年龄" <<s.age<<s.age<<"得分:" <<s.score<<endl; } int main () { struct student s={"张三" ,15 ,70 }; printStudents (&s); cout<<"main中张三的年龄为" <<s.age<<endl; system ("pause" ); }
常量引用:使用场景是 用来修饰形参,防止误操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void show (const int &val) { cout<<"val=" <<val<<endl; } int main () { const int &ref=10 ; int a=100 ; showValue (a); cout<<"a:" <<a<<endl; system ("pause" ); return 0 ; }
内存分区模型
代码区:存放函数体的二进制代码,由操作系统进行管理(共享和只读)
全局区:存放全局变量和静态变量以及常量(字符串常量和其他常量,const修饰的变量)(这个区域的数据在程序结束后由操作系统释放)
栈区:由编译器自动分配释放,存放函数的函数值,局部变量等
堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统自动回收
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int * func () { int *p=new int (10 ); return p; } int main () { int *p=func (); cout<<*p<<endl; system ("pause" ); }
new操作符 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 int * func () { int *p=new int (10 ); return p; } void text01 () { int *p=func (); cout<<*p<<endl; } void text02 () { int * arr=new int [10 ]; for (int i=0 ;i<10 ;i++) { arr[i]=i+100 ; } for (int i=0 ;i<10 ;i++) { cout<<arr[i]<<endl; } delete []arr; } int main () { }
注意:代码区和全局区都是程序在运行前的一个区域(C++中在程序运行前分为全局区和代码区),在程序运行后才会有栈区和堆区 引用(给变量起别名) 数据类型 &别名=原名 引用的注意事项: 1.引用必须要初始化
2.引用一旦初始化就不可以更改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int main () { int a=10 ; int &b=a; int c=20 ; b=c; cout<<"a=" <<a<<endl; cout<<"b=" <<b<<endl; cout<<"c=" <<c<<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 28 29 30 31 32 33 void mySwap01 (int a,int b) { int temp=a; a=b; b=temp; } void mySwap02 (int *a,int *b) { int temp=*a; *a=*b; *b=temp; } void mySwap03 (int &a,int &b) { int temp=a; a=b; b=temp; } int main () { int a=10 ; int b=20 ; cout<<"a=" <<a<<endl; cout<<"b=" <<b<<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 28 29 30 31 int & text01 () { int a=10 ; return a; } int & text02 () { static int a=10 ; return a; } int main () { int &ref=text02 (); cout<<"ref=" <<ref<<endl; cout<<"ref=" <<ref<<endl; cout<<"ref=" <<ref<<endl; text02 ()=1000 ; cout<<"ref=" <<ref<<endl; cout<<"ref=" <<ref<<endl; }
函数提高 函数默认参数 语法:返回值类型 函数名(形参=默认值){} 1 2 3 4 5 6 7 8 9 10 11 12 int func (int a,int b=20 ,int c=30 ) { return a+b+c; } int main () { cout<<func (10 )<<endl; cout<<func (10 ,30 )<<endl; system ("pause" ); return 0 ; }
注意事项:1.如果某个位置已经有了默认参数,那么从这个位置往后,从左到右都必须有默认值 例如: int func2(int a,int b=10,int c=20,int d=30) 2.如果函数的声明有默认参数,那么函数实现就不能有默认参数(声明和实现只能有一个有默认参数) 1 2 3 4 5 int func3 (int a=10 ,int b=10 ) ;
函数占位参数 语法:返回值类型 函数名 (数据类型){} //小括号中只有一个数据类型,没有参数 C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置 1 2 3 4 5 6 7 8 9 10 void func (int a,int ) { cout<<"this is func" <<endl; } int main () { func (10 ,10 ); system ("pause" ); }
目前阶段占位函数用不到 函数重载(函数名可以相同)(析构函数没有参数,因此它不能被重载。一个类可以有多个构造函数,但是只能有一个析构函数) 作用:函数名可以相同,提高复用性 函数重载满足条件:
同一个作用域下(全局作用域)
函数名称相同
函数参数类型不同 或者 个数不同 或者 顺序不同
注意:函数的返回值不可以作为函数重载的条件(无法重载仅仅按返回类型区分的函数) 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 void func () { cout<<"func的调用" <<endl; } void func (int a) { cout<<"func(int a)的调用" <<endl; } void func (double a) { cout<<"func(double a)的调用" <<endl; } void func (int a,double b) { cout<<"func(int a,double b)的调用" <<endl; } void func (double a,int b) { cout<<"(double a,int b)的调用" <<endl; } int main () { func (2.12 ,10 ); system ("pause" ); }
函数重载注意事项 1.引用作为重载条件 2.函数重载遇到默认参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void func (int &a) { cout<<"func(int &a)调用" <<endl; } void func (const int &a) { cout<<"func(const int &a)调用" <<endl; } int main () { int a=10 ; func (a); func (10 ); system ("pause" ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void func2 (int a,int b) { cout<<"func2(int a,int b)" <<endl; } void func2 (int a) { cout<<"func2(int a)" <<endl; } int main () { }
构造函数语法:类名(){}
构造函数,没有返回值也不写void
函数名称与类名相同
构造函数有参数,因此可以发生重载
程序在调用对象时会自动调用构造,无需手动调用,而且只会调用一次
构造函数的分类及调用 两种分类方式: 按参数分为:有参构造和无参构造(默认构造)
按类型分为:普通构造和拷贝构造
三种调用方式: 1.括号法: 1 2 3 4 5 Person p1; Person p2 (10 ) ;Person p3 (p2) ;
2.显示法: 1 2 3 4 5 6 7 Person p1; Person p2=Person (10 ); Person p3=Person (p2); Person (10 );
3.隐式转换法: 1 2 Person p4=10 ; Person p5=p4;
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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 class Person { public : Person () { cout<<"Person默认构造函数调用" <<endl; } Person (int age) { cout<<"Person有参函数调用" <<endl; m_age=age; } Person (const Person &p) { cout<<"Person拷贝构造函数调用" <<endl; m_age=p.m_age; } ~Person (int age) { cout<<"Person析构函数调用" <<endl; } int m_age; }; void text01 () { Person p1 (20 ) ; Person p2 (p1) ; cout<<"p2的年龄为: " <<p2.m_age<<endl; } void dowork (Person p) { } void text02 () { Person p; dowork (p); } Person dowork2 () { Person p1; cout<<(int *)&p1<<endl; return p1; } void text03 () { Person p=dowork2 (); cout<<(int *)&p<<endl; } main (){ text01 (); text02 (); text03 (); }
注意:
(1)在使用对象赋值语句进行对象赋值时,两个对象的类型必须相同,如对象的类型不同,编译时将出错。 (2)两个对象之间的赋值,仅仅是对其中的数据成员赋值,而不对成员函数赋值。 (3)数据成员是占存储空间的,不同对象的数据成员占有不同的存储空间,而不同对象的成员函数是占有同一个函数代码段,无法对它们赋值。
析构函数语法:~类名(){}
析构函数,没有返回值也不写void
函数名称与类名相同,在名称前加上符号~
析构函数不可以有参数,因此不能发生重载
程序在对象销毁前会自动调用析构,无需手动调用且只会调用一次
在以下情况, 当对象的生命周期结束时,析构函数会被自动调用:
① 如果定义了一个全局对象,则在程序流程离开其作用域时(如main函数结束或调用exit函数)时,调用该全局对象的析构函数。
② 如果一个对象被定义在一个函数体内,则当这个函数结束时,该对象的析构函数被自动调用。
③ 若一个对象是使用new运算符动态创建的,在使用delete运算符释放它时,delete会自动调用析构函数。
默认情况下,C++编译器至少给一个类添加三个函数
默认构造函数(无参,函数体为空)
默认析构函数(无参,函数体为空)
默认拷贝构造函数,对属性进行值拷贝
1.构造函数的调用规则(上3) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Person () {public : Person () { cout<<"Person的默认构造函数调用" <<endl; } Person (int age) { cout<<"Person的有参构造函数调用" <<endl; m_age=age; } Person (const Person &p) { cout<<"Person的拷贝构造函数调用" <<endl; m_age=p.m_age; } };
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 41 42 43 44 45 46 47 48 49 50 Person (){ public : Person () { cout<<"Person的默认构造函数调用" <<endl; } Person (int age,int height) { m_age=age; m_Height=new int (height); cout<<"Person的有参构造函数调用" <<endl; } Person (const Person &p) { cout<<"Person拷贝构造函数调用" <<endl; m_age=age; m_Height=new int (*p.m_Height); } ~Person () { if (m_Height!=NULL ) { delete m_Height; m_Height=NULL ; } } int m_age; int *m_Height; }; void text01 () { Person p1 (18 ,160 ) ; cout<<"p1的年龄为:" <<p1.m_age<<"身高为:" <<*p1.m_Height<<endl; Person p2 (p1) ; cout<<"p2的年龄为:" <<p2.m_age<<"身高为:" <<*p2.m_Height<<endl; } void main () { text01 (); }
注意:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
初始化列表赋初值 语法:构造函数():属性1(值1):属性2(值2):属性3(值3)…{} 类对象作为类成员 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 class Phone { public : Phone (string pName) { cout<<"Phone的构造函数调用" <<endl; m_PName=pName; } ~Person () { cout<<"Phone的析构函数调用" <<endl; } string m_PName; }; class Person { public : Person (string name,string pName):m_Name (name),m_Phone (pName) { cout<<"Person的构造函数调用" <<endl; } ~Person () { cout<<"Person的析构函数调用" <<endl; } string m_Name; Phone m_Phone; }; void text01{ Person p ("张三" ,"苹果MAX" ) ; cout<<p.m_Name<<"拿着:" <<p.m_Phone.m_PName<<endl; } main (){ text01; }
静态成员(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 35 36 37 38 39 40 41 42 class Person { public : static int m_A; private : static int m_B; }; int Person::m_A=100 ;int Person::m_B=200 ;void text01 () { Person p; cout<<p.m_A<<endl; Person p2; p2.m_A=200 ; cout<<p.m_A<<endl; } void text02 () { Person p; cout<<p.m_A<<endl; cout<<Person::m_A<<endl; cout<<Person::m_A<<endl; } main (){ text01 (); text02 (); }
静态成员函数
所有对象共享一个函数
静态成员函数只能访问静态成员变量
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 class Person { public : static void func () { m_A=100 ; cout<<"static void func调用" <<endl; } static int m_A; int m_B; private : static void func2 () { cout<<"static func2()的访问调用" <<endl; } }; int Person::m_A=0 ;void text01 () { Person p; p.func (); Person::func (); } int main () { return 0 ; }
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 class Person { int m_A; static int m_B; void func () {} static void func2 () {} }; int Person::m_B=0 ;void text01 () { Person p; cout<<"size of p=" <<sizeof (p)<<endl; } void text02 () { Person p; cout<<"size of p=" <<sizeof (p)<<endl; } int main () { text01 (); text02 (); }
this指针概念(不同对象的数据成员存放在不同的内存地址,所有对象的成员函数对应的是同一个函数代码段) this指针的本质是指针常量 指针的指向是不可以修改的 但是指向的值是可以修改的
每个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码,那这一块代码是如何区分是哪个对象调用自己呢?当一个对象要调用成员函数时,this指针中就装着该对象的地址, 成员函数就根据这个指针,找到相应的数据,然而进行相应的操作。
C++通过提供特殊的对象指针,this指针,解决上述问题,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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Person { public : Person (int age) { age=age; } Person& PersonAddAge (Person &p) { this ->age+=p.age; return *this ; } int age; }; void text01 () { Person p1 (18 ) ; cout<<"p1的年龄为 " <<p1.age<<endl; } void text02 () { Person p1 (10 ) ; Person p2 (10 ) ; p2.PersonAddAge (p1).PersonAddAge (p1).PersonAddAge (p1); cout<<"p2的年龄为 " <<p2.age<<endl; } main (){ text01; }
空指针访问成员函数 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 class Person () { void showClassName () { cout<<"this is Person class" <<endl; } void showPersonAge () { cout<<"age=" <<this ->m_Age<<endl; if (this ==NULL ) { return ; } } int m_Age; } void text01 () { Person *p=NULL ; p->showClassName (); } main (){ text01 (); }
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 31 32 33 34 35 36 37 class Person () { void showPerson () const { this ->m_B=100 ; } int m_A; mutable int m_B; }; void test01 () { Person p; p.showPerson (); } void test02 () { const Person p; p.m_B=200 ; p.showPerson (); } main (){ text01 (); }
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 Date { private : const int year; const int month; const int day; public : Date (int y,int m,int d); void showDate () ; }; Date::Date (int y,int m,int d) :year (y),month (m),day (d) { } inline void Date::showDate () { cout<<year<<"." <<month<<"." <<day<<endl;} int main () { Date date1 (2009 ,10 ,15 ) ; date1.showDate (); 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 class Building { friend void goodGay (Building *building) ; public : Building () { m_SittingRoom="客厅" ; m_BedRoom="卧室" ; } public : string m_SittingRoom; private : string m_BedRoom; }; void goodGay (Building *building) { cout<<"好基友全局函数 正在访问:" <<building->m_SettingRoom<<endl; cout<<"好基友全局函数 正在访问:" <<building->m_BedRoom<<endl; } void text01 () { Building building; goodGay (&building); } main (){ text01 (); }
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 class Building ;class GoodGay { public : GoodGay (); void visit () ; Building *building; }; class Building { friend class GoodGay ; public : Building (); public : string m_SittingRoom; private : string m_BedRoom; } Building::Building () { m_SettingRoom="客厅" ; m_BedRoom="卧室" ; } GoodGay::GoodGay () { building=new Building; } void GoodGay::visit () { cout<<"好基友类正在访问:" <<building->SettingRoom<<endl; } void text01 () { GoodGay gg; gg.visit (); } void main () { text01 (); }
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 58 59 60 61 class Building ;class GoodGay () {public : GoodGay (); void visit () ; void visit2 () ; Building *building; }; class Building { friend void GoodGay::visit () ; public : Building (); public : string m_SittingRoom; private : string m_BedRoom; } Building::Building () { m_SittingRoom="客厅" ; m_BedRoom="卧室" ; } GoodGay::GoodGay () { building=new Building: } void GoodGay::visit (){ cout<<"visit正在访问:" <<building->m_SettingRoom<<endl; cout<<"visit正在访问:" <<building->m_BedRoom<<endl; } void GoodGay::visit2 () { cout<<"visit2正在访问:" <<building->m_SettingRoom<<endl; } void text01 () { GoodGay gg; gg.visit (); gg.visit2 (); } void main () { }
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 Date ; class Time { public : Time (int h,int m,int s) { hour =h; minute =m; sec =s;} void showDate_Time (Date&) ; private : int hour; int minute; int sec; }; class Date { public : Date (int y,int m,int d) { year=y; month=m; day=d; } friend void Time::showDate_Time (Date&) ; private : int year; int month; int day;}; void Time::showDate_Time (Date& d) { cout<<d.year<<"." <<d.month<<"." <<d.day<<endl; cout<<hour<<":" <<minute<<":" <<sec<<endl; } int main () { Date date1 (2010 ,11 ,14 ) ; Time time1 (6 ,12 ,18 ) ; time1.showDate_Time (date1); return 0 ; } 程序的运行结果如下: 2010.11 .14 6 :12 :18
(1) 友元关系是单向的,不具有交换性。 若类X是类Y的友元,类Y是否是X的友元,要看在类中是否有相应的声明。
(2) 友元关系也不具有传递性。 若类X是类Y的友元,类Y是类Z的友元,不一定类X是类Z的友元。
运算符重载 对已有的运算符进行定义,赋予其另一种功能,以适应不同的数据类型 加号运算符重载(实现两个自定义数据类型的运算)
对于内置的数据类型的表达式的运算符是不可能改变的
不要滥用运算符重载
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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 class Person { public : Person operator +(Person &p) { Person temp; temp.m_A=this ->m_A+p.m_A; temp.m_B=this ->m_B+p.m_B; return temp; } int m_A; int m_B; }; void test01 () { Person p1: p1.m_A=10 ; P1.m_B=10 ; Person p2: p2.m_A=10 ; P2.m_B=10 ; Person p3=p1+p2; cout<<"p3.m_A" <<p3.m_A<<endl; cout<<"p3.m_B" <<p3.m_B<<endl; } main (){ text01 (); } Person operator +(Person &p) { Person temp; temp.m_A=this ->m_A+p.m_A; temp.m_B=this ->m_B+p.m_B; return temp; } Person p3=p1.operator +(p2); Person p3=p1+p2; 2.
运算符重载 也可以发生函数重载(用途:重复使用函数名) 1 2 3 4 5 6 7 8 9 10 11 12 Person p3=p1+10 ; Person Person+(Person &p1,int ) { person temp; temp.m_A=p1.m_A+p2.m_A; temp.m_B=p1.m_B+p2.m_B; return temp; } Person p4=p1+100 ; cout<<"p4.m_A" <<p4.m_A<<endl; cout<<"p4.m_B" <<p4.m_B<<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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class Person () { friend ostream & operator <<(cout,p); public : public : Person (int a,int b) { m_A=a; m_B=b; } int m_A; int m_B; }; ostream & operator <<(ostream &cout,Person &p) { cout<<"m_A=" <<p.m_A<<"m_B=" <<p.m_B; return cout; } void text01 () { Person p (10 ,10 ) ; cout<<p<<"hello" <<endl; } int main () { text01 (); 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 50 51 52 53 54 55 56 57 58 59 60 class MyInteger { public : friend ostream& operator <<(ostream& cout,Myinteger myint) MyInteger () { m_Num=0 } MyInteger& operator ++() { m_Num++; return *this ; } MyInteger operator ++(int ) { MyInteger temp=*this ; m_Num++; return temp; } private : int m_Num; }; ostream& operator <<(ostream& cout,Myinteger myint) { cout<<myint.m_Num; return cout; } void text01 () { MyInteger myint; cout<<++(++myint)<<endl; cout<<myint<<endl; } void text02 () { MyInteger myint; cout<<myint++<<endl; cout<<myint<<endl; } int main () { text01 (); text02 (); }
赋值运算符重载 编译器会至少给一个类添加4个函数
默认构造函数(无参,函数体为空)
默认析构函数(无参,函数体为空)
默认拷贝构造函数,对属性进行拷贝
赋值运算符operator=对属性进行值拷贝
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 58 class Person { Person (int age) { m_Age=new int (age); } ~Person () { if (m_Age!=NULL ) { delete m_Age; m_Age=NULL ; } } Person& operator =(Person &p) { if (m_Age!=NULL ) { delete m_Age; m_Age=NULL ; } m_Age=new int (*p.m_Age) return *this ; } int *m_Age; }; void text01 () { Person p1 (18 ) ; Person p2 (20 ) ; Person p3 (30 ) ; p3=p2=p1; cout<<"p1的年龄为:" <<*p1.m_Age<<endl; cout<<"p2的年龄为:" <<*p2.m_Age<<endl; cout<<"p3的年龄为:" <<*p3.m_Age<<endl; } int main () { text01 (); }
关系运算符重载 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 class Person { public : Person (string name,int age) { m_Name=name; m_Age=age; } bool operator ==(Person &p) { if (this ->m_Name==p.m_Name && this ->m_Age==p.m_Age) { return true ; } return false ; } string m_Name; int m_Age; }; void test01 () { Person p1 ("Tom,18" ) ; Person p2 ("Tom,18" ) ; if (p1==p2) { cout<<"p1和p2是相等的" <<endl; } else { cout<<"p1和p2是不相等的" <<endl; } } int main () { test01 (); }
调用运算符重载
函数调用运算符()也可以重载
由于重载后使用的方式非常像函数的调用,因此称为仿函数
仿函数没有固定写法,非常灵活
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 class Myprint { public : void operator () (string test) { cout<<test<<endl; } }; void MyPrint02 (string test) { cout<<test<<endl; } void test01 () { MyPrint myPrint; myPrint ("hello world" ); MyPrint02 ("hello world" ); } class MyAdd { public : int operator () (int num1,int num2) { return num1+num2; } }; void test02 () { MyAdd myadd; int ret=myadd (100 ,100 ); cout<<"ret=" <<ret<<endl; cout<<MyAdd ()(100 ,100 )<<endl; } int main () { test01 (); test02 (); return 0 ; }
继承 语法: class 子类(派生类) :继承方式 父类(基类)
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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 class Base1 { public : int m_A; protected : int m_B; private : int m_C; }; class Son1 :public Base1{ public : void func () { m_A=10 ; m_B=20 ; } }; void text01 () { Son1 s1; s1.m_A=100 ; } class Base2 { public : int m_A; protected : int m_B; private : int m_C; }; class Son2 :protected Base2{ public : void func () { m_A=100 ; m_B=100 ; } } void text02 () { Son2 s1; } class Base3 { public : int m_A; protected : int m_B; private : int m_C; }; class Son3 :private Base3{ public : void func () { m_A=100 ; m_B=100 ; m_c=100 ; } }; void text03 () { Son s1; s1.m_A=1000 ; S2.m_B=1000 ; }
继承中的对象模型 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 class Base { public : int m_A; protected : int m_B; private : int m_C; }; class Son :public Base{ public : int m_D; }; void text01 () { cout<<"size of Son:" <<sizeof (Son)<<endl; } int main () { text01 (); }
当基类含有带参数的构造函数时,派生类必须定义构造函数,并缀上基类名(参数表),以提供把参数传递给基类构造函数的途径。 其中基类构造函数参数表的参数,通常来源于派生类构造函数的参数表,也可以用常数值。
(1)可以将派生类构造函数定义在类的外部,而在类体内只写该函数的声明。
1 2 3 4 5 6 7 8 9 D (int a,int b); D (int a,int b):B (b) { cout<<"调用派生类的构造函数\n" <<endl; j=a; }
当派生类中含有对象成员时,其构造函数的一般形式为: 派生类名(参数总表基类名(参数表0),对象名成员1(参数表1),…,对象成员名n (参数表n)
{
//** 派生类新增成员的初始化语句 ..**
}
继承中构造和析构顺序 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 class Base { public : Base () { cout<<"Base的构造函数" <<endl; } ~Base () { cout<<"Base的析构函数" <<endl; } }; class Son :public Base{ public : Son () { cout<<"Son的构造函数" <<endl; } ~Son () { cout<<"Son的析构函数" <<endl; } }; void text01 () { Son s; } main (){ text01 (); }
继承中同名成员的处理方式 当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据
访问子类同名成员 直接访问就行
访问父类同名成员 需要加作用域
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 class Base { public : Base () { m_A=100 ; } int m_A; void func (int a) { cout<<"Base-func()调用" <<endl; } }; class Son :public Base{ public : Son () { m_A=200 ; } void func () { cout<<"Son-func()调用" <<endl; } int m_A; }; void text01 () { Son s; cout<<"Son下 m_A=" <<s.m_A<<endl; cout<<"Base下 m_A=" <<s.Base::m_A<<endl; } void text02 () { Son s; s.func (); s.Base::func (); s.Base::func (100 ); } main (){ text01 (); text02 (); }
继承中同名静态成员 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 58 59 class Base { public : static int m_A; static void func () { cout<<"Base static void func()" <<endl; } static void func (int a) { cout<<"Base static void func(int a)" <<endl; } }; int Base::m_A=100 ;class Son ::public Base{ public : static int m_A; static void func () { cout<<"Son static void func()" <<endl; } }; int Son::m_A=200 ;void text01 () { Son s; cout<<"Son 下 m_A=" <<s.m_A<<endl; cout<<"Base 下 m_A=" <<Son::m_A<<endl; cout<<"通过类名访问:" <<endl; cout<<"Son 下 m_A=" <<Son::m_A<<endl; cout<<"Base 下 m_A=" <<Son::Base::m_A<<endl; } void text01 () { cout<<"通过对象访问" <<endl; Son s; s.func (); s.Base::func (); cout<<"通过类名访问" <<endl; Son::func (); Son::Base::func (); Son::Base::func (); }
多继承语法 语法:class 子类:继承方式 父类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 41 42 43 44 45 class Base1 { public : Base1 () { m_A=100 ; } int m_A; }; class Base2 { public : Base2 () { m_B=200 ; } int m_B; }; class Son :public Base1,public Base2{ public : Son () { m_C=300 ; m_D=400 ; } }; void text01 () { Son s; cout<<"sizeof Son=" <<sizeof (s)<<endl; cout<<"Base1 m_A=" <<Base1::s.m_A<<endl; cout<<"Base2 m_A=" <<Base2::s.m_A<<endl; } int main () { text01 (); }
虚基类(菱形继承)
羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时就会产生二义性
草泥马继承了动物的数据继承了两份,但这个数据继承一次就行
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 class Animal { public : int m_Age; }; class Sheep :virtual public Animal{ }; class Tuo :virtual public Animal{ }; class SheepTuo :public Sheep,public Tuo{ }; void text01 () { SheepTuo st; st.Sheep::m_Age=18 ; st.Tuo::m_Age=28 ; cout<<" st.Sheep::m_Age=" << st.Sheep::m_Age<<endl; cout<<" st.Tuo::m_Age=" << st.Tuo::m_Age<<endl; cout<<" st.m_Age=" << st.m_Age<<endl; } int main () { text01 (); }
派生类对基类成员的访问形式主要有以下两种: 内部访问 由派生类中新增的成员函数 对基类继承来的成员的访问
对象访问 在派生类外部 通过派生类的对象对从基类继承来的成员的访问
对于基类中的私有成员: 无论哪种派生方式,基类中的私有成员,
不允许派生类的对象直接访问(对象访问),
不允许派生类中成员函数直接访问(内部访问) ,
但是可以通过基类提供的公有成员函数访问。
多态
静态多态:函数重载和运算符重载属于静态多态,重复使用函数名
动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态的区别:
静态多态的函数地址早绑定,编译阶段确定函数地址
动态多态的函数地址晚绑定,运行阶段确定函数地址
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 class Animal { public : virtual void speak () { cout<<"动物在说话" <<endl; } }; class Cat :public Animal{ public : void speak () { cout<<"小猫在说话" <<endl; } }; void doSpeak (Animal &animal) { animal.speak (); } void text01 () { Cat cat; doSpeak (cat); } void text02 () { cout<<"sizeof Animal=" <<sizeof (Animal)<<endl; cout<<"sizeof Animal=" <<sizeof (Animal)<<endl; } int main () { text01 (); text02 (); }
在C++中规定 : 基类的对象指针可以指向它的公有派生的对象,但是当其指向公有派生类对象时,它只能访问派生类中从基类继承来的成员,而不能访问公有派生类中定义的成员1 2 3 4 5 6 7 8 9 10 11 12 class A { public :void print1 () {. . .} };class B :public A { public :void print2 () {. . .} }; void main () {A *p1; B op2; p1=&op2; p1->print1 (); p1->print2 (); }
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.h> class base { int a,b; public : base (int x,int y) { a=x; b=y; } void show ( ) { cout<<"调用基类base的show函数\n" ; cout<<"a=" <<a<<“b="<<b<<endl; } }; class dirive:public base { int c; public: dirive(int x,int y,int z):base(x,y){c=z;} void show( ) { cout<< " 调用派生类dirive的show函数\n"; cout<<" c="<<c<<endl; } }; void main( ) { base mb(50,50),*mp; dirive mc(10,20,30); mp=&mb; mp->show( ); mp=&mc; mp->show( ); } /* 程序运行结果不是: 调用基类base的show函数 a=50 b=50 调用派生类dirive的show函数 c=30 程序运行结果如下: 调用基类base的show函数 a=50 b=50 调用基类base的show函数 a=10 b=20 */
如果在派生类中没有对基类的虚函数重新定义,则公有派生类继承其直接基类的虚函数。一个虚函数无论被公有继承多少次,它仍然保持其虚函数的特性。
1 2 3 4 5 6 7 8 class B0 { . . . public : virtual void show () {. . .} }; class B1 :public B0{ ... };
动态多态满足条件 1.有继承关系
2.子类要重写父类的虚函数,这时virtual可以不写 virtual void speak()和void speak()相同 重写:函数名相同,形参列表,函数返回值类型都完全相同
动态多态的使用 父类的指针或引用 指向子类对象
动态多态的原理
cat类中重写speak函数与否的原理(上边没有重写,下边重写了)
纯虚函数和抽象类 在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容 语法: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 class Base { public : virtual void func () =0 ; }; class Son :public Base{ public : virtual void func () { cout<<"func函数调用" <<endl; } }; void text01 () { Son s; Base *base=new Son; base->func (); } int main () { text01 (); }
多态:多态的作用就是为了使函数接口更通用化,通过一个父类的指针或引用,因为创造的对象不同,可以调用多种形态的函数
虚析构和纯虚析构 多态使用时,如果子类中有些属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
如果将基类的析构函数定义为虚函数,由该基类所派生的所有派生类的析构函数也都自动成为虚函数。
在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 36 37 38 #include <iostream> using namespace std;class B { public : ~B () { cout<<"调用基类B的析构函数\n" ;} }; class D :public B{ public : ~D () { cout<<"调用派生类D的析构函数\n" ;} }; int main () { B *p; p= new D; delete p; return 0 ; } ------------------------------------------------- #include <iostream> using namespace std;class B { public : virtual ~B () { cout<<"调用基类B的析构函数\n" ;} }; class D :public B{ public : ~D () { cout<<"调用派生类D的析构函数\n" ;} }; int main () { B *p; p= new D; delete 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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 class Animal { public : Animal () { cout<<"Animal构造函数调用" <<endl; } virtual ~Animal ()=0 ; virtual void spesk () =0 ; }; Animal::~Animal () { cout<<"Animal纯虚析构函数调用" <<endl; } class Cat :public Animal{ public : Cat (string name) { cout<<"Cat的构造函数调用" <<endl; m_Name=new string (name); } void speak () { cout<<*m_Name<<"小猫在说话" <<endl; } ~Cat () { if (m_Name!=NULL ) { cout<<"Cat析构函数调用" <<endl; delete m_Name; m_Name=NULL ; } } string *m_Name; }; void text01 () { Animal* animal=new Cat; animal->speak (); delete animal; } int main () { text01 (); }
由于抽象类中至少包含有一个没有定义功能的纯虚函数,因此抽象类只能用作其他类的基类,不能建立抽象类对象。
不允许从具体类派生出抽象类。所谓具体类,就是不包含纯虚函数的普通类;
抽象类不能用作函数的参数类型、函数的返回类型或显式转换的类型;
可以声明指向抽象类的指针或引用,此指针可以指向它的派生类,进而实现多态性。
在派生类中,如果对基类的纯虚函数没有重新定义,则该函数在派生类中仍是纯虚函数,该派生类仍为抽象类。