恐竹丶石叶秋的C++精简笔记
大家好我是恐竹丶石叶秋,你们也可以直接叫我:恐秋
这个笔记是精简模块化笔记,看起来不那么乱糟糟的(或许吧)
还有就是这个笔记不是从头开始学的,而是从指针开始学的,一只到学完多态。
本笔记所使用的变量大多数都是拼音加上我自创的EWVM住音标法,他们分别是:一音E、二音W、三音V、四音M
比如:class renWleiM 就等于人类就是这么简单,再比如:class xueWshengEleiM 就等于学生类
指针
指针的概念
》可以通过指针间接访问内存
》内存从0开始记录
》》用十六进制
》可以用指针保持变量地址
》指针定义
》》数据类型 * 指针变量名;
》》代码
int main()
{
int a=10;
//定义指针
int *p;
//让指针记录变量a的地址
p=&a;
//使用指针
*p =1000;
}
》》》符号&是取地址符
》》》获取某个变量内存地址用的
》》》符号*是解引用
》》》代表找到了内存中的数据,这样就可以改变和访问该值
const修饰指针
》const修饰指针有三种情况
》》const修饰指针:常量指针
》》》const int *p =&a;
》》》指向可以改,指向的值不可以改
》》》》可以*p = &b
》》》》不可以*p=20
》》const修饰常量:指针常量
》》》 int *const p = &a;
》》》指向不可以改,指向的值可以改
》》》》可以p=20
》》》》不可以*p = &b
》》const即修饰指针,又修饰常量
》》》const int * const p =&a;
》》》指向与值都不可以改
》》》》不可以p=20
》》》》不可以*p = &b
指针和数组
》代码
int main()
{
int arr[10]={1,2,3,4,5,6,7,8,9,10};
int *p=arr;//arr就是首地址
p++//移动了四个字节到了2的部分
return 0;
}
》》代码:利用指针遍历代码
int *p2 =arr;
for(int i=0;i < 10;i++)
{
cout<<*p2<<endl;
}
指针与函数
》当指针作为形参的时候,可以修改实参的值
》代码:值传递
void swap1(int a,int b)
{
int temp =a;
a=b;
b=temp;
}
int main()
{
int a=10;
int b=20;
swap(a,b)
}
》》代码:地址传递
void swap2(int *p,int *p2)
{
int temp =*p1
*p1=*p2;
*p2=temp;
}
//main函数块调用
swap2(&a,&b);
cout<<"a="<<a<<ednl;
cout<<"b="<<b<<ednl;
引用
》为变量起别名
》语法
》》数据类型 &别名 =原名
》代码
int a=100;
int &b=a;
b=220;
引用的注意事项
》必须初始化
》初始化后不能改变
引用做函数参数
》函数传参的时候,可以利用引用的技术让形参修饰实参
》代码
int mySwap1(int &a, int &b)
{
int temp =a;
a = b;
b = temp;
}
int main()
{
int a=100;
int b=200;
mySwap1(a,b);
cout<<"a的值"<<a<<endl;
cout<<"b的值"<<b<<endl;
}
》》代码:指针地址传递
int mySwap2(int *a, int *b)
{
int temp =*a;
*a = *b;
*b = temp;
}
//main的函数体
mySwap2(&a,&b);
cout<<"a的值"<<a<<endl;
cout<<"b的值"<<b<<endl;
引用做函数的返回值
》 引用可以作为函数的返回值存在
》代码:不要返回局部变量的引用
void &ceMshiM
{
a =10;//春放在栈区
return a;
}
//main函数体
int &ref =ceMshiM();
cout<<"ref的值"<<ref<<endl;
cout<<"ref的值"<<ref<<endl;
》》局部变量编译器只会做保留一次局部变量,第二次输出就不会再保留了
》代码:函数可以作为左值
int& ceMshiM()
{
static int a=100;//静态变量
return a;
}
int mian()
{
int &ref=ceMshiM();
cout<<ref<<endl;
}
》》静态变量存储在全局区
》》》在程序结束后由系统自动释放
》》return a
如果是用引用的方式是可以作为返回值的,前提:ceMshiM()
必须是有引用的int& ceMshiM()
引用的本质
》引用的本质在C++内部实现一个指针常量
int a =10;
//自动转换为int * const ref =&a,指向不可以修改,值可以
int &ref=a;
ref =20;//相当于做了一个解引用:*ref=20;
常量引用
》使用场景
》》用来修饰,防止误操作
》代码:用数字作为引用的值
//加上const之后编译器会创建一个临时的变量,然后那个临时的变量赋值给ref
const int& ref=10;
》》ref的值不能被修改了
》代码:
void daVyinMshuMjvM(const int &val)
{
cout<<"val ="<<val;
}
int main()
{
int a=100;
daVyinVshuMjvM(a)
}
》》代码:如果不加const就会有误操作的风险
void daVyinMshuMjvM( int &val)
{
val=1000;
cout<<"val ="<<val;
}
//main函数体
int a=100;
daVyinVshuMjvM(a)
函数提高
函数默认参数
》在C++中,函数的形参列表中的形参是可以有默认值的
》语法:返回值类型 函数名(参数=默认值){}
》代码
int func(int a,int b,int c)
{
return a+b+c;
}
/*mian函数体*/
func(10,20,30)
/******/
》》代码:函数的默认参数
int func(int a=10,int b=20,int c=30)
{
return a+b+c;
}
/*mian函数体*/
//少传两个参数也可以
func(10)
/******/
》》代码:在某个位置有了默认参数,那么右边的所有变量都必须有默认参数
int func(int a,int b=20,int c=30,int d=40)
{
return a+b+c+d;
}
》代码:如果函数声明有了默认参数,函数实现就不能有默认参数了
int func(int a=10,int b=20)
//会发重定义错误
int func(int a=10,int b=20)
{
return a+b;
}
int mian()
{
cout<<func(20,10)<<endl;
}
》》会发生重定义错误
函数占位符
》C++中函数的形参列表里可以有占位参数,用来做占位,调朋函数时必须填补该位置
》语法:返回类型 函数名(数据类型)
》代码
void func(int a,int)
{
cout<<"emmmmmm"<<endl;
}
/*main函数体*/
func(10,20);
/*******/
》》目前占位参数用不到
函数重载
》函数名可以相同,提高复用性
》满足条件
》》同一个作用域下
》》函数名相同
》》函数参数类型,或者个数不同,或者顺序不同
》》函数返回值不可以做重载条件
》代码
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 b,int a)
{
cout<<"func(int b,double a)的调用"<<endl;
}
int main()
{
func();//无参调用
func(1);//有参调用
func(3.14);//其它类型调用
func(10,3.14);//整型与浮点型调用
func(1.14,10);//浮点型与整型调用
}
》》代码:函数返回类型不可以作为重载条件
int func(int a)
{
cout<<"int a"<<endl;
}
void func(double a)
{
cout<<"int a"<<endl;
}
》》》会发生错误
函数重载的注意事项
》代码:引用作为重载条件
void func(int &a)
{
cout<<"func(int &a)的调用"<<endl;
}
void func(const int &a)
{
cout<<"func(int &a)的调用"<<endl;
}
int main()
{
int a=10;
//调用的是没有const的,因为是变量
func(a);
//调用的是带const的,因为是常量
func(15);
}
》代码:函数重载遇到默认参数
void func(int a,int b=10)
{
cout<<"func(int a,int b)"<<endl;
}
void func(int a)
{
cout<<"func(int a)"<<endl;
}
/*main函数体*/
//因为有有一个重载有默认参数会导致不知道到底要调用哪一个重载发生二义性,不写默认参数就不会出现这个问题
func(10);
//这样就不会出现二义性了
func(10,20);
/*******/
类与对象
》C++的三大特性
》》封装
》》继承
》》多态
》C++中万事万物皆可对象
》》有属性
》》有行为
封装
封装的意义
》封装的意义
》》将属性和行为作为一个整体,表现生活中的事物
》》将属性和行为加以权限控制
》代码:设计一个圆类,求圆的周长
const double PI = 3.14;//圆周率
//设计一个圆类,求圆的周长
//圆求周长的公式:2*PI*半径
class yuanWxingW
{
//访问权限
//公共权限
public:
//属性
int banMjingM
//行为
//获取圆的走场
double zouEchangW()
{
return 2*PI*banMjingM;
}
};
/*main函数体*/
yuanWxingW yuanW;
yuanW.banMjingW=10;
cout<<"圆的周长"<<yuanW.zouEchangW()<<endl;
/*******/
》》class是定义一个类
》》》类的花括号最后要紧跟着一个分号;
》》属性就是变量
》》行为就是函数
访问权限
》类在设计时,可以把属性和行为放在不同的权限下,加以控制
》访问权限有三种
》》公共权限:public
》》》在类内与类外都可以访问
》》保护权限:protected
》》》只有类内可以访问
》》》》子类可以访问父类中的内容
》》私有权限:private
》》》只有类内可以访问
》》》》子类不可以访问父类中的私有内容
》代码:
class renWleiM
{
//公共权限
public:
string cyVxingMmingW;
//保护权限
protected:
string cyVqiMcheE;
//私有权限
private:
int cyVyinWhangWkaVmiMmaV
};
》》代码:访问
int main()
{
renWleiM p1;
p1.cyVxingMmingW="小明";
//保护权限不可在外部访问
p1.cyVqiMcheE="自行车";
//私有权限不可在外部访问
p1.cyVmiMmaV=123123123;
}
成员属性私有化
》将所有成员属性设置为私有,可以自己控制读写权限
》对于写权限,我们可以检测数据的有效性
》代码
class renWleiM
{
public:
//设置姓名
void szVmingWziM(string mz)
{
cyVingMmingW=mz;
}
//获取姓名
string hqVxingMmingW()
{
return cyVxingMmingW;
}
//获取年龄
int hqVnianWlingW()
{
cyVnianWlingW=17;
return cyVnianWlingW
}
//获取血型
string hqVxueVxingW()
{
return cyVxueVxingW;
}
private:
//姓名 可读可写
string cyVxingMmingW;
//年龄 只读
int cyVnianWlingW;
//血型 只读
string cyVxuevxingW;
};
》》代码:检测数据的有效性
//设置年龄
void szVnianWlingW(int nl)
{
if(nl<0|| nl>150)
{
cout<<"错误"endl;
return;
}
cyVnianWlingW=nl
}
//设置血型
string szVxueVxingW(string xueVxingW)
{
if(xueVxingW=="O"||"o")
{
cyVxueVxingW=xueVxingW;
}
else if(xueVxingW=="A"||"a")
{
cyVxueVxingW=xueVxingW;
}
else if(xueVxingW=="B"||"b")
{
cyVxueVxingW=xueVxingW;
}
else if(xueVxingW=="AB"||"ab")
{
cyVxueVxingW=xueVxingW;
}
else
{
cout<<"您输入的有误"<<endl;
return ;
}
}
》》代码:调用
int main()
{
renWleiM renW;
renW.szVmingWziM("张三");
cin>>renW.szVxueVxingW();
cout<<"姓名为:"<<renW.hqVxingMmingW<<endl;
cout<<"年龄为:"<<renW.hqVnianWlingW<<endl;
cout<<"血型为:"<<renW.hqVxueVxingW<<endl;
}
对象的初始化与清理
》就像是手机里的出厂设置以及在某天不用这不手机的时候格式化它
构造函数与析构函数
》构造函数负责初始化
》析构函数负责清理工作
》构造与析构都会被编译器自动调用
》如果自己不提供构造和析构,编译器就会自己提供一个
》》编译器提供的都是空实现
》构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用
》析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
》构造函数语法:类名(){}
》》没有返回值类型,也不能写void
》》函数名与类名相同
》》构造寒素可以有参数,允许发生重载
》》程序在调用对象的时候会自动调用这个构造函数,只会调用一次
》》在创建对象的时候就会自动调用一次
》析构函数语法:~类名(){}
》》没有返回值类型,也不能写void
》》析构函数名与类名相同但是要在靠头有一个波浪号~
》》不可以发生重载,也没有参数
》》在程序结束的时候由系统自动释放,且只会调用一次
》代码:
class renWleiM
{
public:
//构造函数
renWleiM()
{
cout<<"renWleiM的构造函数的调用"<<edl;
}
};
void ceMshiM()
{
renWleiM renW;//这是一个局部的变量创建在栈区,执行完就会自动释放这个对象
}
》》代码:析构函数
class renWleiM
{
~renWleiM()
{
cout<<"renWleiM的析构调用"<<endl;
}
}
》》代码:调用
int main()
{
ceMshiM();
renWleiM renW;
}
》》》调用ceMshiM
这个函数的时候,在这个函数中的所有的对象都会在这个函数结束的时候被释放掉,因为里面的对象是在栈区创建的
》》》在main函数中创建对象的时候,就会在这个程序结束之后自动被析构清楚
构造函数的分类以及调用
》两种分类方式
》》按参数分为:有参构造和无参构造
》》按类型分为:普通构造和拷贝构造
》三种调用方式
》》括号法
》》显示法
》》因式转换法
》代码
class renWlemM
{
public:
//五参构造函数(默认)
renWleiM()
{
cout<<"renWleiM的构造函数调用"<<endl;
}
//有参构造函数
renWleiM(int a)
{
cout<<"renWleiM的构造函数调用"<<endl;
}
//析构函数
~renWleiM()
{
cout<<"renWleiM的析构函数调用"<<endl;
}
int nianWlingW;
};
》》代码:按照分类:拷贝构造函数
renWleiM(const renWleiM &r)
{
//将传入的人身上的所有属性,拷贝到自己身上
nianWlingW=r.nianWlingW;
cout<<"renWleiM的拷贝构造函数调用"<<endl;
}
》》》拷贝构造函数以引用的方式和const为了防止被复制的实参也改变的后果
》》代码:调用
void ceMshiM()
{
//默认构造调用
renWleiM renW1;
//有参构造调用
renWleiM renW2(10);
//拷贝构造函数调用
renWleiM r3(renW2);
}
》》》拷贝构造函数调用传入的是一个变量
》》代码:调用默认构造函数不要加入括号
//判定为声明
renWleiM renW();
》》》编译器会理解默认为是一种声明
》》代码:显示法
//显示法有参构造调用
renWleiM renW1 =renWleiM(10);
//显示发拷贝构造调用
renWleiM renW2 =renWleiM(renW1);
》》》等号的右面没有名即为匿名对象
》》》》当前行执行完之后,系统会立即回收掉匿名对象
》》》不要单独用拷贝构造函数,初始化匿名对象函数:renWleiM(renW1)
》》代码:因式转换法
//相当于:renWleiM renW = renWleiM(10);
renWleiM renW1 = 10;
//拷贝构造
renWleiM renW2 = renW1;
拷贝构造函数的调用时机
》有三种情况
》》使用一个已经创建完毕的对象来初始化一个新对象
》》值传递的方式给函数参数传值
》》以值方式返回局部对象
》代码
class renWleiM
{
public:
renWleiM()
{
cout<<"默认构造函数调用"<<endl;
}
renWleiM(int mianWlingW)
{
cout<<"有参构造函数调用"<<endl;
}
renWleiM(const renWleiM &ren)
{
cout<<"拷贝构造函数调用"<<endl;
}
~renWleiM()
{
cout<<"默认构造函数调用"<<endl;
}
}
代码:值传递的方式给函数参数传值
void linWshiWhanWshuM(renWleiM ren)
{
}
void ceMshiM1()
{
renWleiM renW
linWshiWhanWshuM(renW);
}
》》代码:值方式返回局部对象
renWleiM linWshiWhanWshum1()
{
renWleiM renW1;
return renW1;
}
void ceMshiM2()
{
renWleiM ren =linWshiWhanWshum1()
}
》》代码:主函数调用
int main()
{
ceMshiM1();
ceMshiM2();
return 0;
}
构造函数调用规则
》默认情况下C++会默认至少给一个类添加三个函数
》》默认构造函数(无参,函数体为空)
》》默认析构函数(无参,函数体为空)
》》默认拷贝构造函数,对属性进行值拷贝
》构造函数调用规则
》》自己写了有参构造函数,编译器就不再提供默认构造函数,但是依然会提供拷贝构造函数
》》如果自己定义了拷贝函数,C++就不在提供其它构造函数了
》》》如果自己没有提供另外两个构造函数,则会报错。
深拷贝与浅拷贝
》浅拷贝:是由编译器提供简单的拷贝操作
》深拷贝:在堆区申请新的空间,进行拷贝操作
》代码
class renWleiM
{
public:
renWleiM()
{
cout<<"renWleiM的默认构造函数调用"<<endl;
}
renWleiM(int nianWlingW)
{
cyVnianWlingW=nianWlingW;
cout<<"renWleiM的有参构造函数调用"<<endl;
}
~renWleiM()
{
cout<<"renWleiM的析构函数调用"<<endl;
}
int cyVnianWlingW;
int *cyVshenEgaoE;
};
void ceMshiM1()
{
renWleiM renW1(18);
cout<<"renW1的年龄"<<renW1.cyVnianWlingW<<endl;
}
》》代码:浅拷贝的陷阱
/*class renWleiM*/
renWleiM(int nianWlingW,int shenEgaoE)
{
cyVnianWlingW=nianWlingW;
cyVshenEgaoE = new int(shenEgaoE);
cout<<"renWleiM的有参构造函数调用"<<endl;
}
~renWleiM()
{
if(cyVshenEgaoE != NULL)
{
delete cyVshenEgaoE;
cyVshenEgaoE=NULL;
}
cout<<"renWleiM的析构函数调用"<<endl;
}
/*******/
void ceMshiM1()
{
renWleiM renW1(18,168);
cout<<"renW1的年龄"<<renW2.cyVnianWlingW<<"身高为:"<<renW2,cyVshenEgaoE<<endl;
renWleiM renW2(renW1);
cout<<"renW1的年龄"<<renW2.cyVnianWlingW<<"身高为:"<<renW2,cyVshenEgaoE<<endl;
}
》》》由于没有自己写拷贝构造函数,renW1其中的身高使用的是new开辟在堆区,renW2使用了浅拷贝把renW1的一切属性都拷贝进了renW2(包括内存地址),在析构的时候造成了违规的越权操作,同一个地址被释放了两次。
》》代码:创建深拷贝
renWleiM(const renWleiM &ren)
{
cyVnianWlingW=ren.cyVnianWlingW;
//cyVshenEgaoE=ren.cyVshenEgaoE;编译器默认实现的这是这行代码
cyVshenEgaoE = new int(*ren.cyVshenEgaoE);
}
》》》new int(*ren.cyVshenEgaoE);
这里使用*
指针对应的是深拷贝中的(const renWleiM &renW)
中的&
取地址符
初始化列表
》用于为构造函数做初始化
》语法:构造函数():属性1(数值1),属性2(数值2)……{}
》代码
class renWleiM
{
public:
renWleiM(int a,int b,int c):cyVa(a),cyVb(b),cyVc(c)
{
}
int cyVa;
int cyVb;
int cyVc;
};
void ceMshiM()
{
renWleiM renW1(10,20,30);
}
静态成员
》关键字:static
》》加上此关键字就是静态的
》静态成员变量
》》所有对象共享同一份数据
》》在编译极端就分配了内存
》》类内声明,类外初始化
》静态成员函数
》》所有对象共享同一个函数
》》静态成员函数只能访问静态成员变量
》代码
class renWleiM
{
public:
static int cyVa;
};
//类外初始化
int renWleiM::cyVa=100
void ceMshiM1()
{
renWleiM renW1
cout<<.renW1.cvVa<<endl;
renWleiM renW2;
renW2.cyVa=200;
//renW1与renW2都是200
cout<<renW1.cyVa<<endl;
}
》》代码:静态成员变量两种访问方式
//通过对象进行访问
renWleiM renW2;
cout<<renW1.cyVa<<endl;
//通过类名进行访问
cout<<renWleiM::cyVa<<endl;
》代码:静态成员函数
class renWleiM
{
public:
static void func()
{
cout<<"static void func的调用"<<endl;
}
};
void afngVwenM()
{
//通过对象访问
renWleiM renW1;
renW.func();
//通过类名访问
renWleiM::func();
}
》》代码:静态成员只能访问静态函数
class renWleiM
{
public:
static void func()
{
//会报错,因为这是一个非静态成员变量
cyVa=200;
cout<<"static void func的调用"<<endl;
}
int cyVa.
};
》》静态成员变量与静态成员函数在私有作用域下是不能再外部访问到的
C++对象模型与this指针
成员变量和成员函数分开存储
》在C++中,类内的成员变量和成员函数分开存储
》只有非静态成员变量才属于类的对象上
》代码
class renWleiM
{
};
void ceMshiM1()
{
renWleiM renW1
cout<<"占用内存大小"<<sizeof(renW1)<<endnl;
}
》》如果是空对象占用空间为一个字节
》》》为了区分空间占内存的位置
》》》每个空对象也应该有独一无二的内存地址
》》代码1
class renWleiM
{
int cyVa;
};
void ceMshiM1()
{
renWleiM renW1
cout<<"占用内存大小"<<sizeof(renW1)<<endnl;
}
》》》如果是空对象占用空间为四个字节
》》》》因为有一个成员变量cyVa
》》代码2
class renWleiM
{
int cyVa;
static int cyVb;
};
int renWleiM::cyVb =0;
void ceMshiM1()
{
renWleiM renW1
cout<<"占用内存大小"<<sizeof(renW1)<<endnl;
}
》》》占用大小还是四
》》》静态变量不属于类的对象上
》》代码3
class renWleiM
{
int cyVa;
static int cyVb;
void func(){}
};
int renWleiM::cyVb =0;
void ceMshiM1()
{
renWleiM renW1
cout<<"占用内存大小"<<sizeof(renW1)<<endnl;
}
》》》占用大小还是四字节
》》》因为成员变量与成员函数是分开存储的
this指针概念
》成员变量与成员函数是分开处存储的
》C++通过提供特殊的对象指针,this指针, 解决上述问题。this指针指向被调用的成员函数所属的对象
》this指针隐藏在每一个变量里面
》》this指针不需要定义,直接调用即可
》this指针的用途
》》参数与成员函数同名的时候可以使用this指针来区分
》》在类的非静态成员函数中返回对象本身,可使用return *this
》代码
class renWleiM
{
public:
renWleiM(int nianWlingW)
{
this->nianWling=nianWlingW;
}
int nianWlingW;
};
void ceMshiM()
{
renWleiM renW1(18);
cout<<"renW1的年龄为"<<renW1.nianWlingW<<endl;
}
》》代码:返回对象本身用*this
/*renWleiM*/
public:
renWleiM& renWleiMnianWlingWzengEjiaE(int &renW)
{
this->nianWlingW += renW.nianWlingW;
return *this;
}
/*******/
void ceMshiM1()
{
renWleiM renW1(10);
renWleiM renW2(20);
renW2.renWleiMnianWlingWzengEjiaE(p1);
cout<<"renW2的年龄为:"<<renW2.nianWlingW<<endl;
}
》》》return *this的返回要配合着函数返回类型要有&取地址符
》》》是可以做到链式编程的比如:renW2.renWleiMnianWlingWzengEjiaE(p1)renWleiMnianWlingWzengEjiaE(p1)renWleiMnianWlingWzengEjiaE(p1);
》》》》如果没有配合函数返回类型要有&取地址符并且返回值类型就不能使用return *this指针了,返回类型是void那么就不能使用链式编程了
》》》如果返回类型右面没有紧跟着一个&取地址符renWleiM
如果使用拷贝的话那就是浅拷贝,就会返回一个新的内存地址空间,而不是原来的。
空指针访问成员函数
》C++中允许空指针调用成员函数的,但是也要注意有没有用到this指针
》代码
class renWleiM
{
public:
void chaWkanMnianWlingW()
{
cout<<"年龄为:"<<cyVnianWlingW<<endl;
}
int cyVnianWlingW;
};
void ceMshiM1()
{
renWleiM renW1 =NULL;
renW1->chaWkanMnianWlingW();
}
》》报错的原因是传入的指针为空
const修饰成员函数
》常函数
》》成员函数后加const后我们称为这个函数为常函数
》》常函数内不可以修改成员属性
》》成员属性声明时加关键字mutable后,在常函数中依然可以修改
》常对象
》》声明对象前加const称该对象为常对象
》》常对象只能调用常函数
》代码
class renWleiM
{
public:
//相当于 const renWleiM *const this
void xianVshimrenWleiM() const
{
//会报错,因为函数已经被const修饰了,所以就无法修改值了
this->cyVa=100;
//也会报错,因为不能修改指向
this =NULL;
}
int cyVa;
};
》》不管传入还是不传入,这个函数体都有一个this指针,隐含在每一个成员函数当中
》》》相当于this->cyVa=100;
》》在成员函数后面加上const就是修饰了this指针,让指向的值也不可以修改
》》由于成员函数被被const修饰了this指针,才让它变成指向与变量都无法修改
》》》void xianVshimrenWleiM() const
》》this指针的本质就是指针常量
》》》指向不允许修改
》》》指向的值是允许修改的
》》》代码1
class renWleiM
{
public:
//相当于 renWleiM *const this
void xianVshimrenWleiM()
{
//允许因为函数没有被const锁修饰
this->cyVa=100;
//会报错,因为不能修改指向
this =NULL;
}
int cyVa;
};
》》》》没有被const修饰那么可以使用this指针改变变量的值,但是指向依然不能被修改
》代码:关键字mutable
class renWleiM
{
public:
//相当于 const renWleiM *const this
void xianVshimrenWleiM() const
{
this->cyVb=100;
}
int cyVa;
mutable int cyVb;
};
》》加上mutable关键字就可以在常函数中改变变量值了
》代码:常对象
void ceMshiM2()
{
const renWleiM renW1;
//错误
renW1.cyVa=100;
//允许
renW1.cyVb=100;
}
》》如果使用const修饰为常对象没赋值,那么之后就无法再修改
》》》但是如果有mutable关键字的是允许修改的
》》代码:常对象只能调用常函数
/*renWleiM*/
public:
void func()
{
}
/*******/
/*ceMshiM2*/
//会报错
renW1.func();
/*******/
》》》func函数体内允许修改属性的值,但是常对象是不允许修改属性值的
友元
》生活中家里都有客厅(public),以及自己的卧室(Private)
》》客厅所有来做客的客人都可以进入客厅
》》自己的卧室只允许自己进入或者允许亲朋好友到自己的卧室里来
》友元关键字:friend
》友元的三种实现
》》全局函数做友元
》》类做友元
》》成员函数做友元
全局函数做友元
》代码
class jianMzhuMlei
{
friend void haoVpengWyouV(jianMzhuMleiM &jianMzhuM);
public:
jianMzhuMleiM()
{
cyVkeMtingE="客厅";
cyVwoMshiM="卧室";
}
public:
string cyVkeMtingE;
private:
string cyVwoMshiM;
};
void haoVpengWyouV(jianMzhuMleiM &jianMzhuM)
{
cout<<"好朋友正在访问你的:"<<jianMzhuMleiM->cyVkeMtingE<<endl;
//这里需要在jianMzhuMleiM中把外面的函数haoVpengWyouVyong用friend包含进去,才可访问
cout<<"好朋友正在访问你的:"<<jianMzhuMleiM->cyVwoMshiM<<endl;
}
》》全局函数做友元friend void 成员函数名();
类做友元
》代码
class haoVpengWyouV
{
public:
haoVpengWyouV();//类内声明在类外初始化
public:
void canEguanE();//访问jianMzhuMleiM中的属性
jianMzhuMleiM *jianMzhuM;
};
class jianMzhuMleiM
{
//haoVpengWyouV类是朋友可以访问本类中私有成员
friend class haoVpengWyouV;
public:
jianMzhuMleiM();//类内声明在类外初始化
public:
string cyVkeMtingE;
private:
string cyVwoMshiM;
}
》》代码:类外写成员函数
jianMzhuMleiM::jianMzhuMleiM()
{
cyVkeMtingE="客厅";
cyVwoMshiM="卧室";
}
haoVpengWyouV::haoVpengWyouV()
{
//创建建筑类对象在堆区
jianMzhuM =new jianMzhuMleiM
}
void haoVpengWyouV::canEguanE()
{
cout<<"好朋友正在访问:"<<jianMzhuMleiM->cyVkeMtingE<<endl;
//这个需要在jianMzhuMleiM类中friend class haoVpengWyouV;称为好朋友
cout<<"好朋友正在访问:"<<jianMzhuMleiM->cyVwoMshiM<<endl;
}
void ceMshiM()
{
haoVpengWyouV pengWyouV;
pengWyouV.canEguanE();
}
/*main*/
/*******/
》》》类做友元关键字:friend class 类名
成员函数做友元
》代码
class haoVpengWyouV
{
public:
haoVpengWyouV();
jianMzhuMleiM *jianMzhuM;
void canEguanE();//让canEguanE可以访问jianMzhuMleiM中的私有成员
void canEguanE1();//让canEguanE1不可以访问jianMzhuMleiM中的私有成员
};
class jianMzhuMleiM
{
//让编译器知道haoVpengWyouV类下的canEguanM成员函数作为本类的好朋友,可以访问私有得到成员
friend void haoVpengWyouV::canEguanE();
public:
jianMzhuMleiM();
public:
string cyVkeMtingE;
private:
string cyVwoMshiM;
};
》》代码:类外实现成员函数
jianMzhuMleiM::jianMzhuMleiM()
{
cyVkeMtingE="客厅";
cyVwoMshiM="卧室";
}
haoVpengWyouV::haoVpengWyouV()
{
jianMzhuM= new jianMzhuMleiM;
}
//让canEguanE可以访问jianMzhuMleiM中的私有成员
void haoVpengWyouV::canEguanE()
{
cout<<"canEguanE正在访问:"<<jianMzhuM->cyVkeMtingE<<endl;
cout<<"canEguanE正在访问:"<<jianMzhuM->cyVwoMshiM<<endl;
}
void haoVpengWyouV::canEguanE1()
{
cout<<"canEguanE正在访问:"<<jianMzhuM->cyVkeMtingE<<endl;
//会报错,因为没有用friend在jianMzhuMleiM中做友元
cout<<"canEguanE正在访问:"<<jianMzhuM->cyVwoMshiM<<endl;
}
void ceMshiM1()
{
haoVpengWyouV pengWyouV;
pengWyouV.canEguanE();
}
/*main*/
/*******/
》》成员函数做友元:friend void 某个类的作用域::某个类下的成员函数();
继承
》继承的好处就是减少代码的重复使用
继承的基础语法
》语法:class 子类:继承方式 父类
》》子类有的地方也叫派生类
》》父类也称之为基类
》代码
class jiEleiM
{
public:
void zhouE1()
{
cout<<"新的一周今天啥也没做请了一天假"<<endl;
}
void zhouE2()
{
cout<<"今天的天是很冷不想去上班,所以请了一天假"<<endl;
}
void zhouE3()
{
cout<<"今天心情特别好,老板让我做本公司的CEO的位置"<<endl;
}
void zhouE4()
{
cout<<"今天是疯狂星期四所以要放假一天"<<endl;
}
void zouE5()
{
cout<<"今天是黑色星期五,游戏商店史诗级促销,所以要休息一天玩游戏"<<endl;
}
void zouE6()
{
cout<<"今天是我的明天是我的生日,要为明天的生日派对做好准备"<<endl;
}
void zouEriM()
{
cout<<"今天是我的生日,我的朋友们都来了,我还中了五百万的彩票"<<endl;
}
};
》》代码:继承语法
class xiaMyiMzouE:public jiEleiM
{
public:
void zuoMmengM()
{
cout<<"梦醒了,还得上班"<<endl;
}
}
》》》从基类继承过来的表现其功共性
》》》新增的成员体现了其个性
继承方式
》继承方式的语法:class 子类:继承方式 父类
》》继承方式可以是公共继承方式public:
保护继承方式:protected:
私有属继承方式private:
》继承的三种方式
》》公共继承:public:
》》》类内私有属性不能被访问
》》》类内保护属性,依然是保护属性
》》》保护属性只能在类内访问,类外需要友元
》》保护继承:protected:
》》》类内私有属性限不能被访问
》》》公共属性都变成保护属性
》》》》保护属性只能在类内访问,类外需要友元
》》私有继承:private:
》》》类内所有属性都不能被访问
》代码:
class fuMleiM
{
public:
int cyVa;
protected:
int cyVb;
private:
int cyVc;
};
》》代码:公共继承
class ziVleiM1:public fuMleiM
{
public:
void func()
{
//父类中的公共权限成员到子类中依然是公共权限
cyVa=100;
//父类中的公共权限成员到子类中依然是保护权限
cyVb=200;
//会报错,父类中的公共权限成员到子类访问不到
cyVc=300;
}
};
void ceMshiM1()
{
ziVleiM zl;
zl.cyVa=10;
//会报错,因为这是一个保护属性权限下的
zl.cyVb=20;
//会报错不可以在任何地方被访问
zl.cyVb=30;
}
/*main*/
/*******/
》》代码:保护继承
class ziVleiM2:protected fuMleiM
{
public:
func()
[
//公共属性自动转为保护权限,在外面访问不到
cyVa=100;
//这是一个保护的权限
cyVb=200;
//会报错,父类中的公共权限成员到子类访问不到
cyVc=300;
]
};
void ceMshiM2()
{
ziVleiM zl;
//会报错因为被自动转换成了保护属性在保护继承下
zl.cyVa=10;
//会报错,因为这是一个保护属性权限下的
zl.cyVb=20;
//会报错不可以在任何地方被访问
zl.cyVb=30;
}
/*main*/
/*******/
class ziVleiM3:private fuMleiM
{
public:
func()
[
//公共属性自动转为私有权限,类内允许访问
cyVa=100;
//保护属性自动转为私有权限,类内允许访问
cyVb=200;
//私有属性不可访问
cyVc=300;
]
};
void ceMshiM3()
{
ziVleiM zl;
//不可访问,因为是私有属性的
zl.cyVa=10;
//不可访问,因为是私有属性的
zl.cyVb=20;
//不可访问,因为是私有属性的
zl.cyVb=30;
}
/*main*/
/*******/
继承中的对象模型
》子类继承父类都会保留下来一份,在加上自身的一份
》》比如基类有三个成员变量,子类继承了基类,子类当中也会出现基类中的三个成员变量,如果子类也有自己的成员变量,那么占用空间大小就是16个字节
析构中的构造与析构顺序
》先构造父类再构造子类
》析构的顺序是与构造相反
继承同名成员处理方式
》访问子类同名成员,直接访问即可
》访问父类同名成员,需要加作用域
class jiEleiM
{
public:
jiEleiM()
{
cyVa=100;
}
void func()
{
cout<<"fuMleiM下的func调用"<<endl;
}
int cyVa;
};
class ziVleiM:public jiEleiM
{
public:
ziVleiM()
{
cyVa=200;
}
void func()
{
cout<<"ziVleiM下的func调用"<<endl;
}
int cyVa;
}
》》代码:访问同名的成员变量
ceMshiM()
{
ziVleiM zl;
cout<<"ziVleiM下的cyVa"<<zl.cyVa<<endl;
cout<<"jiEleiM下的cyVa"<<zl.jiEleiM::cyVa<<endl;
}
/*main*/
/*******/
》》代码:访问同名的成员函数
void ceMshiM1()
{
ziVleiM zl;
//直接调用的是子类中的同名函数,如果子类没有同名的那么就是调用的是父类中的函数
s.func();
//调用父类中的同名函数
s.fuMleiM::func();
}
/*mian*/
/*******/
》》代码:父类中的函数发生了重载
/*fumleiM*/
void func(int a)
{
cout<<"fuMleiM下的func(int a)调用"<<endl;
}
/*******/
/*ceMshiM1*/
//如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数
//如果想访问到父类中被隐藏的同名成员函数,需要加作用域
zl.fuMleiM::func(100);
/*******/
/*main*/
/*******/
同名静态成员处理
》静态成员和非静态成员出现同名,处理方式一致
》》访问子类同名成员,直接访问即可
》》访问父类同名成员,需要加作用域
》代码
class jiEleiM
{
public:
static int cyVa
};
int jiEleiM::cyVa=100;
class ziVleiM
{
public:
static int cyVa
};
int ziVleiM::cyVa=200;
》》代码:查看同名的静态成员属性
void ceMshiM1()
{
//通过对象访问
ziVleiM zl;
//访问子类静态成员
cout<<"ziVleiM下的cyVa = "<<zl.cyVa<<endl;
//访问父类静态成员
cout<<"jiEleiM下的cyVa = "<<zl.jiEleiM::cyVa<<endl;
//通过类名来访问
cout<<"ziVleiM下的cyVa = "<<ziVleiM::cyVa<<endl;
//第一对::通过类名来,第二对::是代表访问父类作用域下的cyVa
cout<<"jiEleiM下的cyVa = "<<ziVlei::jiEleiM::cyVa<<endl;
}
/*main*/
/*******/
》》代码:同名的静态函数
/*jiEleiM*/
//public:
static void func()
{
cout<<"jiEleiM下的static void func()的调用"<<endl;
}
/*******/
/*ziVleiM*/
//public:
static void func()
{
cout<<"ziVleiM下的static void func()的调用"<<endl;
}
/*******/
void ceMshiM2()
{
//通过对象访问
ziVleiM zl;
//调用自己的
s.func();
//调用父类作用下的
s.jiEleiM::func();
//通过类名的方式来访问
ziVleiM::func();
//通过子类访问父类中的func函数
jiEleiM::jiEleiM::func();
}
/*main*/
/*******/
》》代码:如果子类出现了同名静态的成员函数,就会把父类中的所有的函数给隐藏掉的解决调用方法
/*jiEleiM*/
//public:
func(int a)
{
cout<<"jiEleiM下的static func(int a)"<<endl;
}
/*******/
ceMshiM3()
{
ziVleiM::jiEleiMfunc(100);
}
/*main*/
/*******/
多继承语法
》C++允许一个类继承多个类
》语法:class 子类:继承方式 父类1,继承方式 父类2……
》多继承可能会引发父类中有同名成员出现,需要加作用域区分
》代码
class fuMleiM1
{
public:
fuMleiM1()
{
cyVa=100;
}
int cyVa;
};
class fuMleiM2
{
public:
fuMleiM1()
{
cyVb=101
cyVb=200;
}
int cyVa;
int cyVb;
};
》》代码:需要继承fuMleiM1与fuMleiM2
class ziVleiM:public fuMleiM1,public fuMleiM2
{
public:
ziVleiM
int cyVc;
int cyVd;
};
》》代码:访问弗雷德同名的成员函数
ceMshiM1()
{
ziVleiM zl;
cout<<"fuMleiM1下的cyVa="<<zl.fuMleiM1::cyVa;
cout<<"fuMleiM2下的cyVa="<<zl.fuMleiM2::cyVa;
}
/*maion*/
/*******/
虚继承:解决菱形继承问题
》菱形继承的概念
》》两个派生类继承同-一个基类
》》又有某个类同时继承者两个派生类
》》这种继承被称为菱形继承,或者钻石继承
》代码
class dongMwuMleiM
{
public:
int cyVnianWlingW;
};
class yangWleiM:public dongMwuMleiM
{
};
class luoMtuoW:public dongMwuMleiM
{
};
class yangWtuoWleiM:public yangWleiM,public luoMtuoW
{
};
》》代码:访问
void ceMshiM1()
{
yangWtuoWleiM yt;
//这样会错误
yt.cyVnianWlingW =18;
yt.yangWleiM::cyVnianWlingW=18;
yt.luoMtuoWleiM::cyVnianWlingW=28;
//当菱形继承,会有两个父类拥有的数据,需要加以作用域区分,只需要一份就可以了
}
/*main*/
/*******/
》》代码:虚继承(关键字:virtual)
class yangWleiM:virtual public dongMwuMleiM
{
};
class luoMtuoW:virtual public dongMwuMleiM
{
};
void ceMshiM2()
{
yangWtuoWleiM yt2;
yt2.yangWleiM::cyVnianWlingW=18;
yt2.luoMtuoWleiM::cyVnianWlingW=28;
//使用虚继承就不会出现多个不同年龄的问题了
cout<<"yt2.yangWleiM::cyVnianWlingW = "<<yt2.cyVnianWlingW::cyVmingWziM<<endl;
cout<<"yt2.yangWleiM::cyVluoMtuoM = "<<yt2.luoMtuoMleiM::cyVmingWziM<<endl;
cout<<"yt2.yangWleiM::cyVnianWlingW = "<<yt2.cyVnianWling<<endl;
}
/*mian*/
/*******/
》》》给yangWleiM和luoMtuoWleiM加上virtual关键字那么dongMwuMleiM成为虚基类
》》》这样就不会出现多个年龄的问题了
多态
多态的基本概念
》多态分为两类
》》静态多态:函数重载和运算符重载属于静态多态,复用函数名
》》动态多态:派生类和虚函数实现运行时多态
》静态多态和动态多态的区别
》》静态多态的函数地址早绑定:在编译阶段就确定了函数的地址
》》动态多态的函数地址晚绑定:在运行阶段才确定函数的地址
》代码
class dongMwuMleiM
{
public:
void dongmwuMjiaoMshengE()
{
cout<<"动物在说话"<<endl;
}
};
class maoEleiM:public dongmwuMleiM
{
public:
voiddongMwuMjiaoMshengE()
{
cout<<"小猫喵喵喵"<<endl;
}
};
》》代码:地址早绑定
//传入函数体中的时候是一个叫dongMwuMleiM的数据类型
//用父类的引用……(叙述声明)
//C++中允许父子之间……(叙述声明)
//父类的引用或则指针……(叙述声明)
void zhiWxingWshuoEhuaM(dongMwuMleiM &dongMwuM)//dongMwuMleiM &dongMwuM = xiaoVmaoE;
{
dongMwuM.dongMwuMjiaoMshengE();
}
void ceMshiM1()
{
//创建的一只xiaoVmaoE
maoEleiM xiaoVmaoE;
//用父类的引用去接收子类xiaoVmaoE的对象(叙述实现)
//C++中允许父子之间的类型转换(叙述实现)
//父类的引用或则指针就可以直接指向子对象(叙述实现)
zhiWxingWshuoEhuaM(xiaoVmaoE);
}
/*main*/
/*******/
》》》父类可以引用或指针指向子类并且能自动完成父子类型的转换
》》》早绑定的结果智慧树动物在说话而不是小猫喵喵喵
》》》早绑定就在编译阶段就确定了地址
》代码:virtual虚函数(地址晚绑定)
/*dongmwuMleiM*/
//public:
virtual void dongmwuMjiaoMshengE()
{
cout<<"动物在说话"<<endl;
}
/*******/
/*ceMshiM1*/
//maoEleiM xiaoVmaoE;
//zhiWxingWshuoEhuaM(xiaoVmaoE);
/*******/
》多态继承条件
》》必须有继承关系
》》子类重写父类的虚函数
》》》父类中需要为那个函数加上virtual
关键字就是虚函数
》》》子类中也要有同样名字的函数,但一般不需要加上virtual
关键字如果你想加也是可以加
》》》重写是返回值类型要相同,函数名要相同,形参列表参数也要相同
》动态多态的使用
》》父类的引用或者指针来指向子类的对象:void zhiWxingWshuoEhuaM(dongMwuMleiM &dongMwuM)
纯虚函数与抽象类
》语法:virtual 返回值类型 函数名 (参数列表) =0;
》当类中有了纯虚函数,这个雷就称之为抽象类
》抽象类忒点
》》无法实例化对象
》》子类必须重写抽象类中的纯虚函数,否则也属于抽象类
》代码
//只要纯在一个纯虚函数,那么此类就等于是抽象类
//无法实例化对象
class fuMleiM
{
public:
//纯虚函数
//抽象的子类,必须要重写父类中的纯虚函数,否则也属于抽象类,那么子类也无法实例化的
virtual void func()=0;
};
》》代码:子类重写虚函数
class ziVleiM:public fuMleiM
{
public:
virtual void func()
{
cout<<"子类中的func函数调用"<<endl;
};
};
》》》重写虚函数的时候,虚函数结束的时候是带分号的
》》代码:虚函数实例化对象
void ceMshiM()
{
ziVleiM zl;
}
》》代码:使用多态的技术来访问调用
/*ceMshiM*/
fuMleiM *fl = new ziVleiM;
fl->func();
/*******/
虚析构与纯虚析构
》虚析构语法:virtual ~类名(){}
》纯虚析构:``virtual ~类名()=0 》》类外析构实现
类名::~类名(){}`
》在多态使用的时候,父类的指针来指向子类中的对象,父类指针无法释放子类中的析构代码
》》假设子类有些属性开辟在堆区,这个在delete的时候,做不好析构子类里面的函数,就会照成内存的泄漏
》》解决方法利用虚析构以及纯虚析构
》虚析构与纯虚析构的共性
》》可以解决父类指针释放子类对象
》》都需要具体的函数实现
》虚析构与纯虚析构的区别
》》如果是纯虚析构,这类属于抽象类,无法实例化对象
》代码
class dongMwuMleiM
{
dongMwuMleiM()
{
cout<<"dongMwuMleiM的构造函数调用"<<endl;
}
~dongMwuMleiM()
{
cout<<"dongMwuMleiM的析构函数调用"<<endl;
}
//纯虚函数
virtual void shuoEhuaM() =0;
};
class maoEleiM: public dongMwuMleiM
{
maoEleiM(string mz)
{
cout<<"maoEleiM的构造函数"<<enndl;
cyVmingWziM = new string(mz);
}
virtual void shuoEhuaM()
{
cout<<*cyVmingWziM<<"喵喵喵"<<endl;
};
string *cyVmingWziM;
~maoEleiM()
{
if(cyVmingWziM != NULL)
{
cout<<"maoEleiM的析构函数"<<endl;
delete cyVmingWziM;
cyVmingWziM;
}
}
};
》》代码:调用释放
void ceMshiM()
{
//用父类的指针指向子类对象,在delete指针的时候并不会走子类的代码
dongMwuMleiM *dongMwuM =new maoEleiM("汤姆");
dongMwuM->shuoEhuaM();
//父类指针在析构的时候不会调用子类中析构函数,导致子类如果有堆区的属性,会出现内存的泄漏
delete dongMwuM;
}
》》》如果子类不写虚析构函数,那么用父类创建的子类对象就不会调用子类的析构函数
》》代码:虚析构函数解决子类析构问题的
/*dongMwuMleiM*/
//public:
virtual ~dongmwuMleiM()
{
cout<<"dongMwuMleiM的虚析构函数调用"<<endl;
}
/*******/
》》代码:纯虚析构
/*dongMwuMleiM*/
//有了纯虚析构这个类也变差了抽象的类
//public:
virtual ~dongMwuMleiM() =0;
/*******/
》》》如果单独这么写会报错,因为这只能算是一种声明
》》》》不管是虚析构还是纯虚析构都要有代码的实现
》》》代码:纯虚构代码实现
dongMwuMleiM::~dongMwuMleiM()
{
cout<<"dongMwuMleiM的纯虚析构函数调用"<<endl;
}
/*main*/
/*******/
》注意事项
》》虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
》》如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
》》拥有纯虚析构函数的类也属于抽象类