学习视频地址https://www.bilibili.com/video/BV1ob411q7vb?p=28
1.引用类型:int & 变量名
是类型别名,指针与目标相同
可以作为函数参数传递,也可以作为返回值
2.常量引用:const int & 变量名
作用:不能通过该引用修改目标对象的值
3.内联函数
在函数定义时前加inline使编译函数时将函数体直接插入主程序,减少了参数出入栈的步骤
4.函数重载
使功能近似相同的函数,命名相同
编译器根据函数调用语句中的实参个数和类型判断到底是哪个函数
5.类的基本要素
类的定义:类的属性+成员函数的声明
成员函数的定义:在类外定义(类名::函数名)或者直接在类内定义
访问权限:public private只能在成员函数内被 protected
6.构造函数:将类的各个属性初始化
以类名作为函数名,在类中声明,类外定义。
这样在定义一个类时就需要说明初始化的参数值(可以缺省)
7.复制构造函数
形如类名::类名(函数名)+(该类型的常引用){}
起到复制或者别的作用。
8.类型转换构造函数
参数只有一个,通过临时对象实现类型转换,因此伴随着临时对象的生成和消亡
9.析构函数
对象消亡时析构函数被调用
~+函数定义
10.this指针
c++中类的成员函数在定义时可以直接作用到类中的成员,翻译成c就是一个全局函数通过一个指向struct中成员对象
的this指针来对其进行操作,this指针作为全局函数的一个参数。
class CCar{
public:
int price;
void SetPrice(int p);
};
void CCar::SetPrice(int p){
price=p;
}
翻译为c
struct CCar{
int price;
}
void SetPrice(struct CCar *this,int p)
{this->price=p;}
this的作用1:
非静态成员函数中可以直接使用this来代表指向该函数作用的对象的指针。
//继续以CCar为例
void SetPrice(int p)
{
this->price=p;
return *this;
}
//可以这样调用
int mian()
{
CCar p1(2),p2(3);
p1=p2.SetPrice(4);//p1的最终值为4,this的指向由p2决定。
return 0;
}
注意:(翻译后)this指针首先出现在函数的形参列表中,并没有真正被使用,具体是否使用还要看函数的内容
11.静态成员变量(随时使用)
定义成员变量或函数时加入static关键字,该成员独立于类外为所有类所共享
访问方法:
1.类名::成员名
2.对象名.成员名
3.指针->成员名
4.引用.成员名
12.成员对象和封闭类
成员对象:是类中的成员,一个实例对象
封闭类:包含成员对象的类
类构建时,初始化顺序:成员对象,封闭类
类消亡时,初始化顺序:封闭类,成员对象
类必须初始化,成员对象通过无参或者有参构造函数初始化,没有构造函数编译会出错
冒号:初始化列表:为每一个成员变量指定一个初始值CTyre(int r,int w):radius®,width(w){} 是构造函数的另一种形式
13.友元
友元函数:可以访问类的私有成员,定义时在前加friend
友元类:A是B的友元类,A的成员函数可以访问B的私有成员,在B中声明friend class A
14.运算符重载
语法:返回值类型 +operator+需要重载的运算符+形参列表
{
(重载的形式)
return 重载的目标对象
}
eg:
class complex
{
pubic:
double real,imag;
complex(double r=0.0,double i=0.0):real(r),imag(i){};
complex operrator-(const complex &a){};
};
complex operator+(const complex &a,const complex &b)
{
return complex(a.real+b.real,a.imag+b.imag);//return的一个类,同时调用构造函数对其进行初始化
}
complex complex::operator-(const complex &a)
{
return complex(real-a.real,imag-a.imag);
}
可以看作一个函数:operator-()
注意:重载为成员函数时,参数个数为运算符目数减一(在对象内部重载)
重载为外部函数时,参数个数为运算符目数
原理如下:
调用实例
int main()
{
complex a(4,4),b(1,1),c,d;
c=a+b;//等价于c=operator+(a,b),调用的是外部函数
d=a-b;//等价于d=a.operator-(b),调用的是运算符左侧类中的成员函数
return 0;
}
15.赋值运算符重载
不总是有相同对象赋值对象的情况,那么当赋值运算符两边的类型不匹配时,就需要重载赋值运算符
实例:
class String{
private:
char *str; //指明一块存放string的区域
public:
String():str(new char[1]){str[0]=0;} //申请一块长度为1的char用以初始化string
const char*c_str(){return str;};
String & operator = (const char *s);
String::~String(){delete [] str;}
};
String & String::operator = (const char *s)//常量指针保证该区域的地址不会被改变减少bug
{
delete [] str; //释放原有str
str = new char[strlen(s)+1]; //根据实际赋值情况申请新长度的str
strcpy(str,s); //赋值
return * this;
}
使用过程中可能会出现的问题:
1.当一个string类型赋值另一个string类型时,可以不重载吗?
赋值“=”运算符是将该区域所有的内存变为与目标内存信息相同,而string中的成员变量是一个指针,这就导致了赋值的同时丢失了赋值号左边对象的成员指针,又因为没有释放导致内存垃圾
2.重载赋值号后遇到自己赋值自己
观察以上重载函数,发现在赋值前先delete了一方的内存,这就导致自己赋值自己时把自己delete掉了
应当先加上
String & String::operator=(const string& s)
{
if(this==&s)
return *this;
delete [] str;
str=new char[strlen(s.str+1)];
strcpy(str,s.str);
return *this;
}
注意:返回值是对象引用的原因:考虑到连等情况a=b=c或(a=b)=c,c++规定赋值语句返回等号左边对象的引用,所以要保持原有风格。
16.运算符重载为友元函数
当运算符重载为外部函数时,不能访问对象的私有成员,当重载为成员函数时,例如a+5,由于调用的是运算符左侧对象的成员函数,导致5+a编译出错,那么就需要将运算符重载为友元函数。
17.流插入运算符和流提取运算符的重载
流插入运算符:
样例:假设我们有一个s类,我们想输出这个类中的一个特定成员age,需要用到ostream类
ostream & operator<<(ostream& o,const s& sp)
{
o<<p.age;
return o;//为了连续输出:a<<b<<c成立,必须返回ostream对象才能输出
}
流提取运算符:
样例:通过键盘输入数字用空格分隔,自动转换为a+bi的形式
istream & operator>>(istream& i,const complex& c)
{
string s;
i>>s;
int pos=s.find("+",0);
string sTmp=s.substr(0,pos);//分离出代表实部的字符串
c.real=atof(sTmp.c_ctr());
sTmp=s.substr(pos+1,s.length()-pos-2);//分离出代表虚部的字符串
c.imag=atof(sTmp.c_ctr());
return i;
}
18.继承和派生
目的:提高可重用性
特点:
1、派生类具有基类的全部成员函数和成员变量,不论是private,protected,public,派生类的成员函数中依然不能访问基类中被protected的对象
语法: class派生类名:public基类名
{
}
eg:学生
class CStudent{ //基类
private:
string sName;
int nAge;
public:
bool ifGood(){};
void SetName(const string& name){sName = name};
};
class CUndergraduateStudent:public CStudent{
private:
int nDevelopment;//派生类的新属性
public:
bool ifGood(){};//成员函数作用和目的相同但是方法不同,可以以相同名字覆盖基类在派生类中原有的成员函数
bool BaoYan(){};
}
2、派生类对象的内存空间等于基类所有成员的内存空间加上派生类新属性的内存空间,基类对象的存储空间位于派生类对象新增的成员变量之前
3、类之间的两种关系:继承和复合
·继承关系
基类与派生类的关系,目的是提高代码的重用性。
继承关系为”是“的关系,一个未毕业的学生首先是一个学生。
·复合关系
基类与复合类的关系
是”有“的关系,比如说一个人类有胳膊,胳膊便可以作为基类被人类所拥有。(包含)
·两个对象中的成员为对方的指针,为”互相知晓“的关系
19、覆盖和保护成员
1、覆盖:在派生类中定义一个与基类成员同名的成员,一样但(内容)不完全一样
一般来说,基类和派生类不定义同名成员变量
2、如若在派生类中访问基类中的成员,不能因为它们是继承关系就直接访问,也需要加作用域符号
3、protected特点
·基类的private成员:可以被下列函数访问(权限最高)
— 基类的成员函数
— 基类的友元函数
·基类的public成员:可以被下列函数访问(广义全部)
— 基类的成员函数
— 基类的友元函数
— 派生类的成员函数
— 派生类的友元函数
— 其他的函数
· 基类的protected成员:可以被下列函数访问(相比private多一条权限)
— 基类的成员函数
— 基类的友元函数
— 派生类的成员函数可以访问当前对象的基类的保护成员
20、派生类的(析)构造函数
引入:派生类在生成时需要调用构造函数,如果直接在派生类中写作用于被派生类所继承的基类中的私有成员,就存在无法访问的错误,那么该怎么样写派生类的构造函数呢?
1、先构造基类,以此构造派生类,即在派生类定义时调用基类的构造函数达到目的
eg:苍蝇:虫子,已知nLegs和nColor为私有成员
FlyBug::FlyBug(int legs,int color,int wings):Bug(legs,color) //一个初始化列表,可理解为构造函数的继承关系
{
nWings = wings;//新成员
}
构造的实际步骤:
在创建派生类的对象是,需要调用基类的构造函数,初始化派生类对象从基类对象中继承的成员,在执行一个派生类的构造函数之前,总是先执行基类的构造函数。
2、调用基类构造函数的两种方式
— 显式方式:在派生类的构造函数中,为基类的构造函数提供参数。
形式:derived::derived(arg_derived-list):base(arg_base-list)
— 隐式方式:在派生类的构造函数中,不操作从基类继承的私有成员,省略基类的构造函数时,派生类的构造函数则自动调用基类的构造函数。
3、派生类的析构函数
派生类的析构函数被执行时,执行完派生类的析构函数时,自动执行基类的析构函数。(先构造后析构)
4、包含对象成员的派生类的构造函数的写法
eg:
class Bug{
private:
int nLegs; int nColor;
public:
int nType;
Bug(int legs,int color);
void PrintBug(){};
};
class Skill{
public:
Skill(int n){}
};
class FlyBug:public Bug{ //FlyBug类包含了对象成员Skill和继承自Bug的变量成员
//int nWings;
//Skill sk1,sk2;
public:
FlyBug(int legs,int color,int wings);
};
FlyBug::FlyBug(int legs,int color,int wings)://常规函数名+形参格式
Bug(legs,color),sk1(5),sk2(color),nWings(wings){//特别的初始化列表格式(包含构造函数的初始化列表)
}
总结:初始化列表 ,,行!
21、public继承的赋值兼容规则
一、在继承时,基类前为什么要加public?
使类遵循赋值兼容规则(基类可兼容派生类)
1、派生类对象可以赋值给基类对象(对象向基类兼容)
因为派生类对象中有基类对象的所有元素,因此可以赋值兼容,相反,基类对象因为没有派生类对象的新元素,不能将一个基类对象赋值给派生类对象
(base)b=d
2、派生类对象可以初始化基类引用(引用向基类兼容)
base &b=d
3、派生类对象的地址可以赋值给基类对象的指针(基类对象的指针可以指向派生类中的基类成员)(指针向基类兼容)
(base)*d = &b
· 如果派生类的方式不是public而是private或protected,则上述赋值方式不可行
二、直接基类与间接基类(上层和上上上····层)
·如果类之间存在多重继承的关系,在声明派生类时,只需要列出它的直接(上层)基类
— 派生类的成员包括
·派生类自己定义的成员
·直接基类中的所有成员
·所有间接基类的全部成员
22、虚函数和多态
一、虚函数
·········在类的定义中,前面有virtual关键字的成员函数就是虚函数
·········virtual关键字只用在类定义时成员函数的声明中,写函数体时不用
··········构造函数和静态成员函数不能是虚函数
二、多态的表现形式
1、派生类的指针可以赋给基类指针
·通过基类指针调用基类和派生类中的同名虚函数时:
(1)若该指针指向一个基类的对象,那么被调用的是基类的同名虚函数
(2)若该指针指向一个派生类的对象,那么被调用的是派生类的同名虚函数
2、派生类对象可以初始化基类引用(赋值兼容规则)
·通过基类引用调用基类和派生类的同名虚函数时:
(1)若该引用引用的是一个基类对象,那么调用的是基类中的同名虚函数
(2)若该引用引用的是一个派生类对象,那么调用的时派生类中的同名虚函数
总结:多态是充分发挥了赋值兼容规则,使得基类与派生类中的虚函数灵活调用。
假设有一个基类(父类)指针,它有很多儿子,那么多态允许他根据赋值给他的儿子不同调用不同儿子的同名不同内容的虚函数,那么儿子在某种意义上做到了相互独立,在彼此相似的前提下做到了彼此不同
优点:提高了代码的维护性(继承保证)
提高了代码的扩展性(由多态保证)
把不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,以适应不同的变化