[TOC]
1-内存分配模型
2-引用
2.1-引用的基本使用
作用:给变量起别名
语法:数据类型 &别名 = 原名;
int main() {
// 数据类型 &别名 = 原名;
int a = 10;
//创建引用
int& b = a;
printf("a=%d,b=%d\n",a, b);
b = 999;
printf("a=%d,b=%d\n", a, b);
system("pause");
return 0;
}2.2-引用注意事项
- 引用必须初始化
- 引用初始化后,不可改变
int main()
{
int a = 10;
int b = 20;
//int &c;//ERROR!!必须初始化
int &c = a;
c = b;//这里是赋值操作,并不是更改引用
}2.3-引用做函数参数
作用:函数传参时,可以利用引用的技术让形参修饰实参
优点:简化指针修改实参
//值传递
void swap1(int a, int b) {
int temp = a;
a = b;
b = temp;
cout << "01,a=" << a << endl;
cout << "01,b=" << b << endl;
}
//指针传递
void swap2(int *a,int *b) {
int* t = a;
a = b;
b = t;
cout << "02,a=" << *a << endl;
cout << "02,b=" << *b << endl;
}
//引用传递
void swap3(int &a,int &b) {
int t = a;
a = b;
b = t;
cout << "03,a=" << a << endl;
cout << "03,b=" << b << endl;
}
int main() {
int a = 0;
int b = 9;
swap1(a,b);
cout << "main:a=" << a << endl;
cout << "main:b=" << b << endl;
swap2(&a,&b);
cout << "main:a=" << a << endl;
cout << "main:b=" << b << endl;
swap3(a,b);
cout << "main:a=" << a << endl;
cout << "main:b=" << b << endl;
system("pause");
return 0;
}输出的结果为:
01,a=9 01,b=0 main:a=0 main:b=9 02,a=9 02,b=0 main:a=0 main:b=9 03,a=9 03,b=0 main:a=9 main:b=0 请按任意键继续…
2.4-引用做函数返回值
注意:不要返回局部变量引用
用法:函数调用做左值
//返回局部变量
int& test01(int n) {
int a = n; //存放在 栈区
return a;
}
int& test02(){
static int a = 666;/*静态变量,放在全局区,在程序结束后释放*/
return a;
}
int a = 0;
int b = 9;
int& c = test01(b);
printf("%d\n", c);
printf("%d\n", c);
int& d = test02();
printf("%d\n", d);
printf("%d\n", d);
test02() = 1000;//对a赋值为1000
printf("%d\n", d);
printf("%d\n", d);
system("pause");
return 0;
}2.5-引用的本质
本质:在C++内部实现一个指针常量
//发现是引用,转换为int* const ref = &a;
void func(int &ref){
ref = 100;
}
int main()
{
int a = 10;
//自动转换为int* const ref = &a;
int &ref = a;
ref = 20;
cout << "a:" << a << endl;
cout<< "ref:" <<ref<< endl;
func(a);
return 0;
}3-函数提高
3.1-函数默认参数
函数的形参列表中是可以有默认值的
语法:==返回值类型 函数名(参数=默认值){}==
int func(int a, int b = 10, int c = 10){
return a+b+c;
}
//1.默认的必须是从右到左连续的,一个带默认值,右边全都得带默认值
//2.声明和实现只能有一个有默认参数
int func(int a=2,int b = 9);
int func(int a=2, int b = 9){return 0;}
//报错:func2:重定义默认参数3.2-函数占位参数
C++中函数的形参列表可以有占位参数,用作占位,调用时必须填补该位置
语法:返回值类型 函数名(数据类型){}
现阶段意义不大,不过以后会用到该技术
void func(int a, int,int c) {
cout << "this is a 占位函数" << endl;
}
//占位参数可以有默认值
void fun2(int a, int = 996, int c = 10) {
cout << "this is a 占位函数可以有 默认值" << endl;
}
int main()
{
func(0, 1, 2);
fun2(10);
return 0;
}3.3-函数重载
3.3.1概述
作用:函数名可以相同,提高复用性
函数重载满足的条件:
-
同一个作用域下
-
函数名相同
-
参数不同(类型 / **个数 ** / 顺序)
示例:都在全局作用域,函数名为func,参数不同
void func(int a) { cout << "func的调用\t" << "int a" <<endl; } void func(int a, int b) { cout << "func的调用\t" << "int a, int b" << endl; } void func(int a, double b) { cout << "func的调用\t" << "int a, double b" <<endl; } void func(double a, int b) { cout << "func的调用\t" << "double a, int b" << endl; } int main() { func(1); func(1, 2); func(1, 1.1); func(0.1, 1); return 0; }
注意:函数的返回值不可以作为函数重载的条件
int func(int a){
return a;
}
//报错3.3.2注意事项
- 引用
void func(int &a) {//int &a=100;不合法
cout << "func的调用\t" << "int &a" <<endl;
}
void func(const int& a) {const int &a=100;合法
cout << "func的调用\t" << "const int& a" << endl;
}//两个函数属于类型不同
int main()
{
int b = 10;
func(b);
const int c = 100;
func(c);
return 0;
}- 默认参数
void func(int a,int b=99) {
cout << "func的调用\t" << "int a,int b=99" <<endl;
}
void func(int a) {
cout << "func的调用\t" << "int a" << endl;
}
//编译器蒙圈了
//func(10);//可以调用两个函数4-类和对象
C++面向对象的三大特性为:封装、继承、多态 C++认为万物皆有对象,对象上有其属性和行为 例如 人作为对象,有姓名、身高体重…行为有吃喝玩乐 神作为对象,有姓名,能力…行为有赐福、保护 具有相同性质的对象,我们可以抽象为类,人属于人类,神属于神 类
4.1-封装
4.1.1-封装的意义
封装是C++面向对象的三大特性之一 封装的意义:
- 将属性和行为作为一个整体,表现生活中的事物
- 将属性和行为加以权限控制
成员
示例1.1:创建一个⚪类,输出它的周长
const double PI = 3.141593;
class circle {
//访问权限
public://公共权限
//属性
int m_r;
//行为
double calC() {//求周长
return 2 * PI * m_r;
}
};
int main()
{
circle circle1;
circle1.m_r = 10;
cout << "周长为:" << circle1.calC() << endl;
return 0;
}示例1.2:学生类,属性:姓名、学号,可以赋值并打印学号姓名
class Student
{
public:
string name;
int ID;
void set(){
cout << "name:";
cin >> name;
cout << "ID:";
cin >> ID;
}
void show() {
cout << "name:" << name << endl << "ID:" << ID << endl;
}
};
int main()
{
Student a;
a.set();
a.show();
return 0;
}访问权限
三种权限:
- 公共权限 public
- 保护权限 protected 类外不可访问
- 私有权限 private 类外不可访问
class library
{
public:
string name;
void func() {
name = "BJTU_Library";
book = "Nature";//类内可访问
password = 88888888;//类内可访问
}
protected:
string book;
private:
int password;
};
int main()
{
library BJTL;
BJTL.name = "wqer";
//BJTL.book = "习近平谈治国理政";//类外不可访问
//BJTL.password=188;//类外不可访问
return 0;
}4.1.2-struct和class的区别
struct和class的 唯一区别 是: 默认的访问权限不同;
区别:
- struct默认为公共
- class默认为私有
懒得写了4.1.3-成员属性设置为私有
优点:
- 可以自己控制读写权限
- 对于写的权限,我们可以检测数据的有效性
class person {
public:
void set_name(string NAME) {
name = NAME;
}
void get_name() {
cout << name << endl;
}
int get_age() {
return age ;
}
void set_password(int a) {
password = a;
}
private:
string name;//读写
int age = 18;//只读
int password;//只写
};
int main()
{
person p;
p.set_name("cjj");
p.set_password(123456);
cout << p.get_age() << endl;
p.get_name();
}4.2-对象的初始化和清理
4.2.1构造函数和析构函数
-
一个对象或对象没有初始状态,对其使用的后果是未知的;
-
使用完一个对象或变量,没有及时清理,会造成安全问题
编译器会自动提供构造和析构函数,但是全部为空实现
-
构造函数:创建对象时对成员赋值,由编译器自动调用,无需手动调用
-
析构函数:对象销毁前系统自动调用,执行一些清理工作
构造函数语法:类名(){}
- 构造函数没有返回值也不写void
- 函数名称与类名相同
- 可以有参数,一次可以发送重载
- 程序在调用对象时会自动调用构造一次,无须手动调用,仅调用一次
析构函数语法**:~类名(){}
- 析构函数没有返回值也不写void
- 函数名称与类名相同,但在之前加上~
- 析构函数不可以带参数,因此无法发生重载
- 程序在销毁对象时会自动调用构造一次,无须手动调用,仅调用一次
class person {
public:
person() {
cout << "构造函数的调用" << endl;
}
~person() {
cout << "析构函数的调用" << endl;
}
};
int main()
{
person p;
}
/*输出为:
构造函数的调用
析构函数的调用*/小思考:
为什么没有析构函数的调用呢?
class person {
public:
person() {
cout << "构造函数的调用" << endl;
}
~person() {
cout << "析构函数的调用" << endl;
}
};
int main()
{
person p;
system("pause");
}//还没有到要销毁的时候呢,关闭的一瞬间会有“析构函数的调用”4.2.2构造函数的分类和调用
分类方式:
- 按参数分:有参构造和无参构造
- 按类型分:普通构造和拷贝构造
调用方式:
- 括号法
- 显示法
- 隐式转换法
分类示例:
class person {
public:
int age;
//构造函数
person() {
cout << "普通无参 构造函数的调用" << endl;
}
person(int a) {
cout << "普通有参 构造函数的调用" << endl;
}
person(const person &p) {//写法为此
age = p.age;
cout << "拷贝 构造函数的调用" << endl;
}
~person() {
cout << "析构函数的调用" << endl;
}
};调用示例:
void test1() {
//1.括号法
person p1;//默认构造函数的调用
person p2(10);//普通有参函数
person p3(p2);//拷贝
/*person p1();//错误
编译器认为此为一个函数的声明,使用调用默认析构函数时不要加()*/
//2.显示法
cout << endl << "接下来是显示法" << endl ;
person p4;//默认
person p5 = person(10);//有参构造
person p6 = person(p5);//拷贝
cout << endl << "匿名对象:" << endl;
person(10);//当前行执行完后系统立即回收
//别用拷贝构造函数初始化匿名对象
//person(p6);//编译器认为是 person(p6)=person p6;
//3.隐式转换法
cout << endl << "隐式转换法:" << endl ;
person p7 = 10;//相当于person p4=person(10)
}4.2.3拷贝构造函数调用时机
- 利用原对象,初始化一个新对象
- 值传递给函数参数传值
- 以值方式返回局部变量
EXAMPLE:
//利用原对象,初始化一个新对象
void test01() {
person p1(18);
person p2(p1);
}
//值传递给函数参数传值
void func(person p) {
}
void test02() {
person p;
func(p);
}
//以值方式返回局部变量
person func2() {
person p1;
return p1;
}
void test03() {
person p = func2();
}4.2.4构造函数调用规则
默认情况下,C++编译器至少给类添加3个函数
- 默认构造(无参,函数体为空)
- 默认析构(无参,函数体为空)
- 默认拷贝,对属性值进行拷贝
构造函数调用规则:
- 用户定义有参,c++不在提供无参
- 用户定义拷贝,C++不再提供其他函数
void test01() {
//拷贝函数被注释
person p1;
person p2 = person(p1);
cout << "p2 age:" << p2.age << endl;//p2 age:18
}4.2.5深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
EXAMPLE:
class person {
public:
int age;
int* height;
//构造函数
person() {
cout << "普通无参 构造函数的调用" << endl;
age = 18;
}
person(int agee,int _height) {
age = agee;
height = new int(_height);//创建在堆区
cout << "普通有参 构造函数的调用" << endl;
}
//person(const person &p) {//写法为此
//age = p.age;
//cout << "拷贝 构造函数的调用" << endl;
//}
~person() {
if (height != NULL) {
delete height;
height = NULL;
}
cout << "析构函数的调用" << endl;
}
};
void test01() {
//拷贝函数被注释
person p1(18,178);
cout << "p1:" << p1.age <<"height:"<<*p1.height<< endl;
person p2(p1);
cout << "p2 age:" << p2.age << "height:" << *p1.height << endl;//p2 age:18
}//调试报错
浅拷贝的问题得由深拷贝解决;
class person{
public:
person(const person &p) {
/*your code*/
}
};4.2.6初始化列表
初始化属性
语法:构造函数():属性1(值),属性2(值)...{}
person(int a,int b):height(a),age(b) {
cout << "初始化 函数的调用" << endl;
}4.2.7类对象作为类成员
C++中类的成员可以是另一个类的对象,我们称之为对象成员
但是在创建与销毁时到底谁先谁后?
class Phone {
public:
Phone(string a) :model(a) {
cout << "phone 构造函数正在调用" << endl;
}
~Phone() {
cout << "phone 析构函数正在调用" << endl;
}
string model;
};
class Person {
public:
Person(string a, string b):name(a),myphone(b) {
cout << "person 构造函数正在调用" << endl;
}
~Person() {
cout << "person 析构函数正在调用" << endl;
}
string name;
Phone myphone;
};
void test01() {
Person p("张三","华为");
}输出为:
phone 构造函数正在调用 person 构造函数正在调用 person 析构函数正在调用 phone 析构函数正在调用
形象的比喻:先有零件后有装置,先拆毁装置,再销毁零件
4.2.8静态成员
静态成员就是在成员变量和成员函数加上关键字static,称为静态成员
静态成员分为:
- 静态成员变量
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
- 静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
静态成员变量
class Person {
public:
~Person() {
cout << "person 析构函数正在调用" << endl;
}
static int age;//类内实现
private:
static int height;//可有访问权限
};
//类外初始化
int Person::age = 18;
int Person::height = 50;
void test01(){
Person p;
Person p2;
cout <<"p:"<< p.age << endl;
cout <<"p2:"<< p2.age << endl;
p2.age = 99;
cout << "p:"<<p.age << endl;
cout << "p2"<<p2.age << endl;
}
void test02() {
//因为共享数据,所以有2种访问方式
Person p;
//1.通过对象访问
cout << p.age << endl;
//2.通过类名访问
cout << Person::age << endl;
}静态成员函数
class Person {
public:
static int age;//类内实现
static void func() {
age = 100;
//height = 100;//不可访问非静态成员变量,无法区分到底是哪个对象的属性
cout << "static void func 的调用" << endl;
}
private:
int height;
};
//类外初始化
int Person::age = 18;
void test01() {
Person p;
//通过对象访问
p.func();
cout << p.age << endl;
//通过类名访问
Person::func();
cout << p.age << endl;
}4.3-C++对象模型和this指针
4.3.1成员变量和成员函数分开存储
在c++中只有非静态成员变量才属于类的对象
class none {
};
class person {
int a;
int func() { return 0; }//函数和变量分开存储
static int b, c, d, e, f, g;//不属于类的对象
};
void test01() {
none p;
//空对象占用内存空间为:1
//C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占位
cout << sizeof(p) << endl;
}
void test02() {
person p;//size==4
cout << sizeof(p) << endl;
}4.3.2this指针
通过4.3.1我们知道在C++中成员变量和成员函数是分开存储的
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码那么问题是:这一块代码是如何区分那个对象调用自己的呢?
C++通过提供特殊的对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用即可
this指针的用途:
-
当形参和成员变量同名时,可用this指针来区分
-
在类的非静态成员函数中返回对象本身,可使用return *this
class person {
public:
int age;
person(int age) {
//age = age;同名无法赋值
this->age = age;
}
person& addage(person& p) {
this->age += p.age;
return *this;
}
};
void test01() {
person p2(10);person p1(10);
p2.addage(p1).addage(p1).addage(p1).addage(p1).addage(p1).addage(p1).addage(p1).addage(p1);
cout << p2.age;
}//输出90😄4.3.3空指针访问成员函数
c++中空指针可以调用成员函数,但是得注意有没有用到this
class Person {
public:
void show_class_name() {
cout << "this is class person" << endl;
}
void showage() {
if (this == NULL) { return; }//加上此条语句后不会报错
cout << "age=" << age << endl;
}
int age;
};
void test01() {
Person* p = NULL;
p->show_class_name();//没崩
p->showage();//引发异常
}4.3.4const修饰成员函数
常函数:
- 成员函数后加const后我们称之为常函数
- 常函数内不可以修改成员属性
- 成员属性加关键字mutable后,在常函数中依然可以修改
常对象:
- 声明对象前加const称该对象为常对象
- 常对象只能调用常函数
常函数
class Person {
public:
//this指针的本质是 指针常量 指向不可修改
void showperson()const//const修饰this指针的指向,使其指向的值无法修改
{
//age = 100;//相当于this->age=100;
height = 188;
}
int age;
mutable int height;
};常对象
class Person {
public:
int age;
mutable int height;
void func()const {
height = 188;
}
void func2() {
height = 188;
}
};
void test01() {
const Person p;//变为常对象
//p.age = 100;
p.height = 1000;
//p.func2();//常对象只能调用常函数
}4.4友元
你家有客厅(public),有卧室(private)
客人可以随便进入客厅,但是不能随便进入你的卧室,但是你也可以允许你的好基友进去
在程序中,有些私有属性 也想让类外的一些特殊的函数或者类进行访问,就需要用到友元(friend)的技术
友元的目的是让一些函数或类 访问另一个类中的私有成员
三种实现:
- 全局函数做友元
- 类做友元
- 成员函数做友元
4.4.1全局函数做友元
class home {
//关键标签
friend void goodgay(home& myhome);
public:
string setting_room;
home() {
setting_room = "客厅";
Bedroom = "卧室";
}
private:
string Bedroom;
};
void goodgay(home &myhome) {
cout << "goodgay 函数正在访问:" << myhome.setting_room << endl;
//未加friend标签无法访问
cout << "goodgay 函数正在访问:" << myhome.Bedroom << endl;
}
void test01() {
home home_;
goodgay(home_);
}4.4.2类做友元
函数类外实现:类名::函数名(){}
class home {
friend class goodgay;
public:
string setting_room;
home();
private:
string Bedroom;
};
class goodgay {
public:
string name;
home* room;
goodgay();
void visit();
};
//类外实现成员函数
home::home() {
setting_room = "客厅";
Bedroom = "卧室";
}
goodgay::goodgay(){
//创建建筑物对象
room = new home;
}
void goodgay::visit() {
cout << "goodgay 正在访问" << room->setting_room << endl;
//加入friend可访问卧室
cout << "goodgay 正在访问" << room->Bedroom << endl;
}
void test01() {
goodgay xiao_hao;
xiao_hao.visit();
}4.4.3成员函数做友元
class home ;//先声明 类,在后面实现
class GoodGay {
public:
string name;
home* room;
GoodGay();
void visit();
};
class home {
friend void GoodGay::visit();
public:
string setting_room;
home();
private:
string Bedroom;
};
//类外实现成员函数
home::home() {
setting_room = "客厅";
Bedroom = "卧室";
}
GoodGay::GoodGay(){
//创建建筑物对象
room = new home;
}
void GoodGay::visit() {
cout << "goodgay 正在访问" << room->setting_room << endl;
//加入friend可访问卧室
cout << "goodgay 正在访问" << room->Bedroom << endl;
}4.5运算符重载
4.5.1 ”+ - x /”
作用:实现两个自定义数据类型相加的运算
示例:加号重载
class score {
public:
int math;
int english;
int chinese;
score();
score(int m, int e, int c);
score operator+(score& p);
};
score::score() {
this->math = 60;
this->english = 60;
this->chinese = 60;
}
score::score(int m, int e, int c) {
this->math = m;
this->english = e;
this->chinese = c;
}
score score::operator+(score& p) {
score temp(0,0,0);
temp.chinese = this->chinese + p.chinese;
temp.english = this->english + p.english;
temp.math = this->math + p.math;
return temp;
}
void test01() {
score cjj(100, 100, 100);
score jcj(100, 100, 100);
score jjc(100, 100, 100);
cout << "三人总分为" << "\n语文:" << (cjj + jcj + jjc).chinese << "分\n数学"<< (cjj + jcj + jjc).math<<"分\n英语:"<< (cjj + jcj + jjc).english <<"分"<<endl;
}当然也可以这样实现(不过比较鸡肋)
class score {
public:
int math;
int english;
int chinese;
score();
score(int m, int e, int c);
//score operator+(score& p1, score& p2);
};
score operator+(score& p1, score& p2) {
score temp(0,0,0);
temp.chinese = p1.chinese + p2.chinese;
temp.english = p1.english + p2.english;
temp.math = p1.math + p2.math;
return temp;
}运算符重载 同时 发生函数重载
class score {
public:
int math;
int english;
int chinese;
score();
score(int m, int e, int c);
score operator+(int num);
};
score score::operator+(int num) {
score temp(this->chinese + num,this->math+num,this->english+num);
return temp;
}
score operator+(score& p1, score& p2) {
score temp(0,0,0);
temp.chinese = p1.chinese + p2.chinese;
temp.english = p1.english + p2.english;
temp.math = p1.math + p2.math;
return temp;
}
score::score() {
this->math = 60;
this->english = 60;
this->chinese = 60;
}
score::score(int m, int e, int c) {
this->math = m;
this->english = e;
this->chinese = c;
}
void test01() {
score cjj(100, 100, 100);
cout << "三人总分为" << "\n语文:" << (cjj + (100 + 100)).chinese << "分\n数学"<< (cjj + (100+ 100)).math<<"分\n英语:"<< (cjj + (100 + 100)).english <<"分"<<endl;
}4.5.2 ”<<“左移运算符重载
可以输出用户自定义的数据类型
class score {
public:
int math;
int english;
int chinese;
score(int m, int e, int c);
};
//只能通过全局函数实现
void operator<<(ostream &cout,score &p) {
//cout << p;
cout << "语文成绩:\t" << p.chinese << endl;
cout << "数学成绩:\t" << p.math << endl;
cout << "英语成绩:\t" << p.english << endl;
}
score::score(int m, int e, int c) {
this->math = m;
this->english = e;
this->chinese = c;
}
void test01() {
score cjj(100,100,100);
cout << cjj ;
}以上代码存在瑕疵,当想执行以下代码时报错:
void test01(){
score cjj(100,100,100),jjc(100,100,100);
cout << cjj << jjc << endl;
}该进如下:
//cout 可改成out、啥啥啥的
ostream& operator<<(ostream &cout,score &p) {
//cout << p;
cout << "语文成绩:\t" << p.chinese << endl;
cout << "数学成绩:\t" << p.math << endl;
cout << "英语成绩:\t" << p.english << endl;
return cout;
}//成功实现链式编程tip:可以通过添加友元实现输出私有属性
作业:实现右移运算符重载(cin>>)(●’◡’●)
4.5.3 ”++ —”
模拟实现整型变量
class inter {
friend ostream & operator<<(ostream &cout, inter num);
public:
inter() {
myint = 0;
}
//重载++i运算符
inter& operator++() {
++myint;
return *this;
//返回引用,使函数可以连续使用,且每次都是对同一个数进行操作
}
//重载i++运算符
//int 代表占位参数 重载函数
inter operator++(int) {
inter temp = *this;
myint++;
return temp;
}
private:
int myint;
};
ostream& operator<<(ostream& cout, inter num)
{
cout << num.myint;
return cout;
}
void test01() {
inter myint1,myint2;
cout << myint1++ << endl;
cout << myint1++ << endl;
//论返回引用的好处↓
cout << ++(++(++(++myint2))) << endl;
cout << myint2 << endl;
}4.5.4 ”=”
c++编译器至少给一个类4个函数:
- 默认构造函数
- 默认析构函数
- 默认拷贝构造函数
- 赋值运算符 operator=, 对属性值拷贝
如果类中有属性指向堆区,赋值时也会出现深浅拷贝的问题
class inter {
public:
inter(int num) {
this->num = new int(num);
}
~inter() {
if (num != NULL) {
delete num;
num = NULL;
}
}
inter& operator=(inter &myinter2) {
//判断是否有数据在堆区,如果有则释放
if (num != NULL) {
delete num;
num = NULL;
}
//深拷贝
this->num = new int(*myinter2.num);
return *this;
}
int* num;
};
void test01() {
inter int1(666),int2(888),int3(999);
int3 = int2 = int1;//赋值操作
cout << *int1.num << endl << *int2.num << endl << *int3.num << endl;
//原本崩溃,因为在析构函数中堆区的内存重复释放
}4.5.5 “关系”
自定义比较
EXAMPLE:
class score {
public:
int math;
int chinese;
int english;
score(int a, int b, int c) {
chinese = a;
math = b;
english = c;
}
bool operator==(score s) {
if (s.chinese == chinese && s.english == english && s.math == math) {
return true;
}
return false;
}
};
void test01() {
score p1(100, 100, 100);
score p2(100, 100, 100);
if (p1 == p2) {
cout << "same" << endl;
}
}4.5.6 仿函数
- 函数调用符()也可以重载
- 由于重载后方式非常像函数的调用,因此称之为仿函数
- 仿函数非常灵活,没有固定的写法
示例
class myprint {
public:
//重载函数调用运算符
void operator()(string test) {
cout << test << endl;
}
};
void test01() {
//匿名对象
myprint()("qwertyukjbvcxz");
myprint myprint;
myprint("hello world");
}4.6继承
继承是面向对象三大特性之一
有些类与类之间存在特殊的关系:

