面对对象是由c的面向过程所进化的,总体就三点
封装 继承 多态
一、理解
封装⼜叫隐藏实现,只公开代码单元的对外接口,⽽隐藏其具体实现。
封装思想的语法⽀持:
把 变量(属性) 和 函数(⾏为) 放在⼀起,作为⼀个整体
class封装了⼀个具体事物模型,通过这个模⼦我们可以⽣产多个具有相同特征的对象
二、成员访问控制
- public,类外可以访问。
- private,类外禁⽌访问。
- protected(继承)
类的访问控制并不是绝对的,因为引入了友元的概念。
友元可以访问类的私有成员,使用friend关键字。
友元函数
注意
- friend关键字只在声明处出现。
- 友元函数不是类的成员函数,不包含 this指针。
- 友元函数可以访问对象的任意私有成员。
友元类以及友元成员函数
- 友元类中的所有函数都可以访问本类型的私有成员。 友元关系不能被继承。
- 友元关系是单向的,类 A是类 B的朋友,但类 B不⼀定是类 A的朋友。
- 友元关系不具有传递性。类B是类 A的朋友,类 C是类 B的朋友,但类 C不⼀定是类 A的朋友
- 友元成员函数就是在A类里将B类的某个成员函数设置为友元
三、运算符重载
这里只说一下运算符重载比较重要的几个
- ⽐较运算符的结算结果⼀般是true 、 false 的 bool类型,当然也可以返回0 和 1 代表的C⻛格的bool类型。
- 转换运算符:当我们希望⾃定义类能够转换为其他数据类型时,也可以重载相关的转换运算符。例如,将 Box类型 的对象转换为int类型、bool类型或者其他⾃定义类型
1.转换运算符不能写返回值类型。
2.转换运算符不能有参数。
1. 左移运算符重载是为了让 cout对象输出⾃定义类型。
i. 由于 ostream类内部删除了拷⻉构造函数,不能以值⽅式传参、返回
ii. 由于内建的 operator<<重载函数⾮ const函数,所以 ostream参数不能⽤ const修饰。
2. 右移运算符重载是为了让 cin运算符能够向⾃定义类型写⼊。
i. 由于 istream类内部删除了拷⻉构造函数,不能以值⽅式传参、返回
赋值运算符注意⼏个问题:
1. 赋值操作和初始化操作的区别。
2. 浅赋值操作和深赋值操作(类似深拷⻉和浅拷⻉区别)。
3. 如果没有重载赋值运算符,进⾏对象赋值时,编译器会进⾏简单值拷⻉。
4. 为了实现 box1 =box2 = box3 连续赋值,所以赋值运算符必须返回对象引⽤。
四、 构造函数及析构函数
构造函数和析构函数,这两个函数将会被编译器⾃动调⽤,完成对象初始化和对象
清理⼯作。即使你不提供初始化操作和清理操作,编译器也会给你增加默认的操作,只是这个默认初始化操作不会做任何事,所以编写类就应该顺便提供初始化函数。
class Person{
public:
// 构造函数
Person(){
cout << "构造函数" << endl;
// 对成员变量进⾏初始赋值
m_name = new char[32];
memset(m_name, 0, 32);
strcpy(m_name, "Obama");
m_age = 33;
}
// 普通成员函数
void print(){
cout << "姓名:" << m_name << " 年龄:" << m_age << endl;
}
// 析构函数
~Person(){
cout << "析构函数" << endl;
if (m_name != NULL)
{
free(m_name);
}
}
private:
char *m_name;
int m_age;
};
void test(){
Person person;
person.print();
}
构造函数分类
因为构造函数支持重载
1. 普通构造函数,使⽤基本数据类型参数初始化对象。
2. 拷⻉构造函数,使⽤⼀个同类型的对象初始化本对象。
3. 如果提供了任何构造函数,默认构造函数需要⼿动添加。
class Person{
public:
// 普通构造函数
Person()
{
cout << "⽆参构造函数" << endl;
m_score = 0;
m_age = 0;
}
Person(int score){
cout << "⼀个参数构造函数" << endl;
m_score = score;
m_age = 0;
}
Person(int score, int age){
cout << "两个参数构造函数" << endl;
m_score = score;
m_age = age;
}
// 拷⻉构造函数
Person(const Person &person){
cout << "拷⻉构造函数" << endl;
m_score = person.m_score;
m_age = person.m_age;
}
void print(){
cout << m_age << " " << m_score << endl;
}
public:
int m_age;
int m_score;
};
// 1. ⽆参构造函数调⽤
void test01(){
// 正确写法
Person person;
// 错误写法
// Person person();
// person.print();
}
// 2. 调⽤有参构造函数
void test02(){
// 1. 括号法
// 调⽤1个参数构造函数
Person person1(10);
// 调⽤2个参数构造函数
Person person2(10, 20);
// 调⽤拷⻉构造函数
Person person3(person2);
// 2. =号法(对于⼀个参数构造函数、或者包含默认参数的构造函数 )
Person person4 = 10; // Person person4(10);
Person person5 = person4; // Person person5(person4);
}
// 3. 匿名对象
void test03()
{
// 创建匿名对象,声明周期仅限于本⾏
Person(10);
Person(10, 20);
// 错误写法(重定义)
Person person;
// Person(person);
// Person person = Person(person);
}
五、拷贝构造函数
1. 对象拷⻉(复制)语意
i. 什么都不做,默认的逐字节拷⻉。但当类包含对象成员时,不会进⾏逐字节拷⻉
ii. 禁⽌对象拷⻉,将拷⻉对象私有
2. 深拷⻉和浅拷⻉问题
3. 拷⻉够构造函数调⽤时机
i. 对象以值传递的⽅式传给函数参数
ii. 对象以值传递的⽅式从函数返回
iii. ⽤⼀个对象初始化另⼀个对象
4. NRV具名返回值优化
1.对象拷贝
默认拷贝
class Person{
public:
Person(int a, int b)
{
m_a = a;
m_b = b;
}
#if 0
Person(const Person &) = delete;
#else
private:
Person(const Person &) {}
#endif
public:
int m_a;
int m_b;
};
void test(){
Person person1(10, 20);
Person person2(30, 40);
// 默认逐字节拷⻉
person1 = person2;
}
调⽤对象成员拷⻉构造函数
class Weapon{
public:
Weapon(){
cout << "Weapon 构造函数" << endl;
}
Weapon(const Weapon &){
cout << "Weapon 拷⻉构造函数" << endl;
}
};
class Person{
public:
Person(int a){
m_a = a;
public:
int m_a;
Weapon m_weapon;
};
禁止对象拷贝
class Person{
#if 0
Person(const Person &) = delete;
#else
private:
Person(const Person &) {}
#endif
};
2.深拷贝和浅拷贝
默认构造函数进⾏的逐字节拷⻉Bitwise Copy ⼤部分情况下是⼯作良好,但类中包含了指向动态对象的指针,默认的拷⻉⾏为就会带来很严重的后果。
class Person{
public:
Person(const char *name){
m_name = new char[strlen(name) + 1];
strcpy(m_name, name);
}
~Person(){
if (m_name != NULL){
delete[] m_name;
}
}
public:
char *m_name;
};
void test(){
Person person("Obama");
Person copy_person(person);
}
程序运行失败,如果要解决问题需要添加一个拷贝构造函数
Person(const Person &person){
m_name = new char[strlen(person.m_name) + 1];
strcpy(m_name, person.m_name);
}
3拷贝构造调用时机
1. 对象以值传递的⽅式传给函数参数
2. 对象以值传递的⽅式从函数返回
3. ⽤⼀个对象初始化另⼀个对象
class Person {
public:
Person(){
cout << "no param contructor!" << endl;
mAge = 10;
}
Person(int age){
cout << "param constructor!" << endl;
mAge = age;
}
Person(const Person& person){
cout << "copy constructor!" << endl;
mAge = person.mAge;
}
~Person(){
cout << "destructor!" << endl;
}
public:
int mAge;
};
//1. 旧对象初始化新对象
void test01(){
Person p(10);
Person p1(p);
Person p3 = p;
}
//2. 传递的参数是普通对象,函数参数也是普通对象,传递将会调⽤拷⻉构造
void doBussiness(Person p) {}
void test02()
{
Person p(10);
doBussiness(p);
}
//3. 函数返回局部对象
Person MyBusiness(){
Person p(10);
cout << "局部p:" << (int*)&p << endl;
return p;
}
void test03(){
Person p = MyBusiness();
cout << "局部p:" << (int*)&p << endl;
}
具名返回值优化
class Teacher{
public:
Teacher() {}
Teacher(const Teacher &teacher){
cout << "拷⻉构造函数" << endl;
for (int i = 0; i < 100; ++i){
m_score[i] = teacher.m_score[i];
}
}
private:
int m_score[100];};
Teacher foo(){
Teacher teacher;
return teacher;
}
void test(){
for (int i = 0; i < 10000000; ++i){
Teacher t = foo();}
}
test函数调⽤了 foo函数 1000万次,每⼀次都会进⾏对象拷⻉,效率太低。为了解决这种问题,编译器进⾏了代码优化,使得函数返回局部对象时不再调⽤拷⻉构造,这种优化技术叫做NRV,NamedReturn Value
void foo(Teacher &_result){
// 调⽤ _result 的构造函数
_result.X::X();
// 对 _result 进⾏的操作
//....
return;
}
void test(){
for (int i = 0; i < 10000000; ++i){
Teacher t; // 只分配内存,不能调⽤构造函数初始化
foo(t);}
}
构造函数规则
**1.程序员⻆度**
-默认情况下,C++编译器会根据是否需要为类增加构函数 1.默认构造函数(⽆参,函数体为空)
2.默认析构函数(⽆参,函数体为空) 3.默认拷⻉构造函数,对类中⾮静态成员属性简单值拷⻉
- 如果⽤户定义拷⻉构造函数,C++不会再提供任何默认构造函数
-如果⽤户定义了普通构造(⾮拷⻉),C++不再提供默认⽆参构造,但是会提供默认拷⻉构造
**2.编译器⻆度**
- 编译器只有在需要的情况下才会为类增加默认构造函数,其中⼀个场景就是类内部包含对象成
员。
- 编译器只有在需要的情况下才会为类增加默认析构函数,其中⼀个场景就是类内部包含对象成
员,并且对象成员包含析构函数。
- 当⼀个类内部没有提供拷⻉构造函数时,编译器只有在需要的情况下才会增加拷⻉构造函数,如
果包含对象成员,那么会增加默认拷⻉构造函数。C++编译器默认对内建的数据类型进⾏逐字节
拷⻉。