C++核心编程
4.面向对象
C++面向对象的三大特性:封装,继承,多态
C++认为万事万物都皆为对象,对象上有其属性和行为
4.1封装
4.1.1封装的意义
封装是C++三大特性之一 封装的意义:
- 将属性和行为作为一个整体,表现生活中的事物
- 将属性和行为加以权限控制
封装的意义一:
在设计类的时候,属性和行为写在一起,表现事物
语法:class 类名{访问权限:属性/行为};
封装的意义二:
类在设计时,可以把属性和行为放在不同的权限下,加以控制。
访问权限有三种:
- public 公共权限 成员类内可以访问 类外可以访问
- protected 保护权限 成员类内可以访问 类外不可以访问
- private 私有权限 成员类内可以访问 类外不可以访问
4.1.2struct和class的区别
在C++中struct和class唯一的区别就在于默认的访问权限不同
区别:
- struct的默认权限为公共
- class的默认权限为私有
4.1.3成员属性设置为私有
优点:
- 将所有成员属性设置为私有,可以自己控制读写权限
- 对于写权限,我们可以检测数据的有效性
4.2对象的初始化和清理
C++中面向对象来源于生活,每个对象也都会有初始设置以及对象销毁前的清理数据的设置
4.2.1 构造函数和析构函数
- 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用
- 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作
构造函数语法:类名(){}
- 构造函数,没有返回值也不写void
- 函数名和类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象的时候会自动调用构造,无需手动调用,而且只会调用一次
析构函数语法:~类名(){}
-
析构函数,没有返回值也不写void
-
函数名称与类名相同,在名称前加符号~
-
析构函数不可以有参数,因此不可以发生重载
-
程序在对象销毁前会自动调用析构,无需手动调用,而且只会调用一次
构造和析构都是必须有的实现,如果我们自己不提供,编译器会提供一个空实现的构造和析构。
4.2.2 构造函数的分类及调用
两种分类方式:
- 按参数分为:有参构造和无参构造
- 按类型分为:普通构造和拷贝构造
class person
{
public:
//构造函数
person()
{
cout << "person的无参构造函数" << endl;
}
person(int a)
{
age = a;
cout << "person的有参构造函数" << endl;
}
//拷贝构造函数
person(const person &p)
{
age = p.age;
cout << "person的拷贝构造函数" << endl;
}
//析构函数
~person()
{
cout << "person的析构函数" << endl;
}
int age;
};
C++
三种调用方式:
- 括号法
//括号法
person p1;//默认构造函数
person p2(10);//有参构造函数
person p3(p1);//构造函数
person p4();//这种是错误的
C++
注意事项:
调用默认构造函数时候,不要加(),因为编译器会认为这是一个函数的声明,不会认为在创建对象。
- 显示法
//显示法
person p1;//默认构造函数
person p2 = person(10);//有参构造函数
person p3 = person(p2);//拷贝构造函数
person (10);//匿名对象
//特点:当前行执行结束后,系统会立即回收掉匿名对象
C++
注意事项: 匿名对象所在行执行结束后,系统会立即回收掉匿名对象。 不要利用拷贝构造函数初始化匿名对象。
person(p3);
编译器会认为person(p3)===person p3;对象声明
会报错显示重定义
- 隐式转换法
// 隐式转换法
person p1 = 10;//有参构造 相当于 person p1=person(10);
person p2 = p1;//拷贝构造
C++
4.2.3 拷贝构造函数调用时机
C++中拷贝构造函数调用时机通常有三种情况
- 使用一个已经创建完毕的对象来初始化一个新的对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
4.2.4 构造函数调用规则
默认情况下,C++编译器至少给一个类添加3个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
- 如果用户定义有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝构造
- 如果用户定义拷贝构造函数,C++不会再提供其他构造函数
4.2.5 深拷贝与浅拷贝
深浅拷贝是面试经典问题,也是常见的一个坑
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
4.2.6 初始化列表
作用:
C++提供了初始化列表语法,用来初始化属性
语法:
构造函数():属性1(值1),属性2(值2)...{}
4.2.7 类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称该成员为 对象成员
当其他类对象作为本类的成员,构造的时候先构造类对象,再构造自身
析构的顺序相反(先进后出)
4.2.8 静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
- 静态成员变量
所有对象共享一份数据
在编译阶段分配内存
类内声明,类外初始化
- 静态成员函数
所有对象共享一个函数
静态成员函数只能访问静态成员变量
(无法区分是哪个对象的)
静态成员变量 不属于某个对象上,所有对象都共享同一份数据
因此静态成员变量有两种访问方式
静态成员变量也有访问权限
静态成员函数也有访问权限
4.3 C++对象模型和this指针
4.3.1 成员变量和成员函数分开存储
在C++中,类内的成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上
空对象占用内存空间为: 1
C++编译器会给每个对象也分配一个字节空间,是为了区分空对象占内存的位置,每个空对象也应该有独一无二的内存地址
4.3.2 this指针概念
通过4.3.1我们知道C++中成员变量和成员函数是分开存储的
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
那么问题是:这一块代码是如何区分那个对象调用自己的那?
C++通过提供特殊的对象指针,this指针,解决上述问题
4.3.3空指针访问成员函数
C++中空指针也可以调用成员函数,但是要注意有没有用到this指针
如果用到this指针,需要加判断保证代码的健壮性
如果为NULL,直接returan
4.3.4const修饰成员函数
常函数:
-
成员函数后加const,我们称这个函数为常函数 -
常函数不可以修改成员属性 -
成语属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:
-
声明对象前加const称该对象为常对象 -
常对象只能调用常函数
4.4友元
友元让类外的函数或者类进行访问私有属性
友元的三种实现:
4.4.1
-
全局函数做友元
#include <iostream>
#include<string>
using namespace std;
class Building
{
friend void GoodGay(Building* building);
public:
string m_Sittingroom;
public:
Building()
{
m_Beedroom = "卧室";
m_Sittingroom = "客厅";
}
private:
string m_Beedroom;
};
void GoodGay(Building *building)
{
cout << "好基友正在访问:" <<building->m_Sittingroom<< endl;
cout << "好基友正在访问:" << building->m_Beedroom<< endl;
}
void test()
{
Building building;
GoodGay(&building);
}
int main()
{
test();
system("pause");
return 0;
}
C++
4.4.2
-
类做友元
#include <iostream>
#include<string>
using namespace std;
class Building
{
friend class GoodGay;
public:
Building()
{
Sitingroom = "客厅";
Bedroom = "卧室";
}
public:
string Sitingroom;
private:
string Bedroom;
};
class GoodGay
{
Building building;
public:
void visit();
};
void GoodGay::visit()
{
Building building;
cout << "好基友正在访问:" <<building.Sitingroom << endl;
cout << "好基友正在访问:" << building.Bedroom<< endl;
}
void test()
{
GoodGay gg;
gg.visit();
}
int main()
{
test();
system("pause");
return 0;
}
C++
4.4.3
-
成员函数做友元
#include <iostream>
#include<string>
using namespace std;
class Building
{
public:
Building()
{
Sitingroom = "客厅";
Bedroom = "卧室";
}
string Sitingroom;
string Bedroom;
};
class GoodGay
{
public:
Building* building;
public:
GoodGay()
{
building = new Building;
}
public:
void visit();
void visit2();
};
//类外实现函数
void GoodGay::visit()
{
cout << "好基友正在访问:" << building->Sitingroom<<endl;
}
void GoodGay::visit2()
{
cout << "好基友正在访问:" <<building->Bedroom<< endl;
}
void test()
{
GoodGay gg;
gg.visit();
gg.visit2();
}
int main()
{
test();
system("pause");
return 0;
}
C++
4.5运算符重载
运算符重载:对已有的运算符重新定义,赋予其另一种功能,以适应不同的数据类型
4.5.1加号运算符重载
实现两个自定义数据类型相加的运算
#include <iostream>
#include<string>
using namespace std;
class Person
{
public:
int m_A;
int m_B;
//成员函数重载
/* Person operator+ (Person& p)
{
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}*/
};
//全局函数重载
Person operator+(Person& a,Person&b)
{
Person temp;
temp.m_A = a.m_A + b.m_A;
temp.m_B = a.m_B + b.m_B;
return temp;
}
void Sit()
{
Person p1;
p1.m_A = 10;
p1.m_B = 20;
Person p2;
p2.m_A = 30;
p2.m_B = 40;
Person p3;
p3 = p1 + p2;
cout << "p3的m_A的值为:" << p3.m_A << endl;
cout << "p3的m_B的值为:" << p3.m_B << endl;
}
int main()
{
Sit();
system("pause");
return 0;
}
C++
总结1. 对于内置的数据类型的表达式的运算符是不可改变的
1+1=2
不可以改为1+1=0
总结2.不要滥用运算符重载
在上面的示例中Person operator+不可以改为两个对象数据相减