我们发现,定义这些类时,下级别成员除了拥有上一级的共性,还有自己的特性。
这个时候我们就可以考虑利用继承的技术减少重复代码
4.6.1继承的基本语法
我们看见很多网页中,都有公共的头部、底部、左侧列表,只有中心内容不同
接下来我们分别利用普通写法和继承写法来实现网页中内容,看看继承存在的意义以及好处
普通实现
class java {
public:
void header() {
cout << "公共头部" << endl;
}
void footer() {
cout << "公共页脚" << endl;
}
void left() {
cout << "java Python " << endl;
}
void content() {
cout << "java 视频" << endl;
}
};
class python {
public:
void header() {
cout << "公共头部" << endl;
}
void footer() {
cout << "公共页脚" << endl;
}
void left() {
cout << "java Python " << endl;
}
void content() {
cout << "python 视频" << endl;
}
};
void test01() {
cout << "java 视频界面如下:" << endl;
java ja;
ja.header();
ja.footer();
ja.content();
ja.left();
cout << "python 视频界面如下:" << endl;
python py;
py.header();
py.footer();
py.content();
py.left();
}重复代码太多了,一看就是垃圾
继承实现
语法:class 子类: 继承方式 父类{};
子类也称为派生类,父类也称为基类
class base {
public:
void header() {
cout << "公共头部" << endl;
}
void footer() {
cout << "公共页脚" << endl;
}
void left() {
cout << "java Python " << endl;
}
};
class java:public base {//继承基础页面
public:
void content() {
cout << "java 视频" << endl;
}
};
class python :public base {//继承基础页面
public:
void content() {
cout << "python 视频" << endl;
}
};
void test01() {
cout << "java 视频界面如下:" << endl;
java ja;
ja.header();
ja.footer();
ja.content();
ja.left();
cout << "python 视频界面如下:" << endl;
python py;
py.header();
py.footer();
py.content();
py.left();
}成功减少重复代码 ψ(`∇´)ψ
4.6.2继承方式
继承语法:class 子类: 继承方式 父类
继承方式有3种:
- 公共继承public
- 保护继承protected
- 私有继承private

代码验证一下:
class father {
public:
int a;
protected:
int b;
private:
int c;
};
class son1:public father {
public:
void func() {
a = 10;//父类中public子类可访问
b = 10;//父类中protect子类可访问
//c = 10;
}
};
class son2 :protected father {
public:
void func() {
a = 10;//父类中public子类可访问
b = 10;//父类中protect子类可访问
//c = 10;
}
};
class son3 :private father {
public:
void func() {
a = 10;//父类中public子类可访问
b = 10;//父类中protect子类可访问
//c = 10;
}
};
void test01() {
son1 s1;
s1.a = 100;
//s1.b = 100;//保护权限类外访问不到,所以还是保护权限;
son2 s2;//还是保护权限
//s2.a = 100;//变为保护权限,类外无法访问
//s2.b = 100;
son3 s3;
/*s3.a = 100;
s3.b = 100;
s3.c = 100;*/
}
子类的子类也满足此规则:
class grandson1 :public son1 {
public:
void func() {
a = 100;
b = 100;
//c = 100;
}
};
class grandson2 :public son2 {
public:
void func() {
a = 100;
b = 100;
//c = 100;
}
};
class grandson3 :public son3 {
public:
void func() {
//a = 100;
//b = 100;
//c = 100;
}
};4.6.3继承中的对象模型
4.6.4继承中构造和析构顺序
先有父后有子,销毁时后进先出
class father {
public:
father() {
cout << "father 构造" << endl;
}
~father() {
cout << "father 析构" << endl;
}
};
class son:public father {
public:
son() {
cout << "son 构造" << endl;
}
~son() {
cout << "son 析构" << endl;
}
};
void test01() {
son s;
}4.6.5继承同名函数处理方式
当子类和父类出现同名成员,如何通过子类对象,访问到子类或父类中的成员
- 访问子类同名成员:直接访问
- 访问父类同名成员,加作用域
代码奉上:
class father {
public:
int age = 40;
};
class son:public father {
public:
int age = 18;
};
void test01() {
son s;
cout << "son age:" << s.age << endl;
//父类函数被隐藏,需要加作用域,即使存在重载
cout << "father age:" << s.father::age << endl;
}4.6.6继承同名静态成员处理方式
与非静态成员一致
class father {
public:
static int age;
};
int father::age = 40;
class son:public father {
public:
static int age;
};
int son::age = 18;
void test01() {
//通过类对象访问
son s;
cout << "son age:" << s.age << endl;
cout << "father age:" << s.father::age << endl;
//通过类名访问
cout << "son age:" << son::age << endl;
cout << "father age:" << son::father::age << endl;
}看懂son::father::age了吗:
- 前一个::表示通过son访问父类信息
- 后一个::表示访问父类作用域下成员
4.6.7多继承语法
C++允许一个类继承多个类,也就是一个儿子认多个爹(吕布)
语法:class 子类: 继承方式 父类1, 继承方式 父类2......
多继承可能会引起父类中有同名成员,需要加作用域
==C++实际开发中不建议用多继承==
class father1 {
public:
int age = 100;
};
class father2 {
public:
int age = 40;
};
class son:public father1,public father2 {
public:
int age = 18;
};
void test01() {
//通过类对象访问
son s;
cout << "son age:" << s.age << endl;
cout << "father1 age:" << s.father1::age << endl;
cout << "father2 age:" << s.father2::age << endl;
}多继承中父类出现同名,子类使用时要加作用域
4.6.8菱形继承(钻石继承)
概念:
两个派生类继承同一个基类
又有同一个继承着两个派生类

继承问题:
- 🐏继承了动物数据,🐫继承了动物数据,当
草泥马使用数据时,产生二义性 - 草泥马继承动物数据继承了2份,但只需要继承一份即可
class animal {
public:
int age;
};
class sheep :public animal {};
class camel :public animal {};
class CNM :public sheep, public camel {};
void test01() {
CNM cnm;
//cnm.age = 28;
cnm.sheep::age = 18;
cnm.camel::age = 18;
//两个父类有相同数据
cout << "sheep age: " << cnm.sheep::age << endl;
cout << "camel age: " << cnm.camel::age << endl;
}
需要用虚继承解决问题:
class animal {
public:
int age;
};
class sheep :virtual public animal {};
class camel :virtual public animal {};
class CNM :public sheep, public camel {};
void test01() {
CNM cnm;
cnm.age = 28;
cout << "sheep age: " << cnm.sheep::age << endl;
cout << "camel age: " << cnm.camel::age << endl;
cout << "age:" << cnm.age << endl;
}
vbptr: 虚基类指针,virtual base pointer;它指向vbtable
4.7多态
4.7.1多态的基本概念
多态是C++面向对象三大特性之一
多态分为两类:
- 静态多态:函数重载 和 运算符重载 属于静态多态,复用函数名
- 动态多态:派生类 和 虚函数 运行时多态
静态多态和动态多态的区别:
- 静态多态的函数地址早绑定----编译阶段确定函数地址
- 动态多态的函数地址晚绑定----运行阶段确定函数地址
EXAMPLE
class animal {
public:
void speak() {
cout << "animal is speaking;" << endl;
}
};
class Cat:public animal {
public:
void speak() {
cout << "喵喵喵" << endl;
}
};
//地址早绑定 在编译时确定函数地址
void doSpeak(animal& ani) //父类的指针可以直接指向子类对象
{
ani.speak();
}
void test01() {
Cat cat;
doSpeak(cat);
}修改方式:
class animal {
public:
//虚函数
virtual void speak() {
cout << "animal is speaking;" << endl;
}
};
class Cat:public animal {
public:
void speak() {
cout << "喵喵喵" << endl;
}
};
class Dog :public animal{
public:
void speak() {
cout << "汪汪汪" << endl;
}
};
void doSpeak(animal& ani) //父类的指针可以直接指向子类对象
{
ani.speak();
}
void test01() {
Cat cat;
doSpeak(cat);
Dog dog;
doSpeak(dog);
animal ani;
doSpeak(ani);
}

总结:
多态满足条件:
- 有继承关系
- 子类重写父类中的虚函数
多态使用条件
- 父类指针或引用指向子类对象
重写:函数返回值类型 函数名 参数列表完全一致,内容可以不一样;
4.7.2多态案例一:计算器类
案例描述:分别用普通写法和多态技术设计实现两个操作数进行运算
多态优点:
- 代码组织机构清晰
- 可读性强
- 利于前期和后期拓展与维护
普通写法:
class Calculator {
public:
double getRes(string str) {
double ans = 0;
if (str == "+") { ans = a + b; }
else if(str=="-"){ ans = a - b; }
else if (str == "*") { ans = a * b; }
else if(str=="/"){ ans = a / b; }
else { cout << "暂时无法计算此类复杂运算" << endl; }
return ans;
}
double a, b;
};
void test01() {
Calculator cal;
cal.a = 24;
cal.b = 6;
cout << cal.a << "+" << cal.b << "=" << cal.getRes("+") << endl;
cout << cal.a << "-" << cal.b << "=" << cal.getRes("-") << endl;
cout << cal.a << "*" << cal.b << "=" << cal.getRes("*") << endl;
cout << cal.a << "/" << cal.b << "=" << cal.getRes("/") << endl;
}多态写法:
class Calculator {
public:
double a, b;
virtual double getRes() {
return 0;
}
};
class Add :public Calculator {
public:
double getRes() {
return a + b;
}
};
class Minus :public Calculator {
public:
double getRes() {
return a - b;
}
};
class Product :public Calculator {
public:
double getRes() {
return a * b;
}
};
class Divide :public Calculator {
public:
double getRes() {
if (b == 0) { cout << "被除数不能是0" << endl; }
return a / b;
}
};
void test01() {
Calculator* cal = new Add;
cal->a = 24; cal->b = 6;
cout << cal->a << " + " << cal->b << " = " << cal->getRes() << endl;
delete cal;
cal = new Minus;
cal->a = 24; cal->b = 6;
cout << cal->a << " - " << cal->b << " = " << cal->getRes() << endl;
delete cal;
cal = new Product;
cal->a = 24; cal->b = 6;
cout << cal->a << " * " << cal->b << " = " << cal->getRes() << endl;
delete cal;
cal = new Divide;
cal->a = 24; cal->b = 6;
cout << cal->a << " / " << cal->b << " = " << cal->getRes() << endl;
}calculator忘加virtual了,懵得一匹
4.7.3纯虚函数 和 抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类中重写的内容
所以干脆搞成纯虚函数算了~( ̄▽ ̄)~*
纯虚函数语法:virtual 返回值类型 函数名(XXX) = 0;
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
举个栗子
class Base {
public:
virtual void func() = 0;
};
class son :public Base{
public:
void func() {
cout << "good bye" << endl;
}
};
void test01() {
//无法实例化对象
//Base base;
//new Base;
Base* base = new son;
base->func();
}4.7.4多态案例二:制作饮品
制作饮品大致流程为:煮水 ~ 冲泡 ~ 倒入杯中 ~ 加入辅料

class Base {
public:
virtual void Boil() = 0;
virtual void Brew() = 0;
virtual void PourInCup() = 0;
virtual void PutSth() = 0;
void MakeDrink() {
Boil();
Brew();
PourInCup();
PutSth();
}
};
class Coffee :public Base{
public:
virtual void Boil() {
cout << "煮农夫山泉ing" << endl;
};
virtual void Brew() {
cout << "冲泡咖啡ing" << endl;
};
virtual void PourInCup() {
cout << "目前已倒入星巴克杯中" << endl;
};
virtual void PutSth() {
cout << "加入糖和牛奶" << endl;
};
};
class Tea : public Base{
public:
virtual void Boil() {
cout << "煮山泉ing" << endl;
};
virtual void Brew() {
cout << "冲泡龙井ing" << endl;
};
virtual void PourInCup() {
cout << "目前已倒入瓷杯中" << endl;
};
virtual void PutSth() {
cout << "加入枸杞" << endl;
};
};
void test01() {
cout << "煮咖啡" << endl;
Base* base = new Coffee;
base->MakeDrink();
delete base;
cout << "\n煮茶" << endl;
base = new Tea;
base->MakeDrink();
delete base;
}4.7.5虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
将父类中的析构函数改为虚析构或纯虚析构
共性
- 都可以解决父类指针释放子类对象
- 都需要具体函数实现
区别:
- 如果是纯虚析构,该类属于抽象类无法实例化对象
4.7.6多态案例三:电脑组装
5.文件操作
头文件<fstream>
操作文件:
- ofstream: 写
- ifstream: 读
- fstream: 读写
5.1文本文件
写文件步骤如下:
- 包含头文件:
#include<fstream> - 创建流对象:ofstream ofs;
- 打开文件:ofs.open(“文件路径”,打开方式);
- 写数据:ofs<<"";
- 关闭文件:ofs.close();
文件打开方式
| 打开方式 | 解释 |
|---|---|
| ios::in | 为读文件而打开文件 |
| ios::out | 为写文件而打开文件 |
| ios::ate | 初始位置:文件尾部 |
| ios::app | 追加方式写文件 |
| ios::trunc | 如果文件存在先删除,再创建 |
| ios::binary | 二进制方式打开 |
PS:文件打开方式可以配合使用,利用 | 操作符
#include<fstream>
using namespace std;
void test01() {
ofstream ofs;
ofs.open("text.txt", ios::out);
ofs << "1234567865433" << endl;
ofs.close();
}5.2二进制文件