类
问题1:C语言和C++中struct定义的结构体有什么区别?
答:①C语言中函数不可以作为struct结构体的成员;C++中函数可以作为struct结构体的成员②C语言声明结构体类型之后,定义变量必须为—>struct 结构体名 变量名;C++声明好结构体之后,定义变量为—>结构体名 变量名
在C++中,结构体的定义更喜欢用class来替代
class A
{
//类体:由函数和变量组成
};
//为什么在定义类的末尾要加上';'?
//防止在结构体末尾直接定义变量
●class为定义类的关键字,A为类的名字,{}中为类的主体,要注意类定义结束时后面的分号。类中的元素称为类的成员;类中的数据称为类的属性或者类的成员变量;类中的函数称为类的方法或者类的成员函数
C++中的类类似于C语言中的结构体,但是C++中的类相比C语言中的结构体又增加了许多新的功能。
类的定义
●类的声明和定义全部放在类体中
class A
{
void Swap()
{
//...
}
}
●在类体中进行类的成员函数声明,在.cpp文件中定义
class A
{
void Swap();//在类体中只进行类的成员函数声明
}
void A::Swap()//在类体外定义时,要在函数名前加上作用域限定符
{
//...
}
class的封装
问题2:C++中的struct和class有什么区别?
●①class的默认访问权限是private,而struct为public(因为struct要兼容C)
●public成员在类外可以直接访问
●protected和private成员在类外不能被访问
●他们的作用域从该访问限定符出现的位置开始直到下一个访问限定符出现为止
●class的默认访问权限是private,而struct为public(因为struct要兼容C)
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别
eg:
class Date
{
public:
void Test()
{
_year=2018;
_month=7;
_day=21;
}
private:
int _year;
int _month;
int _day;
};
通常定义类时,将private变量放在下方,这就产生一个问题:使用变量必须先声明后使用,为什么在类中可以先使用后声明?
●编译器编译类分为三步:
1:识别类名
2:识别类中的成员变量
3:识别类中的成员函数,并对成员函数进行修改
类中各成员在内存中的布局形式
假设一:存储类的成员变量以及类的成员函数的地址
缺点:每个成员变量是不同的,但是函数是相同的,如果一个类中有多个函数并且创建多个对象,每个对象中都会保存函数的地址,相同的地址保存多次,造成内存空间的浪费
假设二:相比假设一,假设二中的对象只用存储一个地址来记录函数的地址
假设三:仅存储类的成员变量
我用VS2013求出上面定义的类的大小,结果为12,可能很轻易地看出,在类的内存中,它仅仅只存储了我们定义的三个私有变量,没有保存函数地址,类对象模型中实际只包含了该对象的数据信息。
在测试中发现,空类占1字节—>为了区分对象
如果空类占0字节,则当定义多个类时,系统不会给定义的类开辟新的内存空间,则就会导致多个类指向同一个地址
结论:一个类的大小,实际就是该类中非静态成员变量之和,当然也要进行内存对齐,要注意空类的大小
this指针
写一个程序来设置日期类
class Date
{
public:
void SetDate(int year,int month,int day)
{
_year=year;
_month=month;
_day=day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2;
Date d3;
d1.SetDate(2018,7,21);
d2.SetDate(2018,8,21);
d3.SetDate(2018,9,21);
return 0;
}
运行结果为:
为什么当d1在调用SetDate时,函数就知道去设置d1,而不去设置d2或d3?
用C语言实现上述代码:
struct Date
{
int _year;
int _month;
int _day;
}
void SetDate(struct Date* p,int year,int month,int day)
{
p->_year=year;
p->_month=month;
p->_day=day;
}
int main()
{
struct Date d1;
struct Date d2;
struct Date d3;
SetDate(&d1,2018,7,21);
SetDate(&d2,2018,8,21);
SetDate(&d3,2018,9,21);
return 0;
}
从上述代码可以看出,函数如果要操作类对象,必须将该类对象的地址传进来,否则没有办法对实参对象进行修改。实际上:C++的成员函数也是按照C语言类似方式处理的,即:成员函数实际有一个隐藏指针,指向了调用该函数的对象本身,这个隐藏指针就叫this指针。
this指针的特性:
●this指针的类型: 类类型* const
●this指针并不是对象本身的一部分,不影响sizeof的结果
●this指针作用域在类”成员函数”的内部
●this指针是”类成员函数”的第一个默认隐含函数,编译器会自动维护传递,类编写者不能显式传递
●只有在类的非静态成员函数中才可以使用this指针,其他任何函数都不可以
_thiscall调用约定:
●_thiscall只能用在类的参数确定的非静态成员函数上
●参数从右向左压栈
●如果参数个数确定,this指针通过ecx传递给被调用者;如果参数不确定(_cdecl),this指针在所有参数被压栈后压入堆栈
●对参数个数不确定的,调用者清理堆栈,否则函数自己清理堆栈
类6个默认的成员函数
●构造函数
●拷贝构造函数
●析构函数
●赋值操作符重载
●取地址操作符重载
●const修饰的取地址操作符重载
如果在类中,这六种默认的成员函数任何一个没有给出的话,编译器则会自动合成没有给出的函数,经过实际测试发现:只有在程序需要某一个默认成员函数时,编译器才会合成相应的函数。
构造函数
●构造函数是一个特殊的成员函数,函数名与类名相同,创建类类型对象时,由编译器自动调用,在对象的生命周期内只调用一次,保证每个数据成员都有一个合适的初始值。
//编写一个简单的构造函数
class Date
{
public:
Date(int year,int month,int day)
{
_year=year;
_month=month;
_day=day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d(2017,12,25);//这样创建类的对象就会让d的初始值为_year=2017 _month=12 _day=25
//但是如果写成Date d;或者Date d()的话 编译器就不知道要用什么值来初始化对象的值了
//所以有两种方法解决这个问题:
//1.重载构造函数
//2.将构造函数写成带有缺省值的构造函数
return 0;
}
1.重载构造函数
class Date
{
public:
Date(int year,int month,int day)
{
_year=year;
_month=month;
_day=day;
}
//重载构造函数
Date()
{
_year=2017;
_month=12;
_day=25;
}
private:
int _year;
int _month;
int _day;
};
2,将构造函数编写为带有缺省值的构造函数
class Date
{
public:
Date(int year=2017,int month=12,int day=25)
{
_year=year;
_month=month;
_day=day;
}
private:
int _year;
int _month;
int _day;
};
构造函数的特性:
●函数名与类名相同
●没有返回值
●新对象被创建时,由编译器自动调用,且在对象的生命周期内仅调用一次
●构造函数可以重载,实参决定了调用哪个构造函数
●无参构造函数和带有缺省值的构造函数都认为是缺省的构造函数,并且缺省的构造函数只能有一个
可以给一个不带参数的构造函数,或者给构造函数设置缺省值,两种方法只能选一种
●有初始化列表(可以不用)
只有构造函数有初始化列表
●如果没有显式定义,编译器会自动合成一个默认的构造函数
●构造函数不能用const修饰
●构造函数不能为虚函数
初始化列表
●以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个”成员变量”后面跟一个放在括号中的初始值或表达式
初始化与赋值的区别:赋值可以多次进行,初始化只能进行一次
在初始化列表中不可以用this指针来操作数据,因为此时this指针指向类对象的首地址,但类中每个对象所占的字节数编译器还不明确
注意:
●每个成员在初始化列表中只能出现一次
●初始化列表仅用于初始化类的数据成员,并不指定这些数据成员的初始化顺序,数据成员在类的定义顺序就是参数列表中的初始化顺序
●尽量避免使用成员初始化成员,成员的初始化顺序最好和成员的定义顺序保持一致
类中包含以下成员,一定要放在初始化列表的位置进行初始化:
●引用成员变量
●const成员变量
●类类型成员(该类有非缺省的构造函数)
拷贝构造函数
实质就是构造函数的重载
●只有单个形参,而且该形参是对本类类型对象的引用(常用const修饰),这样的构造函数称为拷贝构造函数。拷贝构造函数是特殊的构造函数,创建对象时使用已存在的同类对象来进行初始化,由编译器自己调用。
class Date
{
public:
Date(int _year=2017,int _month=12,int _day=25)
:_year(year)
,_month(month)
,_day(day)
{}
Date(const Date& d)
:_year(d.year)
,_month(d.month)
,_day(d.day)
{}
private:
int _year;
int _month;
int _day;
}
int main()
{
Date d1();//调用构造函数 d1._year=2017 d1._month=12 d1._day=25
Date d2(2018,6,25);//调用构造函数 d2._year=2018 d2._month=6 d2._day=25
Date d3(d1);//调用拷贝构造函数 d3._year=d1._year d3._month=d1._month d3._day=d1._day
return 0;
}
特征:
●构造函数的重载,构造函数的性质拷贝构造函数均满足
●参数必须使用类类型对象引用传递
如果采用值传递,每次调用程序都会创建临时对象,而调用的函数恰好就是拷贝构造函数,所以程序会不断地调用拷贝构造函数,不断地递归下去,直到程序崩溃
如果采用指针传递,它就不再是拷贝构造函数了
●如果没有显式定义,系统会自动合成一个默认的拷贝构造函数。默认的拷贝构造函数会依次拷贝类的数据成员完成初始化
使用的场景:
●对象实例化对象
●作为函数的参数(值传递)
●作为函数的返回值
析构函数
●与构造函数的功能相反,在对象被销毁时,由编译器自动调用,完成类的一些资源清理和汕尾工作。(析构函数不可以重载)
特性:
●析构函数在类名前面加~,析构函数没有返回值,一个类只有一个析构函数,如果没有定义析构函数,编译器会自动生成一个析构函数,对象生命周期结束时,C++编译系统自动调用析构函数
class A
{
public:
A()
{
p=new int(4);
}
~A()
{
delete p;
}
private:
int* p;
}
int main()
{
A p;
return 0;
//系统会在函数运行结束时调用析构函数
}
运算符重载
●重载运算符时是有特殊函数名的函数,关键字operator后面接需要定义的操作符符号。,运算符重载也是一个函数,具有返回值和形参表。它的形参数目与运算符的操作数目相同,使用运算符重载可以提高代码的可读性。
基本结构:返回值 operator运算符(参数列表)
可以被重载的运算符:
new new[] delete delete[] + - *
/ % ^ & | ~ ! = < > +=
-= *= /= %= ^= &= |= << >>
<<= >>= == != <= >= && || ++
-- , ->* -> () []
不可以被重载的运算符:
● 成员选择符
.* 成员对象选择符
:: 域解析操作符
?: 条件操作符
注意:
●不能通过连接其他符号来创建新的运算符:eg:operator@
●重载运算符必须有一个类类型或者枚举类型的操作数
●用于内置类型的运算符,其含义不能改变。eg:内置的整型+,不能改变其含义
●作为类成员的重载函数,其形参看起来比操作数数目少1,因为类类型的成员函数的操作符有一个默认的形参this指针,限定为第一个参数
●一般将算术运算符定义为非成员函数,将赋值运算符定义为成员函数
●运算符定义为非类的成员函数时,一般将其定义为类的友元
●== 和 != 运算符一般要成对重载
●下标操作符[]:一个非const成员并返回引用,一个是const成员并返回引用
●解引用操作符*和->操作符,不显示任何参数
●前置++/–必须返回被增量或者减量的引用,后缀式操作符必须返回旧值,并且应该是值返回而不是引用返回
●输入操作符>>和输出操作符<<必须定义为类的友元函数