这里写目录标题
类的概念
在C语言中我们完成一件代码是一步一步的走的,根据函数的调用来逐步完成,但是在C++中我们会把一件事情拆分成不同的对象,根据对这些对象的交互来完成这种事情。
在C语言中结构体我们只能定义变量,但是我们在C++中我们引入了类,它类似于结构体,但是类不仅可以定义变量,也可以定义函数。
类的基本结构:
class className
{
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分 号不能省略。
类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者 成员函数。
类的使用方法
1. 声明定义中的内联及作用域操作符 ::
1.1 声明定义不分离
使用类,我们可以在类里面定义函数。当我们在同一文件下进行类的函数定义时,我么需要小心,类可能会把我们定义的函数当作内联函数来使用。
- 以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调
用建立栈帧的开销,内联函数提升程序运行的效率。- 内联函数在编译器编译时不进入符号库中,所以inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
- inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会
用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运
行效率。- 类里面短小函数,适合做内联的函数,直接是在类里面定义的
1.2 声明定义分离
类的使用方法类似于我们之前C语言的时候进行的事情,声明与定义分离,不过类进行分离的时候我们需要加上类名+::
否则,编译器无法找到类内部函数的作用域。
这里提一嘴,命名规则会发生一些小的变动,类里面的函数变量会加一个小小的前缀或者后缀_
2. 类的限定访问符和封装
2.1 访问限定符:
- public修饰的成员在类外可以直接被访问
- protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
- 如果后面没有访问限定符,作用域就到 } 即类结束。
- class的默认访问权限为private,struct为public(因为struct要兼容C)
上述第三条:这里注意一个类里面可以有多个私有
class Person
{
public:
void PrintPersonInfo();
private:
int _year;
int _month;
int _day;
private:
char _name[20];
char _gender[3];
int _age;
};
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别
class中的默认访问权限为私有,struct默认访问权限为公有
2.2 封装
由于C语言的函数保密性很差,所以C++改进了函数的保密性,直接封装到一起,就像电脑开机只使用一个开机键一样,使程序便捷。
C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选
择性的将其接口提供给外部的用户使用
3 类的作用域
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。
class Person
{
public:
void PrintPersonInfo();
private:
char _name[20];
char _gender[3];
int _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()//作用域操作符::
{
cout << _name << " "<< _gender << " " << _age << endl;
}
4 类的实例化与大小
4.1 实例化
类创建出来的时候跟struct是一样的,都是没有实际分配的内存空间,只有将他实例化出来才可以有对应的空间。
类就像是设计的图纸,而实例化就是将图纸装成实际的建筑
4.2 计算类对象大小
类的大小只是成员变量的大小,成员函数只占据1byte,并且不储存有效数据。
一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐
为什么成员变量在对象中,成员函数不在对象中呢?
每个对象成员变量时不一样的,需要独立存储
每个对象调用成员函数是一样的,放到共享公共区域(代码段)
成员函数的大小
大小是1,这1byte不存储有效数据
占位,标识对象被实例化定义出来了。
5 this指针
一个隐藏的指针,编译器自动赋予的参数,可以调用类中的函数,在类中的函数的成员变量都是通过this指针进行调用的。他是编译器自动完成的
this存在哪里?-- 栈,因为他是隐含形参 / vs下面是通过ecx寄存器
this指针不可修改,不可赋值,只能在成员函数内部调用
this指针本质上是“成员函数”的形参,不储存在对象中,随调随用,不需要用户进行操作
6 天选之子
6个天选之子我们说四个:构造、析构、赋值重载、拷贝
我们不写任何函数时,此4个函数都会自动生成
6.1 构造函数
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任
务并不是开空间创建对象,而是初始化对象。
- 函数名与类名相同。
- 无返回值。
- 对象实例化时编译器自动调用对应的构造函数。
- 构造函数可以重载。
class Date
{
public:
// 1.无参构造函数
Date()
{}
// 2.带参构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year=1;
int _month=1;
int _day=1;//缺省参数
};
void TestDate()
{
Date d1; // 调用无参构造函数
Date d2(2015, 1, 1); // 调用带参的构造函数
//Date d1();//会造成干扰,使编译器不清楚是构造函数还是函数声明
// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
// 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
// warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)
//Date d3();//error:无参函数不能使用后面的括号
}
int main()
{
Date d1;
d1.Print();
Date d2(2015, 1, 1);
d2.Print();
return 0;
}
- 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦
用户显式定义编译器将不再生成。
class Date
{
public:
/*
// 如果用户显式定义了构造函数,编译器将不再生成
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
*/
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
// 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数
// 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成
// 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
Date d1;
return 0;
}
- 默认随机值
注意:C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在
类中声明时可以给默认值。
- 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为
是默认构造函数。
class Datee
{
public:
//Datee()//无参构造
//{
// _year = 1900;
// _month = 1;
// _day = 1;
//}
Datee(int year = 1900, int month = 1, int day = 1)//全缺省构造,本质上和上面的是一个东西
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Datee d1;//如果不屏蔽一个,会有多个构造函数造成冲突
//Datee d2(2002, 8, 16);
d1.Print();
//d2.Print();
return 0;
}
我们在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。
构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始
化一次,而构造函数体内可以多次赋值。
所以我们使用初始化列表来处理
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟
一个放在括号中的初始值或表达式
1、哪个对象调用构造件数,初始化列表是它所有成员变量定义的位置
2、不管是否显示在初始化列表写,那么编译器每个变量都会初始化列表定义初始化。
- 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
- 类中包含以下成员,必须放在初始化列表位置进行初始化:
引用成员变量
const成员变量
自定义类型成员(且该类没有默认构造函数时) - 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,
一定会先使用初始化列表初始化。 - 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后
次序无关
6.2析构函数
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由
编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
析构函数内置类型成员不处理,自定义类型成员调用他的析构函数
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值类型。
- ==一个类只能有一个析构函数。==若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
- 对象生命周期结束时,C++编译系统系统自动调用
- 创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数
- 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数;有资源申请时,一定要写,否则会造成资源泄漏
6.3拷贝构造
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存
在的类类型对象创建新对象时由编译器自动调用。
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,
因为会引发无穷递归调用。
3.拷贝构造对内置类型进行浅拷贝,对自定义类型会调用它的拷贝构造
6.4 复制重载
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
- 赋值运算符重载格式
参数类型:const T&,传递引用可以提高传参效率
返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
检测是否自己给自己赋值
返回*this :要复合连续赋值的含义
- 赋值运算符只能重载成类的成员函数不能重载成全局函数,因为没有了this指针
- 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。
注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符
重载完成赋值。
6.5 const成员
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数
隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
const修饰是为了防止可能出显得权限放大、不能对类的任何成员进行修改等问题
const 修饰 *this
this的类型变成 const A *
内部不改变成员变量的成员函数
最好加上const,const对象和普通对象都可以调用
7 explicit
构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值
的构造函数,还具有类型转换的作用
因为explicit修饰构造函数,禁止了单参构造函数类型转换的作用
8static
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;
用static修饰的成员函数,称之为静态成员函数。
静态成员变量一定要在类外进行初始化;
- 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
- 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
- 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制
9 友元函数
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在
类的内部声明,声明时需要加friend关键字。
- 友元函数可访问类的私有和保护成员,但不是类的成员函数
- 友元函数不能用const修饰
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用原理相同
- 友元关系是单向的,不具有交换性。
- 友元关系不能传递
- 友元关系不能继承
9.1内部类
如果一个类定义在另一个类的内部,这个内部类就叫做内部类。
内部类是一个独立的类,
它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越
的访问权限。
注意:内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
- 内部类可以定义在外部类的public、protected、private都是可以的。
- 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
10 匿名对象和编译器优化
匿名对象的特点不用取名字,但是他的生命周期只有这一行
编译器优化
在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝,这个在一些场景下还
是非常有用的。