目录
1、基本概念
类和对象的基本概念
- “类”指的是一类事物,是一个抽象的概念
- “对象”指的是属于某个类的一个实体,是一个具体存在的事物
Tip:
类是一种“模板”,可以通过这种模板创建出不同的对象“实例”
对象“实例”是类“模板”的一个具体实现
一个类可以有很多对象,而一个对象必然属于某个类
2、C++中类的封装
类的组成:
- 成员变量
- C++中用于表示类属性的变量
- 成员函数
- C++中用于表示类行为的函数
- 在C++中可以给成员变量和成员函数定义访问级别
- public
- 成员变量和成员函数可以在类的内部和外界访问和调用
- private
- 成员变量和成员函数只能在类的内部被访问和调用
- public
类成员的作用域:
- 类成员的作用域都只在类的内部,外部无法直接访问
- 成员函数可以直接访问成员变量和调用其它成员函数
- 类的外部可以通过类变量访问public成员
- 类成员的作用域与访问级别没有关系
class和struct的区别:
struct在C语言中已经有了自己的含义,只能继续兼容。
在C++中提供了新的关键字class用于类定义,class和struct的用法是完全相同的,区别如下:
- 在用struct定义类时,所有成员的默认属性为public
- 在用class定义类时,所有成员的默认属性为private
3、构造函数
一般而言所有的对象都需要一个确定的初始状态,为此C++提供了一种特殊的成员函数–构造函数,构造函数名和类名相同,在定义时可以有参数,但是没有任何返回类型的声明!
构造函数的调用:
- 一般情况下C++编译器会自动调用构造函数
- 在一些情况下则需要手动调用构造函数
示例:
exp-1.cpp
#include <stdio.h>
class Test
{
private:
int i;
int j;
int k;
public:
Test(int v)
{
i = v;
j = v;
k = v;
}
void print()
{
printf("i = %d, j = %d, k = %d\n", i, j, k);
}
};
int main()
{
Test t1(4); //自动调用
Test t2 = 5; //自动调用
Test t3 = Test(6); //手动调用
t1.print();
t2.print();
t3.print();
Test tA[3] = {Test(1), Test(2), Test(3)}; //手动调用
for(int i=0; i<3; i++)
{
tA[i].print();
}
return 0;
}
思考:
上述的三种构造Test对象的方法等价么?
将程序做一些更改,验证一下这个问题!
#include <iostream>
using namespace std;
class Test
{
private:
int i;
public:
Test(int v)
{
i = v;
cout << "Test(int v)" << endl;
}
Test(const Test& obj)
{
this->i = obj.i;
cout << "Test(const Test& obj)" << endl;
}
~Test()
{
this->i = 0;
cout << "~Test()" << endl;
}
};
int main()
{
Test t1(4); //自动调用,直接初始化
Test t2 = 5; //自动调用,拷贝初始化
Test t3 = Test(6); //手动调用,直接初始化
cout << "Press any key to continue..." << endl;
cin.get();
return 0;
}
运行结果:
Tip:
从上述的结果中看出,三种不同的构造方式好像本质上是一样的,都调用了Test(int v)这个构造函数!但是事实上真的就是这样么?
其实不是的!在现代C++编译器中,三种不同的构造方式在本质上看起来是一样的,但实际上是编译器做了优化!进行了隐式类类型转换!第一种方式和第三种方式调用Test(int v)这个构造函数很容易理解,关键就在于第二种构造方式:
- 默认情况下,字面量5的类型为int,因此5无法直接用于初始化Test对象;
- 但是编译器在默认情况下可以自动调用构造函数;注意是编译器调用构造函数!!
- 于是编译器尝试调用Test(int)生成一个临时对象;
- 之后调用拷贝构造函数Test(const Test&)用临时对象对t2进行初始化。
所以,这才有了第二种构造方式,但是问题又来了,如果先调用Test(int)方式生成临时对象,然后再调用Test(const Test&)拷贝构造函数,那为什么没有输出拷贝构造函数中的内容?
那么再回过头想想,如果要通过第二种构造方式生成一个对象,都有哪些方法可行?
上述的两种方法中,上面详细描述的就是第一种方法,它会有临时对象生成,效率肯定不高;第二种方法一步到位,所以现代C++编译器使用的就是这种方法,相当于直接替换,因此也就没有拷贝构造函数的内容输出,因为压根没有使用到它!
那么何时会用到拷贝构造函数?
你这么写就要用了:
Test t4 = t2;
抑制编译器的隐式转换:
C++提供了explicit关键字用于阻止编译器对构造函数的调用尝试,抑制隐式类类型转换,它只对一个实参的构造函数有效,需要多个实参的构造函数本来就不能用于执行隐式转换,且只能在类的内部生命构造函数时使用explicit关键字,在类外部定义时不应该重复!这样一来,我们只能使用直接初始化!
将上述构造函数更改:
//相当于剥夺编译器调用此构造函数的权限
explicit Test(int v)
{
i = v;
cout << "Test(int v)" << endl;
}
这样,Test t2 = 5; 就无法编译通过了!
成员函数的重载:
- 类的成员函数和普通函数一样可以进行重载,并遵守相同的重载规则
两个特殊的构造函数:
- 无参构造函数
- 当类中没有定义构造函数时,编译器默认提供一个无参构造函数,并且其函数体为空
- 拷贝构造函数
- 当类中没有定义拷贝构造函数时,编译器默认提供一个拷贝构造函数,简单的进行成员变量的值复制
注意:
- 当类中没有定义任何一个构造函数,C++编译器会提供无参构造函数和拷贝构造函数
- 当类中定义了任意的非拷贝构造函数时,C++编译器不会提供无参构造函数
示例:
exp-2.cpp
#include <stdio.h>
class Test
{
public:
//默认构造函数
Test()
{
printf("Test()\n");
}
//拷贝构造函数
Test(const Test& obj)
{
printf("Test(const Test& obj)\n");
}
};
int main()
{
Test t1; //调用默认构造函数
Test t2 = t1; //调用拷贝构造函数
return 0;
}
Tip:
- 构造函数是C++中用于初始化对象状态的特殊函数
- 构造函数在对象创建时自动被调用
- 构造函数和普通成员函数都遵循重载规则
- 拷贝构造函数是对象正确初始化的重要保证
4、初始化列表
- C++中提供了初始化列表对成员变量进行初始化
- 语法规则
Constructor::Contructor() : m1(v1), m2(v1,v2), m3(v3)
{
// some other assignment operation
}
Tip:
- 成员变量的初始化顺序与声明顺序相关,与在初始化列表中的顺序无关
- 最好将构造函数初始值列表中的初始值顺序与实际初始化顺序保持一致,否则编译器会给出警告
- 初始化列表先于构造函数的函数体执行
5、类中的const成员
- 类中的const成员是肯定会被分配空间的
- 类中的const成员变量只是一个只读变量
Tip:
编译器在编译时无法直接得到const成员变量的初始值,因为这个初始值是在运行时才写入的,因此无法进入符号表成为真正意义上的常量。
6、初始化与赋值
- 初始化是用已存在的对象或值对正在创建的对象进行初值设置
- 赋值是用已存在的对象或值对已经存在的对象进行值设置
区别:
- 初始化:被初始化的对象正在创建
- 赋值:被赋值的对象已经存在
7、析构函数
- C++中的类可以定义一个特殊的成员函数清理对象
- 这个特殊的成员函数叫做析构函数
- 定义:~ClassName()
- 析构函数没有参数也没有任何返回类型的声明
- 析构函数在对象销毁时自动被调用
示例:
exp-3.cpp
#include <stdio.h>
class Test
{
private:
int mI;
public:
Test(int i) : mI(i)
{
printf("Test(), mI = %d\n", mI);
}
~Test()
{
printf("~Test(), mI = %d\n", mI);
}
};
void run()
{
Test t1(1);
Test t2(2);
}
int main()
{
run();
return 0;
}
Tip:
- 析构函数是C++中对象销毁时做清理工作的特殊函数
- 析构函数在对象销毁时自动被调用
- 析构函数是对象所使用的资源及时释放的保障
- 析构函数的调用秩序与构造函数相反
8、构造与析构的调用顺序
当类中有成员变量是其它类的对象时,首先调用成员变量的构造函数,调用顺序与声明顺序相同;之后再调用自身类的构造函数。
析构函数的调用秩序与对应的构造函数调用秩序相反。