一、由来
C语言是一种面向过程的语言,通过调用函数来解决问题,而C++在C语言的基础上增加了面向对象编程,将一件事情拆分成不同的对象,关注对象之间的交互。对象由变量和函数组成,概括了对象的信息属性及基本行为,而结构相似的一类对象,又被归纳为类。类有成员变量和成员函数,又被分别称为类的属性和类的方法。
二、struct的升级
1、struct在c++中升级成了类,同时它也兼容c中的用法
2、类型名不包括struct。
图一
三、类的定义
1、struct可以定义类,但我们更习惯使用class。class为定义类的关键字,{}中为类的主体,划分了一块类作用域,此外要注意类定义结束时后面分号不能省略。类体中内容称为类的成员:类中的变量称为类的属性或成员变量,类中的函数称为类的方法或者成员函数。函数可以在头文件的类里面声明,在源文件中指定类域定义。
2、为了便于区分成员变量,⼀般习惯在成员变量上加⼀个特殊标识,如成员变量前面或者后面加_或者m 开头,注意C++中这个并不是强制的,只是⼀些惯例,具体要求。
3、定义在类里面的成员函数默认为inline。
四、使用
4.1类实例化为对象
我们以图一为例讨论,类list_cpp只是一个类型,类比于这个struct list_c只是一种结构体种类,如果struct list_c st;才能得到一个名为st的结构体,而list_lt;就能得到lt的对象,经初始化它能有自己的数据。
4.1.1对象的储存方式
对象只储存自己的成员变量。因为每个对象的成员变量的值不同,但是调用的函数都是属于类的成员函数,所以为了防止代码的重复冗余,每个对象都只存储自己的成员变量,成员函数存放在了公共的代码段中。
4.1.2对象的大小
1.当类有成员变量时
对象里的成员变量遵循内存对齐规则存储的
2.当类没有成员变量时
这里的1字节是为了占位标识对象的存在
4.2对象调用类的函数
4.2.1访问限定符
出现上述两图情况是因为在cpp中class里面的成员默认是私有的(private),不能访问,而为了兼容c中struct的使用,struct里面的成员默认是共有的(public)
c++有三种访问限定符:public、protected、private。访问限定符的作用域从该限定符的位置开始一直到下一个限定符或者“ } ”结束。在public作用域里的成员可以在类外使用
4.2.2this指针
a、this指针的定义
当不同的对象调用公共的成员函数时,函数又是怎么区分哪个是哪个的对象并作出反应的呢,这靠this指针完成。
this指针是在编译器编译后,类的成员函数默认在形参第⼀个位置增加的⼀个当前类类型的指针,用来指向后期的对象。比如Date类的Init的真实原型为void Init(Date* const this, int year, int month, int day)
b、this指针的使用
1.this指针是编译器隐式在成员函数形参中添加的,不需要我们显示地添加
2.this指针可以在成员函数内部显示地使用
当形参与成员变量同名时,可以用以this指针区分
struct date {
public:
void Init(int year = 1, int month = 1, int day = 1) {
this->year = year;
this->month = month;
this->day = day;
}
private:
int year;
int month;
int day;
};
在非静态成员函数中返回对象本身
struct date {
public:
void Init(int year = 1, int month = 1, int day = 1) {
_year = year;
_month = month;
_day = day;
}
date& Add(int year, int month, int day) {
_year += year;
_month += month;
_day += day;
return *this;
}
private:
int _year;
int _month;
int _day;
};
五、默认成员函数
默认成员函数是编译器给类自动生成的成员函数,到c++11之前有6个,分别为构造函数、析构函数、拷贝构造函数、赋值重载函数、取地址重载函数、针对从const对象的取地址重载函数。
当默认生成的函数不满住我们的需求时,我们可以自己定义实现。
5.1构造函数
目的: 定义对象时自动调用,用于初始化对象
定义:1.函数名与类名相同
2.无返回值,定义构造函数时也不用写void
3.创建对象时自动调用,没有形参时不用写括号,便于与调用函数区分
4.可以重载,多次定义不同形参的构造函数
5.当用户自己显式定义构造函数时,编译器不再自动生成无参的默认构造函数
5.1.1 默认构造函数
1.不传参数就可调用的构造函数称为默认构造函数。避免调用歧义,默认构造函数只能存在一个。默认构造函数包括用户没有显式定义时,编译器自动生成无参的默认构造函数、用户自己显式定义的无参构造函数、全缺省构造函数。
2.默认构造函数会初始化自定义类型的成员变量(用struct/class/union自己定义的类型),不会处理内置类型的成员变量(基本类型,像int、char…),这取决于编译器。C++11中针对成员变量不初始化打了一个补丁,让成员变量在声明时可以给默认值。
5.1.2 初始化列表
目的:除构造函数、拷贝构造函数的函数体初始化外,还有初始化列表对成员变量初始化
定义:1.初始化列表的使用放式是以一个冒号开始,接着是⼀个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。每个成员变量在初始化列表中只能出现一次。
2.默认值与初始化列表:
类的声明中有默认值且在初始化列表里面初始化的,初始化的值由初始化列表决定;
类的声明中有默认值且初始化列表里面没有显式初始化的,c++11支持初始化列表用默认值来初始化该成员变量;
类的声明中没有默认值且初始化列表里面没有初始化的内置变量,初始化列表可能将其初始化为随机值,可能初始化为0,这取决于编译器;
类的声明中没有默认值且初始化列表里面没有(不论是否显式)初始化的自定义变量,初始化列表会调用自定义变量的默认构造函数(没有传入参数)初始化,如果没有默认构造函数,编译报错。
……
总之,无论是内置类型成员变量还是自定义类型成员变量,都满足,初始列表显式初始化>默认值>初始化列表未显示初始化
3.尽量使用初始化列表初始化成员变量。因为无论是否显示写初始化列表,每个构造函数都有初始化列表,如2所指,每个成员变量都会最终走初始化列表初始化。
4.必须在初始化列表初始化:
引用类型的成员变量。必须在声明时初始化。
const成员变量。必须在声明时初始化。
没有默认构造函数的内置类型。在初始化时需要传参。
5.初始化列表初始化的顺序是按照成员变量在类里面声明的顺序初始化。
5.1.3 explicit关键字
目的:构造函数前有关键字explicit,就不允许将内置类型变量转换为自定义类型变量。
一般c++支持将单个的内置类型变量通过单个参数构造函数或第一个参数无缺省值的半缺省构造函数构造一个临时的常性自定义类型变量,再拷贝构造给构造的自定义类型的变量,编译器遇见连续的构造+拷贝构造,会优化成构造。后来c++11之后也支持多个内置类型的转换。
5.2析构函数
目的:在对象销毁时自动调用,清理释放对象中的资源
定义:1.函数名为类名加字符~
2.无参,无返回值,也不用加void
3.一个类只有一个析构函数,如果用户没有显式定义,编译器自动生成默认的析构函数
4.自动生成的析构函数不对内置类型成员做处理,自定义类型成员会调用自己的析构函数。故此类中没有申请资源时,可以使用默认的析构函数,有申请资源时,需要自己显式写析构函数来释放资源
6.一个局部域的多个对象,c++规定后定义的先析构
5.3拷贝构造函数
目的:拷贝一个对象用来初始化另一个要创造的对象。自定义类型的对象进行传值传参和传值返回时使用的也是拷贝构造函数
定义:1.函数名为类型名
2.没有返回值void
3.拷贝构造函数的第⼀个参数必须是类类型对象的引用,拷贝构造函数也可以多个参数,但是第⼀个参数必须是类类型对象的引用,后面的参数必须有缺省值。拷贝构造函数也可以看作是构造函数的重载。
4.c++规定自定义类型的对象进行传值传参和传值返回时使用的是拷贝构造函数(这也说明了为什么拷贝构造函数的第一个参数是引用而不是传值传参。如果为传值传参,那么形参需要拷贝实参使用了拷贝构造函数,形成了无穷递归,而引用在语法逻辑上讲是和实参共用同一块空间不需要拷贝,避免了这个问题)
(两种调用拷贝构造函数的写法)
5.自动生成的拷贝构造对内置类型成员变量会完成值拷贝/浅拷贝(⼀个字节⼀个字节的拷贝),对自定义类型成员变量会调用他的拷贝构造。所以如果内置类型的成员有指向的资源,使用浅拷贝是不行的,需要用户自己实现深拷贝(对指向的资源也进行拷贝)
5.4赋值运算符重载函数
5.4.1运算符重载函数
目的:通过定义运算符重载函数给运算符新的含义,使其能作用于类类型的对象
定义:1.是具有特殊名字的函数,名字是由operator和后面要定义的运算符组成,
2.运算符不能使用不存在的运算符,例如“@”
3. .* :: ?: sizeof . 这五个运算符不能重载
4.重载运算符函数的参数个数和该运算符作用的运算对象数量一样多(二元运算符的左侧运算对象传给第⼀个参数,右侧运算对象传给第二个参数)。如果一个重载运算符函数是成员函数,则它的第一个运算对象默认传给隐式的this指针,显式的参数对象比运算对象少一个。
在类外的运算符重载函数如何访问私有成员变量:成员放公有 、类提供get/set函数 、友元函数 、重载为成员函数
5.运算符重载以后,其优先级和结合性等特性与对应的内置类型运算符保持一致。
例如:a、实现“+”:
b、实现“<<”:1.满足cout<<d的格式,需要流入符号重载函数的第一个形参传入cout,第二个形参是对象。但是如果这个流入符号重载函数作为成员函数,第一个形参会是编译器默认规定的this指针,所以为满足格式条件,需要将函数定义在类外。
2.传入的第二个形参对象在函数内部需要访问成员变量,需要将此符号重载函数作为友元函数或者类提供get/set函数 、私有的成员变量放公有
3.满足cout<<d1<<d2的连续流入的形式,需要一个返回值。cout<<d1成为返回值,与后面的“<<d2”重新构成流插入,说明返回值与此函数第一个形参为从类型。ostream是一个类,cout是这个类的对象,它们被库<ostream>包含,cout不能被拷贝,所以作为参数只能被引用为ostream& out,那么返回类型为ostream。
6.参数至少要有一个类类型的对象,不能通过运算符重载改变内置类型对象的含义,如: int operator+(int x, int y)
7.后置++运算符重载函数参数添加int,用于与前置++运算符重载函数作区分,他们的逻辑区别由函数里面的代码实现,int除作区分外无其他意义。
5.4.2赋值运算符重载函数
目的:用于完成两个已经存在的对象直接的拷贝赋值(与拷贝构造不同,拷贝构造用于⼀个对象拷贝初始化给另一个要创建的对象)
定义:1.赋值运算符重载函数是一个默认成员函数,是一个作为成员函数的运算符重载函数
2.参数建议写成 const当前类类型引用,避免拷贝,提高效率
3.尽量不出现*this和形参一样的情况,避免重复赋值
4.为了支持连续赋值,有返回值,返回值建议写成当前类类型引用,避免拷贝,提高效率
5.没有显式实现时,编译器会自动生成一个默认赋值运算符重载,对内置类型成员变量会完成值拷贝/浅拷贝,对自定义类型成员变量会调用他的赋值重载函数。内置类型成员指向资源时需要我们自己显式实现赋值运算符重载函数实现深拷贝。
5.5取地址运算符重载函数
5.5.1普通取地址运算符重载函数
编译器自动生成返回地址的取地址运算符重载函数,我们也可以自己定义
5.5.2针对const对象的取地址运算符重载函数
编译器自动生成针对const对象返回地址的取地址运算符重载函数,我们也可以自己定义
5.5.3修饰this指针的const
目的:被const修饰的对象调用成员函数时,成员函数传入的this指针指向的内容是没有被const修饰的,权限被放大,函数使用不了,需要在函数的括号后面加const,用来修饰this指针指向的内容,函数才可被调用。
图中后面创建的d2对象调用构造函数时还没有被const修饰,故可以调用函数括号后面没有const的构造函数,构造d2完成后才被const修饰。
没有被const修饰的对象也可以调用被const修饰this指向内容的对象,这是对象的使用权限缩小。在调用此类函数时不用写const。
六、static修饰类的成员
定义:1.静态成员属于类的成员,受访问限定符(public、protected、private)的限制
2.静态成员变量为所在类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区。
3.静态成员变量不能有默认值。因为有默认值会通过初始化列表初始化,但是静态成员变量不属于具体的对象,所以不能被初始化列表初始化
4.静态成员变量一定要在类外初始化。声明在类中时有static关键字,定义在类外无static关键字
5.静态成员函数没有this指针,可以访问静态成员变量,不能访问非静态成员变量。
七、友元
友元提供了⼀种突破类访问限定符封装的方式,友元分为:友元函数和友元类。在函数声明或者类声明的前面加friend,并且把这个友元声明放到⼀个类的里面。
友元提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
友元函数:
1.外部友元函数可访问类的私有和保护成员。
2.友元函数可以直接访问类中的静态成员,而不需要借助外部类的对象或类名,因为静态成员属于所有对象共享。
3.友元函数仅仅是⼀种声明,他不是类的成员函数,所以它可以在类定义的任何地方声明,不受类访问限定符限制,没有this指针,不被const修饰。
4.一个函数可以是多个类的友元函数。
友元类:
5.友元类中的成员函数都可以是另一个类的友元函数。
6.友元类的关系是单向的,不具有交换性,比如A类是B类的友元,但是B类不是A类的友元。
7.友元类关系不能传递,如果A是B的友元,B是C的友元,但是A不是C的友元。
8.友元类关系不能继承
八、内部类
定义:1.如果一个类定义在另一个类的内部,这个定义在内部的类就称为内部类。
2.内部类是一个独立的类,它不属于外部类,外部类的大小不包括内部类。
3.内部类默认是外部类的友元类,外部类不是内部类的友元。内部类不受外部类的访问限定符限制。
4.内部类可以直接访问外部类中的静态成员,而不需要借助外部类的对象或类名,因为静态成员属于所有对象共享。
5.内部类受外部类类域限制。
九、匿名对象
目的:只用一次某个类的成员函数使用。
定义:它的生命周期只在这一行,程序走到了下一行就会自动调用析构函数销毁。