内存分区模型
代码区:存放二进制代码,由操作系统管理
全局区:存放全局变量、静态变量、全局常量
栈区:编译器 自动分配释放,存放函数参数值、局部变量等
堆区:由程序员 分配、释放,若不释放,结束时由操作系统回收
意义:赋予不同的生命周期,灵活编程
程序运行前 未执行.exe文件前,分为两个区域
代码区:
存放cpu执行的机器指令
特点:共享(频繁被执行的程序只要在内存中有一份代码即可),只读(防止程序意外修改了指令)
全局区:
程序运行后
栈区
存放函数的局部变量及参数,编译器自动开辟与free
堆区
由程序员手动分配与释放,如果程序员不释放的话就由操作系统释放
在c++中用new来分配内存
new操作符语法 分配:
1 2 3 4 5 int * func () { int * p=new int (10 ); return p; }
释放:
1 2 3 4 5 6 7 int main () { int *p=func (); cout<<*p<<endl; delete p; cout<<*p<<endl; }
开辟一段连续的数组空间:
1 2 3 4 5 6 7 8 9 10 11 int * func2 () { int * p=new int [10 ]; for (int i=0 ;i<10 ;i++) p[i]=i; for (int i=0 ;i<10 ;i++) cout<<p[i]<<endl; reuturn p; }
释放数组:
1 2 3 4 5 6 int main () { int *p=func2 (); delete [] p; reuturn 0 ; }
引用 基本语法使用
作用
语法
数据类型 &别名=原名
比如原来有int a=10
创建引用int &b=a;
操作的是同一块内存
注意事项
必须初始化
int &b错误的
初始化后不可改变引用的对象
引用作为函数参数
作用:
可以用引用让形式参数修饰实参
可以不用指针,简化思考
这里举一个交换两个数的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void swap (int &a,int &b) { int temp=a; a=b; b=temp; }int main () { int a=10 ,b=20 ; cout<<"交换前:a=" <<a; cout<<" b=" <<b<<endl; swap (a,b); cout<<"交换后:a=" <<a; cout<<" b=" <<b<<endl; return 0 ; }
引用做函数返回值
函数调用可以作为等号左边的值
1 2 3 4 5 6 7 8 9 10 11 12 13 int &test1 () { static int a=10 ; return a; }int main () { int &b=test1 (); cout<<"b=" <<b<<endl; test1 ()=1000 ; cout<<"b=" <<b<<endl; return 0 ; }
本质
常量引用
1 2 3 4 5 6 7 8 9 10 11 12 13 void test1 (const int &a) { cout<<a<<endl; }int main () { const int &a=10 ; int aa=10 ; test1 (a); return 0 ; }
函数提高 函数默认参数 C++中,函数的形参列表中可以有默认值
1 2 3 4 void test (int a,int b=10 ,int c=20 ) { ... }
如果我没有传参就用默认,如果我们有传参,就用我们传的参数
注意:
如果某个位置有了默认参数,那么往后都必须要有默认值
1 2 3 4 void test (int a=10 ,int b,int c) { ... }
函数占位参数 C++中可以有占位参数,用来占位,但在函数调用时必须填补该位置
比如
1 2 3 4 5 6 7 8 9 void test (int a,int ) { ... }int main () { test (10 ,10 ); return 0 ; }
1 2 3 4 5 6 7 8 9 void test (int a,int = 10 ) { ... }int main () {test (10 ,10 );return 0 ; }
函数重载 概述
作用:
条件:
同一个作用域(比如都是全局)
函数名相同
函数参数类型不同 或个数不同 或顺序不同
注意:
举个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void func (int a) { cout<<"func(int a)被调用" <<endl; }void func () { cout<<"func()被调用" <<endl; }int main () { func (10 ); func (); return 0 ; }
注意事项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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 ); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void func (int a) { cout<<"func(int a)被调用" <<endl; }void func (int a,int b=10 ) { cout<<"func(int a,int b=10)被调用" <<endl; }int main () { func (10 ,20 ); return 0 ; }
类和对象 C++面向对象三大特性:封装、继承、多态
封装 意义
将属性和行为封装在一起,表现事物
将属性和行为加以权限控制
意义1:
比如设计一个圆类,求圆周长
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Circle { public : int r; double c () { return 2 *3.14 *r; } };int main () { Circle c1; c1. r=10 ; cout<<"圆周长为" <<c1. c ()<<endl; return 0 ; }
类中的属性行为统一称为成员
属性:成员属性,成员变量
行为:成员函数,成员方法
意义2:
访问权限分为3种
public 公共权限
protected 保护权限
private 私有权限
struct class区别 唯一区别:默认访问权限不同
struct:公共
class:私有
将成员属性设置为私有 优点1:将所有成员属性设置为私有,可以自己控制读写权限
优点2:对于写权限,我们可以检查数据有效性(就是在写的时候加if来判断即可)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class person {public : void setname (string n) { name=n; } void getage () { cout<<"年龄为:" <<age<<endl; }这样就是只读了 string setidol (string i) { idol=i; }private : string name; int age=18 ; srring idol; }
对象的初始化和清理 构造函数和析构函数 初始化 和清理 是非常重要的安全问题
没有初始化,对象使用后果是位置的
使用完对象或变量没有及时清理,也会造成安全问题
构造和析构就是来解决初始化和清理的,由编译器自动调用,如果我们不提供构造和析构,编译器会提供,但编译器提供的是空的
构造:主要作用在创建对象时为成员属性赋值,由编译器自动调用,无须手动
析构:主要作用在于对象销毁前自动调用,执行一些清理
构造函数语法:类名 (){}
没有返回值也不写void
函数名称和类名相同
可以有参数,因此可以发生重载
程序在调用对象的时候会自动调用构造,无须手动调用,而且只会调用一次
析构函数语法:~类名 (){}
没有返回值但也不写void
函数名称和类名相同,在名称前面加~
不可以 有参数,因此不可以 发生重载
程序在对象销毁前会自动调用析构,无须手动调用且只会调用一次
举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class person {public : person () { cout<<"Person构造函数被调用" <<endl; } ~person () { cout<<"person析构函数被调用" <<endl; } }void test1 () { person p; }int main () { test1 (); 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 class person {public : person () { cout<<"Person无参构造函数被调用" <<endl; } person (int a) { cout<<"Person有参构造函数被调用" <<endl; } person (const person &p) { cout<<"Person拷贝" <<endl; age=p.age } ~person () { cout<<"person析构函数被调用" <<endl; } int a; };
调用方式:
括号法
注意事项
调用默认构造函数时不要加(),person p1()会被当做是一个函数的声明
显示法
隐式转换法
1 2 3 4 5 6 7 8 9 10 11 12 13 void test01 () { person p; person p1 (10 ) ; person p2 (p1) ; person p3=person (10 ); person p4=person (p1); person (10 ); person p5=10 ; }
拷贝构造函数调用时机 调用的三种情况
调用一个已经创建完毕的对象来初始化新对象
值传递方式给函数参数传值
以值传递方式返回局部对象
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 : person () { cout<<"默认构造函数被调用" <<endl; } person (int a) { age=a; cout<<"有参构造被调用" <<endl; } ~person () { cout<<"析构函数被调用" <<endl; } person (const person &p) { age=p.age; cout<<"拷贝构造被调用" <<endl } };void work1 (person p) { }person work2 () { person p1; return p1; }int main () { person p1 (20 ) ; person p2 (p1) ; work1 (p1); person p=work2 (); return 0 ; }
构造函数调用规则 默认情况下,c++编译器至少给一个类添加三个函数
默认构造函数(无参,函数体为空)
默认析构函数(无参,函数体为空)
默认拷贝函数,对属性进行值拷贝
构造函数调用规则:
用户自定义有参构造 函数,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 class person {public : person () { cout<<"默认构造函数被调用" <<endl; } person (int a,int h) { age=a; high=new int (h); cout<<"有参构造被调用" <<endl; } ~person () { if (high!=NULL ) delete high; cout<<"析构函数被调用" <<endl; }private : int age; int * high=NULL ; };
浅拷贝会导致一块内存重复被释放
int main()
{
person p1(18,120);
person p2(p1);//系统提供的拷贝函数,是浅拷贝
return 0;
}//此时p1p2中的age存储的是同一块堆中的地址,当p2结束后会执行一次析构函数,此时high已经被释放掉,而p1结束后还会再来一次析构函数,而此时已经释放掉的内存就会再一次被释放,导致程序出错
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 此时就需要深拷贝,需要重新申请一块内存 ```c++class person {public : person() { cout<<"默认构造函数被调用" <<endl; } person(int a,int h) { age=a; high=new int (h); cout<<"有参构造被调用" <<endl; } ~person() { if (high!=NULL ) delete high; cout<<"析构函数被调用" <<endl; } person(const person &p) { cout<<"深拷贝函数被调用" <<endl; age=p.age; high=new int (*p.high); }private : int age; int * high=NULL ; };
初始化列表 作用 :
C++提供初始化列表语法,用于初始化属性
语法:构造函数():属性1(值1),属性2(值2)....{}
参数非常数时:person(int a,int b,int c):m_A(a),m_B(b),m_C(c){}
类对象作为类成员 C++中类中的成员可以是另一个类的对象,我们称该成员为:对象成员
比如
1 2 3 4 5 6 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 class phone {public : phone (string p) { pname=p; } string pname; }class person {public : person (string n,string pn) { name=n; m_phone.pname=pn; } srring name; phone m_phone; }void test01 () { person p ("pp" ,"apple" ) ; }
结论:当其他类对象作为本类对象,其他类的构造函数先被调用,然后自身的构造函数再来,析构则是反过来
静态成员 在成员变量和成员函数前加static,称为静态成员
静态成员分为
静态成员变量
所有对象共用一份数据
编译阶段分配内存
类内声明,类外初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class person {public : static int a; };int person::a=100 ;void test01 () { person p; cout<<p.a<<endl; person p1; p1. a=200 ; cout<<p.a<<endl; }
静态成员变量不属于某个对象上,所有对象共享同一份数据
因此有两种访问方式
1 2 3 4 5 6 7 8 void test02 () { person p; cout<<p.a<<endl; cout<<person::a<<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 class person {public : static int a;private : static int b; };int person::a=100 ;int person::b=101 ;void test01 () { person p; cout<<p.a<<endl; person p1; p1. a=200 ; cout<<p.a<<endl; }void test02 () { cout<<person::b<<endl; }
静态成员函数
所有对象共享同一个函数
静态成员函数只能访问静态成员变量(因为无法区分这个变量是谁的)
1 2 3 4 5 6 7 8 class person {public : static void func () { cout<<"静态成员函数被调用" <<endl; } };
访问方式:
1 2 3 4 5 6 7 8 void test01 () { person p; p.func (); person::func (); }
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 30 class person { };class person1 { int a; };class person2 { int a; static int b; };int person2::b=100 ;void test01 () { person p; cout<<"size of p = " <<sizeof (p)<<endl; }void test02 () { person1 p; cout<<"size of p = " <<sizeof (p)<<endl; }void test03 () { person2 p; cout<<"size of p = " <<sizeof (p)<<endl; }
this指针 C++通过提供特殊的对象指针,this指针,来区分是哪个对象调用的。this指针指向被调用的成员函数所属的对象
this指针隐含在每一个非静态成员函数内的一种指针
this指针不需要定义,可以直接使用
用途
当形参和成员函数重名时,用this指针来区分
在非静态成员函数中返回对象本身,可以使用return *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 class person {public : person (int age) { this ->age=age; } person& addage (person& p) { this ->age+=p.age; return *this ; } int age; }void test01 () { person p1 (18 ) ; cout<<"p1年龄:" <<endl; }void test02 () { person p1 (10 ) ; person p2 (10 ) ; p2. addage (p1).addage (p1),addage (p1); cout<<"p2年龄为:" <<p2. age<<endl; }
空指针访问成员函数 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 class person {public : void showtype () { cout<<"This is person class" <<endl; } void showage () { if (this ==NULL ) return ; cout<<age<<endl; }private : int age; };void test01 () { preson *p=NULL ; p->showtype (); p->showage (); }
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 class person {private : int a;public : void showperson () const { a = 100 ; } void show () { } };void test01 () { person p; p.showperson (); }void test02 () { const person p; p.a=100 ; p.showperson (); p.show (); }
调用规则
const 成员函数 可以被 const 对象调用,也可以被非 const 对象调用(但优先调用非 const 版本)。 非
** const 成员函数** 只能被非 const 对象调用。
const 可以用来区分重载函数 ,只要它们的参数列表相同,但 const 属性不同。
友元 在程序里,有些私有属性,也想让类外的一些特殊的函数或类进行访问,需要用到友元
关键字: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 class room { friend void gfriend (room* r) ;public : room () { sroom = "a" ; bedroom = "b" ; }public : string sroom;private : string bedroom; };void gfriend (room* r) { cout << "全局函数访问:" << r->sroom << endl; cout << "全局函数访问:" << r->bedroom << endl; }void test01 () { room rr; gfriend (&rr); }
类做友元 让一个类可以访问另一个类的私有成员
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 class room { friend class gfriend ;public : room ();public : string sroom;private : string bedroom; }; room::room () { sroom = "a" ; bedroom = "b" ; }class gfriend {public : room* r; gfriend () { r = new room; } void visit () { cout << "gfriend正在访问:" << r->sroom << endl; cout << "gfriend正在访问:" << r->bedroom << endl; } };void test01 () { gfriend gg; gg.visit (); }
成员函数做友元 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 room ; class gfriend {public : room* r; gfriend (); void visit () ; };class room { friend void gfriend::visit () ; public : room (); string sroom;private : string bedroom; }; room::room () { sroom = "a" ; bedroom = "b" ; } gfriend::gfriend () { r = new room; }void gfriend::visit () { cout << "gfriend正在访问:" << r->sroom << endl; cout << "gfriend正在访问:" << r->bedroom << endl; }void test01 () { gfriend gg; gg.visit (); }
运算符重载 对运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
加号运算符重载 作用:实现自定义数据类型的运算
成员函数重载加号 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 class person {public : int a; int b; person operator +(person& p) { person temp; temp.a = a + p.a; temp.b = b + p.b; return temp; } };void test01 () { person p1; p1. a = 10 ; p1. b = 10 ; person p2; p2. a = 10 ; p2. b = 10 ; person p3 = p1 + p2; cout << "p3.a=" << p3. a << endl; cout << "p3.b=" << p3. 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 class person {public : int a; int b; }; person operator +(person& p1, person& p2) { person temp; temp.a = p1. a + p2. a; temp.b = p2. b + p2. b; return temp; }void test01 () { person p1; p1. a = 10 ; p1. b = 10 ; person p2; p2. a = 10 ; p2. b = 10 ; person p3 = p1 + p2; cout << "p3.a=" << p3. a << endl; cout << "p3.b=" << p3. 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 class person {public : int a; int b; }; person operator +(person& p1, person& p2) { person temp; temp.a = p1. a + p2. a; temp.b = p2. b + p2. b; return temp; } person operator +(person& p1, int n) { person temp; temp.a = p1. a + n; temp.b = p1. b + n; return temp; }void test01 () { person p1; p1. a = 10 ; p1. b = 10 ; person p2; p2. a = 10 ; p2. b = 10 ; person p3 = p1 + p2; person p4 = p1 + 20 ; cout << "p3.a=" << p3. a << endl; cout << "p3.b=" << p3. b << endl; cout << endl; cout << "p4.a=" << p4. a << endl; cout << "p4.b=" << p4. b << endl; }
总结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 class person { friend ostream& operator <<(ostream& cout, person& p); friend void test01 () ; private : int a; int b; }; ostream& operator <<(ostream& cout, person& p) { cout << "a=" << p.a << endl; cout << "b=" << p.b << endl; return cout; }void test01 () { person p; p.a = 10 ; p.b = 10 ; cout << p << 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 class person { friend ostream& operator <<(ostream& cout,const person& p);public : person () { a = 0 ; } person& operator ++() { a++; return *this ; } person operator ++(int ) { person temp = *this ; a++; return temp; }private : int a; }; ostream& operator <<(ostream& cout,const person& p) { cout << p.a; return cout; }void test01 () { person p; cout << p << endl; cout << ++p << endl; cout << p << endl; cout << p++ << endl; cout << p; }
总结:前置递增返回的是引用,后置递增返回的是值
赋值运算符重载 C++至少给一个类添加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 class person {public : person (int ag) { age = new int (ag); } ~person () { if (age != NULL ) { delete age; age = NULL ; } } person& operator =(person& p) { { if (age != NULL ) { delete age; age = NULL ; } } age = new int (*p.age); return *this ; } int * age; };void test01 () { person p1 (18 ) ; person p2 (20 ) ; person p3 (22 ) ; p3 = p2 = p1; cout << "p1=" << *p1. age << endl; cout << "p2=" << *p2. age << endl; cout << "p3=" << *p3. 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 class person {public : person (int a) { age=a; } int age; bool operator ==(person& p) { if (age==p.age) return true ; else return false ; } };void test01 () { person p1 (18 ) ; person p2 (18 ) ; if (p1==p2) cout<<"p1与p2相等" <<endl; else cout<<"p1与p2不相等" <<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 #include <iostream> using namespace std;class mprint {public : void operator () (string test) { cout << test << endl; } };class madd {public : int operator () (int n1, int n2) { return n1 + n2; } };void test01 () { mprint mp; mp ("Hello World!" ); }void test02 () { madd md; cout << md (100 , 10 ) << endl; cout << madd ()(100 , 20 ) << 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 class basepage {public : void header () { cout << "登陆 注册" << endl; } };class Java : public basepage {public : void content () { cout << "Java" << endl; cout << "-----------------" << endl; } };class python : public basepage {public : void content () { cout << "python" << endl; cout << "-----------------" << endl; } };class cpp : public basepage {public : void content () { cout << "C++" << endl; cout << "-----------------" << endl; } };void test01 () { Java ja; ja.header (); ja.content (); python py; py.header (); py.content (); cpp cjj; cjj.header (); cjj.content (); }
总结:继承减少重复的代码
继承方式
父类私有成员子类访问不到
公共继承
保护继承
将公共权限变为保护,其它不变,类内可访问类外不可访问
私有继承
全部变成private,类外全部不能访问
此时如果又有一个类(子子类)来继承这个子类,那么也无法访问这个子类的属性,因为子类继承父类时变成私有的了,子子类无法访问
继承中的对象模型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class base {public : int a;protected : int b;private : int c };class son :puiblic base {public : int d; };test01 () { cout<<"size: " <<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 24 25 26 class base {public : int a; base () { a = 100 ; } };class son :public base {public : int a; son () { a = 200 ; } };void test01 () { son s; cout << "son下的a=" << s.a << endl; cout << "base下的a=" << s.base::a << endl; }
函数同理
多态 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 class animal {public : void speak () { cout<<"animal说话" <<endl; } };class cat :public animal {public : void speak () { cout<<"cat说话" <<endl; } };void dospeak (animal& ani) { ani.speak (); }void test01 () { cat c; dospeak (c); }
结果:
地址早绑定,编译阶段就确定了函数的地址,无论穿什么参数,都会执行ani.speak(),
如果要执行cat说话,地址就不能早绑定 ,需要在运行阶段绑定 ,也就是晚绑定
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 class animal {public : virtual void speak () { cout<<"animal说话" <<endl; } };class cat :public animal {public : void speak () { cout<<"cat说话" <<endl; } };void dospeak (animal& ani) { ani.speak (); }void test01 () { cat c; dospeak (c); }
结果:
动态多态的条件:
动态多态的使用:
多态的本质
当父类使用virtual关键字创建虚函数的时候,其内部会有一个vfptr(virtual function prt)指针父类的虚函数表,这个时候父类的虚函数表中会有一个地址:&animal::speak
当子类继承父类的时候会继承父类的vfptr指针,指向子类的虚函数表,但是当子类重写父类的虚函数的时候,会将自身的虚函数表中的地址给替换掉,替换成:&cat::speak,这样就可以调用指向cat::speak的指针
案例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 25 26 27 class cac {public : int n1; int n2; int getresult (string oper) { if (oper == "+" ) return n1 + n2; else if (oper == "-" ) return n1 - n2; else if (oper == "*" ) return n1 * n2; } };void test01 () { cac c; c.n1 = 10 ; c.n2 = 10 ; cout << c.getresult ("+" ) << endl; cout << c.getresult ("-" ) << endl; cout << c.getresult ("*" ) << 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 class basecac {public : virtual int getresult () { return 0 ; } int n1; int n2; };class addcac :public basecac {public : int getresult () { return n1+n2; } };class jfcac :public basecac {public : int getresult () { return n1-n2; } };void test01 () { basecac *a=new addcac; basecac *b=new jfcac; a->n1=10 ; a->n2=10 ; b->n1=20 ; b->n2=10 ; cout<<a->getresult ()<<endl; cout<<b->getresult ()<<endl; delete a; delete b; }
可见优点:
组织结构清晰
可读性强
便于前期和后期的扩展的修改
总结:C++开发中提倡利用多态设计程序架构,其优点很多
纯虚函数和抽象类 在多态中,通常父类中的虚函数的实现是毫无意义的,主要都是在调用子类重写的内容
因此可以将虚函数改成纯虚函数
语法:virtual 返回值类型 函数名 (参数列表)=0
当类中有一个纯虚函数,这个类也就称为抽象类
抽象类特点:
无法实例化对象
子类必须要重写抽象类的纯虚函数,否则这个子类也属于抽象类
1 2 3 4 5 6 7 8 9 10 class base {public : virtual void func () =0 ; };class son :public base {public : void func () {} };
虚析构和纯虚析构 多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决:将父类中析构函数改为虚析构或纯虚析构
虚析构和纯虚析构共性
可以解决父类指针释放子类对象
都需要有具体的函数实现
区别:
如果是纯虚析构函数,该类属于抽象类,无法实例化对象
虚析构:
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 animal {public : animal () { cout<<"动物活了" <<endl; } virtual void speak () { } virtual ~animal () { cout<<"动物死了" <<endl; } };class cat :public animal {public : string *name=NULL ; cat (string n) { cout<<"猫活了" <<endl; name=new string (n); } void speak () { cout<<"1" <<endl; } ~cat () { if (name!=NULL ) { cout<<"猫死了" <<endl; delete name; name=NULL ; } } };void test01 () { animal* a=new cat ("aa" ); a->speak; delete 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 39 40 41 42 43 44 45 46 47 48 49 50 class animal {public : animal () { cout<<"动物活了" <<endl; } virtual void speak () { } virtual ~animal ()=0 ; }; animal::~animal () { cout<<"纯虚析构" <<endl; }class cat :public animal {public : string *name=NULL ; cat (string n) { cout<<"猫活了" <<endl; name=new string (n); } void speak () { cout<<"1" <<endl; } ~cat () { if (name!=NULL ) { cout<<"猫死了" <<endl; delete name; name=NULL ; } } };void test01 () { animal* a=new cat ("aa" ); a->speak; delete a; }
PTA错题回顾 判断 0x001 对于已正确定义的二维数组a, *(a[i]+j)与a[i][j]的含义相同。
T F
0x002 不允许把一个数值或字符赋予指针变量。
T F
0x003 若重载为友元函数,函数定义格式如下:
<类型>operator<运算符>(<参数列表>)
{
<函数体>
}
T F
0x004 对每个可重载的运算符来讲,它既可以重载为友元函数,又可以重载为成员函数,还可以重载为非成员函数。
T F
答案为F,有些运算符必须要用全局函数来重载,比如说左移(<<)
0x005 重定义虚函数的派生类必须是公有继承的。
T F
0x006 如果一个类的函数全部都是纯虚函数,则这个类不能有自己类的实现(包括引用和指针),只能通过派生类继承实现。
T F
0x007 虚函数既可以在函数说明时定义,也可以在函数实现时定义。
T F
0x008 含有纯虚函数的类是不可以用来创建对象的,因为它是虚基类。
T F
选择 0x001 执行以下的程序片段,将输出几个数字?
1 2 3 for (i=0 ;i<3 ;i++); cout <<i;
A.0B.3C.2D.1
0x002 以下数组定义中错误的是( )。
A.int x[2][3]={{1,2},{3,4},{5,6)};
B.int x[][3]={{1,2,3},(4,5,6)};
C.int x[][3]={0};
D.int x[2][3]={1,2,3,4,5,6};
答案为A,定义的数组是两行三列的,而给出的数据是三行两列的
0x003 设变量定义为 int a[2]={1,3}, *p=&a[0]+1;,则*p的值是( )。
A.&a[0]+1B.2C.4D.3
0x004 对类的构造函数和析构函数描述正确的是( )。
A.构造函数不能重载,析构函数也不能重裁
B.构造函数不能重载,析构函数可以重载
C.构造函数可以重载,析构函数不能重载
D.构造函数可以重载,析构函数也可以重载
0x005 下列程序的运行结果是()。
1 2 3 4 5 6 7 8 int i;for (i=0 ;i<3 ;i++) switch(i) { case 1 :cout<<i ; case 2 :cout<<i ; default: cout<<i; }
A.012
B.011122
C.012020
D.120
0x006 一个函数为void f(int x, char y = 'a'),另一个函数为void f(int),则它们_
A.不能在同一程序块中定义
B.可以在同一个程序块中定义,但不可以重载
C.以上说法均不正确
D.可以在同一个程序块中定义并可重载
0x007 关于类模板,描述错误的是。
A.一个普通基类不能派生类模板
B.类模板可以从普通类派生,也可以从类模板派生
C.根据建立对象时的实际数据类型,编译器把类模板实例化为模板类
D.函数的类模板参数需生成模板类并通过构造函数实例化
0x008 类模板的参数错误的说法是:
A.可以有多个
B.可以有0个
C.参数不能给初值
D.不能有基本数据类型
0x009 若需要为xv类重载乘法运算符,运算结果为xv类型,在将其声明为类的成员函数时,下列原型声明正确的是_________。
A.xv operator*(xv);
B.operator*(xv);
C.xv*(xv);*
D.xv operator*(xv,xv);
0x00A 继承机制的作用是
A.数据封装B.数据抽象C.信息隐藏D.定义新类
0x00B 一个类的私有成员
A.只能被该类的成员函数和友元函数访问
B.以上答案都不对
C.只能被该类的成员函数访问
D.只能被该类的成员函数、友元函数和派生类访问
函数 程序 0x1 R7-1 定义类模板实现2个数的算术运算
分数 20
全屏浏览切换布局
作者 孙杰
单位 青岛大学
本题目要求定义类模板实现2个数的最大值、最小值、加、减、乘、除等算术运算,在main()函数中使用该类模板分别实例化为int型和double型的类,定义相关的对象,读入2个整数和2个浮点数,然后分别输出它们的最大值、最小值、加、减、乘、除的结果。
输入格式:
分别输入2组数字,第一行为2个整数,以空格分隔;第二行为2个浮点数,以空格分隔。
输出格式:
分2行分别输出整数和浮点数的运算结果,每行依次输出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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #include <iostream> using namespace std ; template <typename T>class Arithmetic { private: T num1; T num2; public: Arithmetic(T a, T b) { num1=a; num2=b; } T getMax () { if (num1>num2) return num1; else return num2; } T getMin () { if (num1<num2) return num1; else return num2; } T add () { return num1 + num2; } T subtract () { return num1 - num2; } T multiply () { return num1 * num2; } T divide () { return num1 / num2; } void pprint () { cout << getMax() << " " << getMin() << " " << add() << " " << subtract() << " " << multiply() << " " << divide(); } };int main () { int int1, int2; cin >> int1 >> int2; Arithmetic<int > intArith (int1, int2) ; double double1, double2; cin >> double1 >> double2; Arithmetic<double > doubleArith (double1, double2) ; intArith.pprint(); cout << endl ; doubleArith.pprint(); cout << endl ; return 0 ; }
补充 模板的语法及使用 函数模板 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> using namespace std; template <typename T>T maxx (T a,T b) { if (a>b) return a; else return b; }int main () { int a1=maxx (3 ,5 ); double a2=maxx (2.5 ,3.1 ); cout<<a1<<endl; cout<<a2<<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 #include <iostream> using namespace std; template <typename T>class Box {public : T content; Box (T c) : content (c) {} void speak () { cout<<content<<endl; } };int main () { Box<int > intBox (42 ) ; Box<std::string> strBox ("Hello" ) ; intBox.speak (); strBox.speak (); 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 struct ListNode *readlist () { struct ListNode * head =NULL ; struct ListNode * tail =NULL ; int a; while (1 ) { scanf ("%d" ,&a); if (a==-1 ) break ; struct ListNode * new = (struct LiseNode*)malloc (sizeof (struct ListNode)); new->data=a; new->next=NULL ; if (head==NULL ) head=new; else tail->next=new; tail=new; } return head; }struct ListNode *deletem (struct ListNode *L, int m) { struct ListNode dummy ; dummy.next = L; struct ListNode *prev = &dummy; while (prev->next != NULL ) { if (prev->next->data == m) { struct ListNode *temp = prev->next; prev->next = temp->next; free (temp); } else prev = prev->next; } return dummy.next; }