1.内存分区模型
C++在执行程序时,将内存大方向划分为4个区域:
- 代码区:存放函数体的二进制代码,由操作系统进行管理
- 全局区:存放全局变量、静态变量以及常量
- 栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
- 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
不同区域存放的数据,赋予不同的周期,使编程更加灵活
1.1 程序运行前
在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域:
代码区:
- 存放CPU执行的机器指令
- 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
- 代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令
全局区:
- 全局变量和静态变量存放在此
- 全局区还包含了常量区,字符串常量和其他常量也存放在此
- 该区域的数据在程序结束后由操作系统释放
# include<iostream>
using namespace std;
// 全局变量
int g_a = 10;
int g_b = 10;
// const修饰的全局变量(全局常量)
const int c_g_a = 10;
const int c_g_b = 10;
int main()
{
// 普通局部变量
int a = 10;
int b = 10;
cout << "局部变量a的地址:" << (int)&a << endl;
cout << "局部变量b的地址:" << (int)&b << endl;
cout << "全局变量g_a的地址:" << (int)&g_a << endl;
cout << "全局变量g_b的地址:" << (int)&g_b << endl;
// 静态变量:在普通变量前加关键字static即为静态变量
static int s_a = 10;
static int s_b = 10;
cout << "静态变量s_a的地址:" << (int)&s_a << endl;
cout << "静态变量s_b的地址:" << (int)&s_b << endl;
// 常量:常量包括字符串常量和const修饰的变量
// const修饰的变量包括局部和全局
cout << "字符串常量的地址:" << (int)&"hello world" << endl;
// const修饰的局部变量(局部常量)
const int c_l_a = 10;
const int c_l_b = 10;
cout << "局部常量c_l_a的地址:" << (int)&c_l_a << endl;
cout << "局部常量c_l_b的地址:" << (int)&c_l_b << endl;
cout << "全局常量c_g_a的地址" << (int)&c_g_a << endl;
cout << "全局常量c_g_b的地址" << (int)&c_g_b << endl;
system("pause");
return 0;
}
局部变量、const修饰的局部变量(局部常量)不在全局区中
全局变量、静态变量、常量、字符串常量、const修饰的全局变量(全局常量)在全局区中
1.2 程序运行后
栈区:由编译器自动分配释放,存放函数的参数值(形参)、局部变量等
注:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
# include<iostream>
using namespace std;
int* func()
{
int a = 10; // 局部变量 存放在栈区,栈区的数据在函数执行完毕后自动释放
return &a; // 返回局部变量的地址
}
int main()
{
// 接收func函数的返回值
int* p = func();
cout << *p << endl; // 第一次可以打印正确的数字,是因为编译器做了保留
cout << "Hello World" << endl;
cout << *p << endl; // 第二次这个数据就不再保留了
system("pause");
return 0;
}
堆区:
由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
在C++中主要利用new在堆区中开辟内存
# include<iostream>
using namespace std;
int* func()
{
// 利用new关键字将局部变量开辟到堆区
int* p = new int(10); // 指针本身也是局部变量,放在栈区,但指针保存的数据放在堆区
return p;
}
int main()
{
int* p = func();
cout << *p << endl;
cout << "Hello World" << endl;
cout << *p << endl;
cout << *p << endl;
cout << *p << endl;
system("pause");
return 0;
}
1.3 new操作符
C++利用new操作符在堆区开辟区域
堆区开辟的数据,由程序员手动开辟,手动释放,释放用操作符delete
语法:new 数据类型
利用new创建的数据,会返回该数据对应的类型的指针
# include<iostream>
using namespace std;
// 1.new的基本语法
int* func()
{
int* p = new int(10);
return p;
}
void test01()
{
int* p = func();
cout << *p << endl;
cout << "Hello World" << endl;
cout << *p << endl;
cout << *p << endl;
// 释放堆区的数据 用关键字delete
delete p;
// cout << *p << endl; 内存已经被释放,再访问就是非法操作,会报错
}
// 2.在堆区利用new开辟数组
void test02()
{
int* arr = new int[10]; // 10代表的是数组中的元素个数
for (int i = 0; i < 10; i++)
{
arr[i] = i + 100;
}
for (int i = 0; i < 10; i++)
{
cout << arr[i] << endl;
}
// 释放堆区数组 需要加[]
delete[]arr;
}
int main()
{
test01();
test02();
system("pause");
return 0;
}
2.引用
2.1 引用的基本语法
作用:给变量起别名
语法 &别名 = 原名;
即通过“别名”和“原名”访问同一块内存
# include<iostream>
using namespace std;
int main()
{
int a = 10;
int& b = a;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
b = 100;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
system("pause");
return 0;
}
2.2 引用的注意事项
- 引用必须初始化
- 引用在初始化后,不可以改变
2.3 引用做函数参数
作用:函数传参时,可以利用引用让形参修饰实参
优点:可以简化指针修改实参
# include<iostream>
using namespace std;
// 交换函数
// 1.值传递
void myswap01(int a, int b)
{
int temp = a;
a = b;
b = temp;
}
// 2.地址传递
void myswap02(int* a, int* b)
{
int temp = *a;
*a = *b;
*b = temp;
}
// 3.引用传递
void myswap03(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
int main()
{
int x = 10;
int y = 20;
myswap01(x, y); // 值传递,形参不会修饰实参 此时,x = 10,y = 20
cout << "x = " << x << endl;
cout << "y = " << y << endl;
myswap02(&x, &y); // 地址传递,形参会修饰实参 此时,x = 20,y = 10
cout << "x = " << x << endl;
cout << "y = " << y << endl;
myswap03(x, y); // 引用传递,形参会修饰实参 此时,x = 10,y = 20
cout << "x = " << x << endl;
cout << "y = " << y << endl;
/*
引用传递,形参可以修饰实参的原因:
引用是变量的别名,别名和原名修饰的是同一块内存
即 myswap03 中的 a和b 与 x、y 修饰的是同一块内存
故通过引用传递,形参可以修饰实参
*/
system("pause");
return 0;
}
2.4 引用做函数返回值
用法:函数调用作为左值
注:不要返回局部变量引用
# include<iostream>
using namespace std;
// 1.不要返回局部变量引用
int& test01()
{
int a = 10; // 局部变量,存放在栈区,在函数执行完毕后由编译器自动释放
return a;
}
// 2.函数调用作为左值
int& test02()
{
static int a = 10; // 静态局部变量,存放在全局区,程序结束后由操作系统释放
return a;
}
int main()
{
// 不要返回局部变量引用
int ref1 = test01();
cout << "ref1 = " << ref1 << endl;
cout << "Hello World" << endl;
cout << "ref1 = " << ref1 << endl;
// 如果函数返回值是引用,那么这个函数调用可以作为左值
int ref2 = test02();
cout << "ref2 = " << ref2 << endl;
cout << "Hello World" << endl;
cout << "ref2 = " << ref2 << endl;
ref2 = 1000;
cout << "ref2 = " << ref2 << endl;
cout << "Hello World" << endl;
cout << "ref2 = " << ref2 << endl;
system("pause");
return 0;
}
2.5 引用的本质
引用的本质在C++内部实现是一个指针常量
(指针常量:指针的指向不可以修改,但指针指向的值可以修改)
# include<iostream>
using namespace std;
// 发现是引用,转换为 int* const ref = &a
void func(int& ref)
{
ref = 100; // ref是引用,转换为*ref = 100
}
int main()
{
int a = 10;
// 自动转换为int* const ref = &a;指针常量是指针指向不可以修改,也说明引用为什么不可更改
int& ref = a;
ref = 20; // 内部发现ref是引用,自动转换为*ref = 20
cout << "a = " << a << endl;
cout << "ref = " << ref << endl;
func(a);
system("pause");
return 0;
}
2.6 常量引用
作用:常量引用常用来修饰形参,防止误操作
在函数形参列表中,可以加const修饰形参,防止形参改变实参
# include<iostream>
using namespace std;
void showValue(const int& val)
{
// val = 1000; 会报错,const修饰的形参,不可修改
cout << "val = " << val << endl;
}
int main()
{
// int& ref = 10; 不可以,引用必须引一块合法的内存空间,10是一个常量
// 加上const之后,编译器将代码修改为 int temp = 10; const int& ref = temp;
// 加入const之后变为只读,不可修改
const int& ref = 10;
int a = 10;
showValue(a);
cout << "a = " << a << endl;
system("pause");
return 0;
}
3.函数提高
3.1 函数默认参数
在C++中,函数形参列表中的形参是可以有默认值的
语法:返回值类型 函数名(参数 = 默认值){}
- 如果自己传入了数据,就用自己的数据;如果没有,就用默认值
- 如果某个位置已经有了默认参数,那么从这个位置往后,从左往右都必须有默认值(从右往左赋默认参数)
- 如果函数声明有默认参数,则函数实现就不能有默认参数了,即声明和实现只能有一个有默认参数(二义性)
# include<iostream>
using namespace std;
// 有默认参数的函数
// 默认参数从右往左赋值:实参传入形参从左往右,如果实参个数少于形参个数,为防止出错,要保证右侧的形参有默认值
int func01(int a, int b = 20, int c = 30)
{
return a + b + c;
}
// 声明和实现只能有一个有默认参数
int func02(int a, int b = 10); // 声明已有默认参数
int func02(int a, int b) // 实现就不能有默认参数了
{
return a + b;
}
int main()
{
// 如果有自己传入的参数,用传入的参数,否则用默认参数
cout << func01(10) << endl;
cout << func01(10, 50) << endl;
cout << func02(10) << endl;
system("pause");
return 0;
}
3.2 函数的占位参数
C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置
语法:返回值类型 函数名 (数据类型){}
占位参数也可以有默认参数
# include<iostream>
using namespace std;
// 占位参数
void func03(int a, int)
{
cout << "This is a func" << endl;
}
// 占位参数可以有默认参数
void func04(int a, int = 10)
{
cout << "This is a func" << endl;
}
int main()
{
func03(10, 10);
func04(20);
system("pause");
return 0;
}
3.3 函数重载
3.3.1 基本语法
作用:函数名可以相同,提高复用性
函数重载满足条件:
- 同一个作用域下
- 函数名称相同
- 函数参数类型不同或个数不同或顺序不同
注:函数的返回值类型不可以作为函数重载的条件
# include<iostream>
using namespace std;
/*
函数重载的3个条件:
1.同一个作用域下(此处为同在全局作用域下)
2.函数名称相同
3.函数参数类型不同或个数不同或顺序不同
*/
void func05()
{
cout << "函数重载" << endl;
}
void func05(int a)
{
cout << "函数重载-参数个数不同" << endl;
}
void func05(double a)
{
cout << "函数重载-参数类型不同" << endl;
}
void func05(double a, int b)
{
cout << "函数重载-参数顺序不同_double_int" << endl;
}
void func05(int a, double b)
{
cout << "函数重载-参数顺序不同_int_double" << endl;
}
int main()
{
func05();
func05(10);
func05(3.14);
func05(3.14, 10);
func05(10, 3.14);
system("pause");
return 0;
}
3.3.2 注意事项
- 引用作为重载条件
- 函数重载碰到函数默认参数
# include<iostream>
using namespace std;
// 1.引用作为重载条件
void func1(int& a) // int& a = 10; // 不合法
{
cout << "func1(int& a)" << endl;
}
void func1(const int& a) // const int& a = 10; // 合法
{
cout << "func1(const int& a)" << endl;
}
// 2.函数重载碰到函数默认参数
void func2(int a, int b = 10)
{
cout << "func2(int a, int b = 10)" << endl;
}
void func2(int a)
{
cout << "func2(int a)" << endl;
}
int main()
{
int a = 10;
func1(a);
func1(10);
// func2(10); 函数重载碰到函数默认参数,可能会出现二义性
system("pause");
return 0;
}
4.类和对象
C++面向对象的三大特征:封装、继承、多态
C++认为万事万物皆为对象,对象上有其属性和行为
4.1 封装
4.1.1 封装的意义
- 将属性和行为作为一个整体,表现生活中的事物
- 将属性和行为加以权限控制
1.在设计类的时候,属性和行为写在一起,表现事物
语法:class 类名{访问权限:属性/行为};
<例1>设计一个圆类,求圆的周长
# include<iostream>
using namespace std;
const double PI = 3.14; // 圆周率
// 创建一个圆类
class Circle
{
// 访问权限
public: // 公共权限
// 属性
int m_r;
// 行为
double caculateZC() // 计算圆的周长
{
return 2 * PI * m_r;
}
};
int main()
{
// 通过圆类,创建一个具体的圆即对象(实例化)
Circle c1;
// 给对象赋值
c1.m_r = 10;
// 计算圆的周长
cout << "圆的周长:" << c1.caculateZC() << endl;
system("pause");
return 0;
}
<例2>设计一个学生类:属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号
# include<iostream>
using namespace std;
# include<string>
// 创建学生类
class Student
{
public:
// 属性
string m_Name;
int m_ID;
// 行为
// 显示姓名和学号
void showStudent()
{
cout << "姓名:" << m_Name << '\t';
cout << "学号:" << m_ID << endl;
}
// 给姓名赋值
void setName(string name)
{
m_Name = name;
}
// 给学号赋值
void setID(int id)
{
m_ID = id;
}
};
int main()
{
// 创建学生对象(实例化)
Student s1;
// 赋值
s1.setName("张三");
s1.setID(12345);
// 输出信息
s1.showStudent();
cout << endl;
system("pause");
return 0;
}
注:类中的属性和行为统一称为成员
属性又称为成员属性或成员变量
行为又称成员函数或成员方法
2.类在设计时,可以把属性和行为放在不同的权限下,加以控制
访问权限有三种:
- public 公共权限:成员类内可以访问,类外也可以访问
- protected 保护权限:成员类内可以访问,类外不可以访问(儿子可以访问父亲的保护内容)
- private 私有权限:成员类内可以访问,类外不可以访问(儿子不可以访问父亲的私有内容)
# include<iostream>
using namespace std;
# include<string>
// 创建一个人类
class Person
{
public: // 公共权限
string m_Name;
protected: // 保护权限
string m_car;
private: // 私有权限
int password;
public:
void func()
{
m_Name = "张三";
m_car = "拖拉机";
password = 123456;
}
};
int main()
{
// 实例化对象
Person p1;
p1.m_Name = "李四";
// p1.m_car = "奔驰";
// p1.password = 123;
system("pause");
return 0;
}
4.1.2 stuct和class区别
在C++中struct和class的唯一区别在于默认的访问权限不同:
- struct默认访问权限是公共
- class默认权限为私有
4.1.3 成员属性设置为私有
优点:
- 将所有成员属性设置为私有,可以自己控制读写权限
- 对于写权限,可以检测数据的有效性
# include<iostream>
using namespace std;
# include<string>
// 人类
class Person
{
public:
// 设置姓名
void setName(string name)
{
m_Name = name;
}
// 获取姓名
string getName()
{
return m_Name;
}
// 获取年龄
int getAge()
{
return m_Age;
}
// 设置偶像
void setIdol(string name)
{
m_Idol = name;
}
// 设置年龄(0-150)
void setAge(int age)
{
if (age < 0 || age>150)
{
cout << "年龄 " << age << " 输入有误,赋值失败" << endl;
return;
}
m_Age = age;
}
private:
string m_Name; // 姓名 可读可写
int m_Age = 18; // 年龄 只读 也可以写(0-150)
string m_Idol; // 偶像 只写
};
int main()
{
Person p;
// 姓名设置
p.setName("张三");
// 获取姓名
cout << "姓名:" << p.getName() << endl;
// 获取年龄
cout << "年龄:" << p.getAge() << endl;
// 设置年龄
p.setAge(160);
// 偶像设置
p.setIdol("小明");
system("pause");
return 0;
}
<案例>设计立方体类
设计立方体类(Cube)
求出立方体的面积和体积
分别用全局函数和成员函数判断两个立方体是否相等
# include<iostream>
using namespace std;
// 立方体类
class Cube
{
public:
// 设置与获取长宽高
void setL(int l)
{
m_L = l;
}
int getL()
{
return m_L;
}
void setW(int w)
{
m_W = w;
}
int getW()
{
return m_W;
}
void setH(int h)
{
m_H = h;
}
int getH()
{
return m_H;
}
// 计算面积与体积
int caculateS()
{
return 2 * m_L * m_W + 2 * m_L * m_H + 2 * m_W * m_H;
}
int caculateV()
{
return m_L * m_W * m_H;
}
// 利用成员函数判断两个立方体是否相等
bool isSameByClass(Cube& c)
{
if (m_L == c.getL() && m_W == c.getW() && m_H == c.getH())
{
return true;
}
else
return false;
}
private:
int m_L; // 长
int m_W; // 宽
int m_H; // 高
};
// 全局函数判断两个立方体是否相等
bool isSame(Cube& c1, Cube& c2)
{
if (c1.getL() == c2.getL () && c1.getW() == c2.getW() && c1.getH () == c2.getH())
{
return true;
}
return false;
}
int main()
{
// 实例化立方体对象
Cube c1;
c1.setL(10);
c1.setW(10);
c1.setH(10);
// 计算面积与体积
cout << "c1的面积:" << c1.caculateS() << endl;
cout << "c1的体积:" << c1.caculateV() << endl;
Cube c2;
c2.setL(10);
c2.setW(10);
c2.setH(11);
// 通过全局函数判断两个立方体是否相等
bool ret = isSame(c1, c2);
if (ret == 1)
{
cout << "全局函数判断:c1 = c2" << endl;
}
else
cout << "全局函数判断:c1 ≠ c2" << endl;
// 通过成员函数判断两个立方体是否相等
ret = c1.isSameByClass(c2);
if (ret == 1)
{
cout << "成员函数判断:c1 = c2" << endl;
}
else
cout << "成员函数判断:c1 ≠ c2" << endl;
system("pause");
return 0;
}
<案例>点和圆的关系
设计一个圆形类(Circle)和一个点类(Point),计算点和圆的关系
# include<iostream>
using namespace std;
// 点类
class Point
{
public: // 设置与获取点的位置
void setX(int x)
{
m_X = x;
}
int getX()
{
return m_X;
}
void setY(int y)
{
m_Y = y;
}
int getY()
{
return m_Y;
}
private: // 圆心
int m_X;
int m_Y;
};
// 圆类
class Circle
{
public:
// 设置与获取半径
void setR(int r)
{
m_R = r;
}
int getR()
{
return m_R;
}
// 设置与获取圆心
void setCenter(Point center)
{
m_Center = center;
}
Point getCenter()
{
return m_Center;
}
private:
int m_R; // 半径
Point m_Center; // 圆心
};
// 判断点和圆的关系
void isInCircle(Circle& c, Point& p)
{
// 两点之间的距离的平方:(x1 - x2)^2 + (y1 - y2)^2
int distance =
(p.getX() - c.getCenter().getX()) * (p.getX() - c.getCenter().getX()) +
(p.getY() - c.getCenter().getY()) * (p.getY() - c.getCenter().getY());
// 圆半径的平方
int cDistance = c.getR() * c.getR();
// 判断点和圆的关系
if (distance == cDistance)
{
cout << "点在圆上" << endl;
}
else if (distance > cDistance)
{
cout << "点在圆外" << endl;
}
else
{
cout << "点在圆内" << endl;
}
}
int main()
{
// 圆对象
Circle c;
c.setR(10); // 半径
// 圆心
Point center;
center.setX(10);
center.setY(0);
c.setCenter(center);
Point p;
p.setX(10);
p.setY(10);
// 判断关系
isInCircle(c, p);
system("pause");
return 0;
}
通常将类写到不同的头文件和源文件中
点类
头文件:point.h
#pragma once // 防止头文件重复包含
#include<iostream>
using namespace std;
// 点类
class Point
{
public: // 设置与获取点的位置
void setX(int x);
int getX();
void setY(int y);
int getY();
private: // 圆心
int m_X;
int m_Y;
};
源文件:point.cpp
#include"point.h"
void Point:: setX(int x)
{
m_X = x;
}
int Point:: getX()
{
return m_X;
}
void Point:: setY(int y)
{
m_Y = y;
}
int Point:: getY()
{
return m_Y;
}
圆类
头文件:cicle.h
#pragma once
#include<iostream>
#include"point.h"
using namespace std;
// 圆类
class Circle
{
public:
// 设置与获取半径
void setR(int r);
int getR();
// 设置与获取圆心
void setCenter(Point center);
Point getCenter();
private:
int m_R; // 半径
Point m_Center; // 圆心
};
源文件:circle.cpp
#include "circle.h"
void Circle:: setR(int r)
{
m_R = r;
}
int Circle:: getR()
{
return m_R;
}
// 设置与获取圆心
void Circle:: setCenter(Point center)
{
m_Center = center;
}
Point Circle:: getCenter()
{
return m_Center;
}
主文件:
# include<iostream>
using namespace std;
#include "circle.h"
#include "point.h"
// 判断点和圆的关系
void isInCircle(Circle& c, Point& p)
{
// 两点之间的距离的平方:(x1 - x2)^2 + (y1 - y2)^2
int distance =
(p.getX() - c.getCenter().getX()) * (p.getX() - c.getCenter().getX()) +
(p.getY() - c.getCenter().getY()) * (p.getY() - c.getCenter().getY());
// 圆半径的平方
int cDistance = c.getR() * c.getR();
// 判断点和圆的关系
if (distance == cDistance)
{
cout << "点在圆上" << endl;
}
else if (distance > cDistance)
{
cout << "点在圆外" << endl;
}
else
{
cout << "点在圆内" << endl;
}
}
int main()
{
// 圆对象
Circle c;
c.setR(10); // 半径
// 圆心
Point center;
center.setX(10);
center.setY(0);
c.setCenter(center);
Point p;
p.setX(10);
p.setY(10);
// 判断关系
isInCircle(c, p);
system("pause");
return 0;
}
4.2 对象的初始化和清理
C++每个对象都有初始设置以及对象销毁前的清理数据的设置
4.2.1 构造函数和析构函数
对象的初始化和清理是两个非常重要的安全问题:
- 一个对象或者变量没有初始状态,对其使用后果是未知
- 使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
C++使用构造函数和析构函数解决上述问题,这两个函数会被编译器自动调用,完成对象初始化和清理工作
对象的初始化和清理工作是编译器强制要求我们做的事情,因此,如果我们不提供构造和析构,编译器会提供
编译器提供的构造函数和析构函数是空实现
- 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数编译器自动调用,无需手动调用
- 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作
构造函数语法:类名(){}
- 构造函数,没有返回值也不写void
- 函数名与类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时会自动调用构造,无需手动调用,而且只会调用一次
析构函数语法:~类名(){}
- 析构函数,没有返回值也不写void
- 函数名与类名相同,在前面加上~
- 析构函数不可以有参数,因此不可以发生重载
- 程序在对象销毁前会自动调用析构,无需手动调用,而且只会调用一次
#include <iostream>
using namespace std;
class Person
{
public:
// 构造函数
Person()
{
cout << "Person 构造函数的调用" << endl;
}
// 析构函数
~Person()
{
cout << "Person 析构函数的调用" << endl;
}
};
void test01()
{
Person p; // 创建在栈上,函数调用完毕后释放
}
int main()
{
test01();
system("pause");
return 0;
}
4.2.2 构造函数的分类及调用
两种分类方式:
- 按参数分:有参构造和无参构造(默认构造)
- 按类型分:普通构造和拷贝构造
三种调用方式:
- 括号法
- 显示法
- 隐式转换法
#include<iostream>
using namespace std;
class Person02
{
// 分类
public:
// 无参构造函数
Person02()
{
cout << "Person02无参构造函数的调用" << endl;
}
// 有参构造函数
Person02(int a)
{
age = a;
cout << "Person02有参构造函数的调用" << endl;
}
// 拷贝构造函数
Person02(const Person02& p)
{
age = p.age;
cout << "Person02拷贝构造函数的调用" << endl;
}
int age;
};
// 调用
void test02()
{
// 括号法
cout << "调用-括号法:" << endl;
Person02 p1;
Person02 p2(10);
Person02 p3(p2);
// 显示法
cout << "调用-显示法:" << endl;
Person02 p4;
Person02 p5 = Person02(10); // 显示法调用有参构造 Person02(10)为匿名对象,当前行执行结束后,系统会立即回收匿名对象
Person02 p6 = Person02(p5); // 显示法调用拷贝构造
// 隐式转换法
cout << "调用-隐式转换法:" << endl;
Person02 p7 = 10; // 隐式转换法调用有参构造 相当于显示法中的 Person02 p7 = Person02(10)
Person02 p8 = p7; // 隐式转换法调用拷贝构造
}
int main()
{
test02();
system("pause");
return 0;
}
注:
调用默认构造函数时不要加(),编译器会认为这是函数声明,不会认为这是在创建对象
不要利用拷贝构造函数初始化匿名对象,编译器会去掉(),认为这是一个对象声明
4.2.3 拷贝构造函数调用时机
通常有3种情况:
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
#include <iostream>
using namespace std;
class Person03
{
public:
Person03()
{
cout << "Person03默认构造函数的调用" << endl;
}
Person03(int a)
{
m_Age = a;
cout << "Person03有参构造函数的调用" << endl;
}
Person03(const Person03& p)
{
m_Age = p.m_Age;
cout << "Person03拷贝构造函数的调用" << endl;
}
~Person03()
{
cout << "Person03析构函数的调用" << endl;
}
int m_Age;
};
// 使用一个已经创建完毕的对象来初始化一个新对象
void test03()
{
Person03 p1(20);
Person03 p2(p1);
cout << "p2的年龄:" << p2.m_Age << endl;
}
// 值传递的方式给函数参数传值
void doWork01(Person03 p)
{
}
void test04()
{
Person03 p;
doWork01(p); // 实参传递给形参时会调用拷贝构造函数
}
// 以值方式返回局部对象
Person03 doWork02()
{
Person03 p1;
cout << (int*)&p1 << endl;
return p1;
}
void test05()
{
Person03 p = doWork02();
cout << (int*)&p << endl; // 这里把对象看作指针,因为通过p.调用对象中不同的函数及成员
}
int main()
{
cout << "使用一个已经创建完毕的对象来初始化一个新对象:" << endl;
test03();
cout << endl;
cout << "值传递的方式给函数参数传值:" << endl;
test04();
cout << endl;
cout << "以值方式返回局部对象:" << endl;
test05();
system("pause");
return 0;
}
4.2.4 构造函数调用规则
默认情况下,C++编译器至少给一个类添加3个函数:
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
- 如果用户定义有参构造函数,C++不再提供无参构造,但是会提供默认拷贝构造
- 如果用户定义拷贝构造函数,C++不会再提供其他构造函数
以下为有拷贝构造函数的代码:
// 创建一个类,C++编译器会给每个类都至少添加3个函数
#include <iostream>
using namespace std;
class Person
{
public:
// 1.默认构造(空实现)
Person()
{
cout << "Person的默认构造函数调用" << endl;
}
// 有参构造
Person(int age)
{
m_Age = age;
cout << "Person的有参构造函数调用" << endl;
}
// 2.析构函数(空实现)
~Person()
{
cout << "Person的析构函数调用" << endl;
}
// 3.拷贝构造(值拷贝)
Person(const Person& p)
{
m_Age = p.m_Age;
cout << "Person的拷贝构造函数调用" << endl;
}
int m_Age;
};
void test01()
{
Person p1;
p1.m_Age = 18; // 默认
Person p2(p1); // 拷贝
cout << "p2的年龄:" << p2.m_Age << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
以下为无拷贝构造函数的代码:
// 创建一个类,C++编译器会给每个类都至少添加3个函数
#include <iostream>
using namespace std;
class Person
{
public:
// 1.默认构造(空实现)
Person()
{
cout << "Person的默认构造函数调用" << endl;
}
// 有参构造
Person(int age)
{
m_Age = age;
cout << "Person的有参构造函数调用" << endl;
}
// 2.析构函数(空实现)
~Person()
{
cout << "Person的析构函数调用" << endl;
}
int m_Age;
};
void test01()
{
Person p1;
p1.m_Age = 18; // 默认
Person p2(p1);
cout << "p2的年龄:" << p2.m_Age << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
这时的拷贝构造函数为系统提供
以下为仅有有参构造和析构函数代码:
// 如果用户定义有参构造函数,C++不再提供无参构造,但是会提供默认拷贝构造
#include <iostream>
using namespace std;
class Person
{
public:
// 有参构造
Person(int age)
{
m_Age = age;
cout << "Person的有参构造函数调用" << endl;
}
// 2.析构函数(空实现)
~Person()
{
cout << "Person的析构函数调用" << endl;
}
int m_Age;
};
void test02()
{
Person p1(28); // 有参构造
Person p2(p1); // 拷贝构造
cout << "p2的年龄:" << p2.m_Age << endl;
}
int main()
{
test02();
system("pause");
return 0;
}
4.2.5 深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间
浅拷贝带来的问题:堆区的内存重复释放
解决方式:深拷贝
#include <iostream>
using namespace std;
class Person2
{
public:
Person2()
{
cout << "Person2默认构造函数的调用" << endl;
}
Person2(int age, int height)
{
m_Age = age;
m_Height = new int(height);
cout << "Person2有参构造函数的调用" << endl;
}
// 自己实现拷贝构造函数,来解决浅拷贝带来的问题
Person2(const Person2& p)
{
cout << "Person2拷贝构造函数的调用" << endl;
m_Age = p.m_Age;
// m_Height = p.m_Height; 编译器默认实现的是这行代码
// 深拷贝操作
m_Height = new int(*p.m_Height);
}
~Person2()
{
// 析构代码 将堆区开辟的数据做释放操作
if (m_Height != NULL)
{
delete m_Height;
m_Height = NULL;
}
cout << "Person2默认构造函数的调用" << endl;
}
int m_Age;
int* m_Height;
};
void test02()
{
Person2 p1(18, 160);
Person2 p2(p1);
cout << "p1的年龄:" << p1.m_Age << " p1的身高:" << *p1.m_Height << endl;
cout << "p2的年龄:" << p2.m_Age << " p2的身高:" << *p2.m_Height << endl;
}
int main()
{
test02();
system("pause");
return 0;
}
4.2.6 初始化列表
作用:C++提供了初始化列表语法,用来初始化属性
语法:构造函数():属性1(值1),属性2(值2),…{}
#include <iostream>
using namespace std;
class Person3
{
public:
// 初始化列表
Person3(int a, int b, int c):m_A(a),m_B(b), m_C(c)
{
}
int m_A;
int m_B;
int m_C;
};
void test03()
{
Person3 p(30, 20, 10);
cout << "m_A = " << p.m_A << endl;
cout << "m_B = " << p.m_B << endl;
cout << "m_C = " << p.m_C << endl;
}
int main()
{
test03();
system("pause");
return 0;
}
4.2.7 类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称该成员为对象成员
当其他类的对象作为本类的成员时,先构造其他类的对象,再构造本类的对象,析构的顺序与构造相反
#include <iostream>
using namespace std;
#include <string>
// 手机类
class Phone
{
public:
string m_PName; // 手机品牌名称
Phone(string pName)
{
m_PName = pName;
cout << "Phone构造函数的调用" << endl;
}
~Phone()
{
cout << "Phone析构函数的调用" << endl;
}
};
// 人类
class Person4
{
public:
string m_Name; // 姓名
Phone m_Phone; // 手机
Person4(string name, string pName) :m_Name(name), m_Phone(pName)
{
cout << "Person4构造函数调用" << endl;
}
~Person4()
{
cout << "Person4析构函数调用" << endl;
}
};
void test04()
{
Person4 p("张三", "苹果MAX");
cout << p.m_Name << "拿着:" << p.m_Phone.m_PName << endl;
}
int main()
{
test04();
system("pause");
return 0;
}
4.2.8 静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
静态成员变量
- 所有对象共享一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
静态成员变量,不属于某个对象,所有对象都共享一份数据
因此,静态成员变量有两种访问方式:
- 通过对象进行访问
- 通过类名进行访问
静态成员函数
- 所有对象共享一个函数
- 静态成员函数只能访问静态成员变量
静态成员函数访问方式与静态成员变量的相同:
- 通过对象进行访问
- 通过类名进行访问
静态成员变量示例:
#include <iostream>
using namespace std;
// 静态成员变量
class Person5
{
public:
static int m_A; // 类内声明
private:
static int m_B;
};
int Person5::m_A = 100; // 类外初始化
int Person5::m_B = 100; // 私有 类外不可访问
void test05()
{
Person5 p1;
p1.m_A = 100;
cout << p1.m_A << endl; // 通过对象访问静态成员变量
Person5 p2;
p2.m_A = 200; // 所有对象共享一份数据
cout << Person5::m_A << endl; // 通过类名访问静态成员变量
}
int main()
{
test05();
system("pause");
return 0;
}
静态成员函数示例:
#include <iostream>
using namespace std;
// 静态成员函数
class Person6
{
public:
static void func1()
{
m_A = 0; // 静态成员函数只能访问静态成员变量
cout << "static void func1的调用" << endl;
}
static int m_A;
private:
static void func2()
{
// 静态成员函数也是有访问权限的
}
};
int Person6::m_A = 100;
void test06()
{
// 通过对象进行访问
Person6 p1;
p1.func1();
// 通过类名进行访问
Person6::func1();
}
int main()
{
test06();
system("pause");
return 0;
}
4.3 C++对象模型和this指针
4.3.1 成员变量和成员函数分开存储
在C++种,类的成员函数和成员变量分开存储
只有非静态成员变量才属于类的对象上
C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
每个空对象也应该有一个独一无二的内存地址
#include <iostream>
using namespace std;
class Person01
{
};
void test01()
{
Person01 p1;
cout << "size of p1 = " << sizeof(p1) << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
加入整型非静态成员变量后:
#include <iostream>
using namespace std;
class Person01
{
public:
int m_A;
};
void test01()
{
Person01 p1;
cout << "size of p1 = " << sizeof(p1) << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
加入整型静态变量后:
#include <iostream>
using namespace std;
class Person01
{
public:
int m_A; // 非静态成员变量 属于类的对象上
static int m_B; // 静态成员变量 不属于类的对象上
};
int Person01::m_B = 0;
void test01()
{
Person01 p1;
cout << "size of p1 = " << sizeof(p1) << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
加入非静态成员函数后:
#include <iostream>
using namespace std;
class Person01
{
public:
int m_A; // 非静态成员变量 属于类的对象上
static int m_B; // 静态成员变量 不属于类的对象上
void func1(){} // 非静态成员函数 不属于类的对象上
};
int Person01::m_B = 0;
void test01()
{
Person01 p1;
cout << "size of p1 = " << sizeof(p1) << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
加入静态成员函数后:
#include <iostream>
using namespace std;
class Person01
{
public:
int m_A; // 非静态成员变量 属于类的对象上
static int m_B; // 静态成员变量 不属于类的对象上
void func1(){} // 非静态成员函数 不属于类的对象上
static void func2(){} // 静态成员函数 不属于类的对象上
};
int Person01::m_B = 0;
void test01()
{
Person01 p1;
cout << "size of p1 = " << sizeof(p1) << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
4.3.2 this指针的用途
通过4.3.1知道C++成员变量和成员函数是分开存储的
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
那么问题是:这一块代码是如何区分那个对象调用自己的呢?
C++通过提供特殊的对象指针,this指针,解决上述问题
this指针指向被调用的成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用即可
this指针的用途:
- 当形参和成员变量同名时,可用this指针来区分
- 在类的非静态成员函数中返回对象本身,可用return* this
#include <iostream>
using namespace std;
class Person02
{
public:
Person02(int age)
{
// 当形参和成员变量同名时 可用this指针来区分
this->age = age;
}
Person02& PersonAddAge(Person02 p)
{
this->age += p.age;
return*this;
}
int age;
};
void test02()
{
Person02 p1(18);
cout << "p1的年龄:" << p1.age << endl;
}
// 在类的非静态成员函数中返回对象本身,可用retun* this
void test03()
{
Person02 p2(10);
Person02 p3(10);
// 链式编程思想
p3.PersonAddAge(p2).PersonAddAge(p2).PersonAddAge(10);
cout << "p3的年龄:" << p3.age << endl;
}
int main()
{
test02();
test03();
system("pause");
return 0;
}
4.3.3 空指针访问成员函数
C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针
如果用到this指针,需要加以判断保证代码的健壮性
#include <iostream>
using namespace std;
class Person03
{
public:
void ShowClass()
{
cout << "this is Person03 class" << endl;
}
void ShowAge()
{
// 注意判断如果用到this指针,需要加以判断以保证代码的健壮性
if (this == NULL)
{
return;
}
cout << "age = " << this->m_Age << endl;
}
int m_Age;
};
void test04()
{
Person03* p = NULL;
// 空指针也可以调用成员函数
p->ShowClass();
// 注意判断如果用到this指针,需要加以判断以保证代码的健壮性
p->ShowAge();
}
int main()
{
test04();
system("pause");
return 0;
}
4.3.4 const修饰成员函数
常函数:
- 成员函数后加const后称这个函数为常函数
- 常函数不可以修改成员属性
- 成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:
- 声明对象前加const称该对象为常对象
- 常对象只能调用常函数
this指针的本质:指针常量,即指针的指向是不可以修改的
this指针是不可以修改指向的
在成员函数后面加const本质修饰的是this指针,让指针指向的值也不可以修改
#include <iostream>
using namespace std;
class Person7
{
public:
// 常函数
void showPerson()const
{
m_B = 100;
}
int m_A; // 常函数不可以修改成员属性
mutable int m_B; // 成员属性声明时加关键字mutable后,在常函数中依然可以修改
};
void test07()
{
const Person7 p;
p.showPerson(); // 常对象只能调用常函数
}
int main()
{
system("pause");
return 0;
}
4.4 友元
在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术
友元的目的就是让一个函数或者类访问另一个类中私有成员
友元的关键字:friend
友元的三种实现:
- 全局函数做友元
- 类做友元
- 成员函数做友元
4.4.1 全局函数做友元
#include <iostream>
using namespace std;
#include <string>
// 建筑物类
class Building1
{
// 全局函数GoodGay是Building类的友元函数 可以访问Building类的私有成员
friend void GoodGay(Building1* building);
public:
Building1()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
string m_SittingRoom;
private:
string m_BedRoom;
};
// 全局函数
void GoodGay(Building1* building)
{
cout << "全局函数好基友函数 正在访问:" << building->m_SittingRoom << endl;
cout << "全局函数好基友函数 正在访问:" << building->m_BedRoom << endl;
}
void test01()
{
Building1 building;
GoodGay(&building);
}
int main()
{
test01();
system("pause");
return 0;
}
4.4.2 友元类
#include <iostream>
using namespace std;
#include <string>
class Building;
class GoodGay
{
public:
GoodGay();
void visit();
private:
Building* building;
};
class Building
{
friend class GoodGay; //声明GoodGay是Building的友元类,可以访问Building类的私有成员
public:
Building();
string m_SittingRoom;
private:
string m_BedRoom;
};
Building::Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
GoodGay::GoodGay()
{
building = new Building;
}
void GoodGay::visit()
{
cout << "好基友正在访问:" << building->m_SittingRoom << endl;
cout << "好基友正在访问:" << building->m_BedRoom << endl;
}
void test02()
{
GoodGay gg;
gg.visit();
}
int main()
{
test02();
system("pause");
return 0;
}
4.4.3 成员函数做友元
#include <iostream>
using namespace std;
#include <string>
class Building3;
class GoodGay3
{
public:
GoodGay3();
void visit1(); // visit1可以访问Building3中的私有成员
void visit2(); // visit2不可以访问Building3中的私有成员
private:
Building3* building;
};
class Building3
{
friend void GoodGay3::visit1();
public:
Building3();
string m_SittingRoom;
private:
string m_BedRoom;
};
GoodGay3::GoodGay3()
{
building = new Building3;
}
void GoodGay3::visit1()
{
cout << "好基友GoodGay正在访问" << building->m_SittingRoom << endl;
cout << "好基友GoodGay正在访问" << building->m_BedRoom << endl;
}
void GoodGay3::visit2()
{
cout << "好基友GoodGay正在访问" << building->m_SittingRoom << endl;
}
Building3::Building3()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
void test02()
{
GoodGay3 gg;
gg.visit1();
gg.visit2();
}
int main()
{
test02();
system("pause");
return 0;
}
4.5 运算符重载
定义:对已有的运算符进行重新定义,赋予其另一种功能,以适应不同的数据类型
4.5.1 加号运算符重载
作用:实现两个自定义数据类型相加的运算
- 通过成员函数实现
#include <iostream>
using namespace std;
class Person1
{
public:
int m_A;
int m_B;
// 成员函数实现运算符+重载
Person1 operator+(Person1& p)
{
Person1 temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
};
void test01()
{
Person1 p1;
p1.m_A = 10;
p1.m_B = 10;
Person1 p2;
p2.m_A = 10;
p2.m_B = 10;
Person1 p3 = p1 + p2;
cout << "p3.m_A = " << p3.m_A << endl;
cout << "p3.m_B = " << p3.m_B << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
- 通过全局函数实现
#include <iostream>
using namespace std;
class Person2
{
public:
int m_A;
int m_B;
};
Person2 operator+ (Person2 & p1, Person2 & p2)
{
Person2 temp;
temp.m_A = p1.m_A + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}
void test02()
{
Person2 p1;
p1.m_A = 10;
p1.m_B = 10;
Person2 p2;
p2.m_A = 10;
p2.m_B = 10;
Person2 p3 = p1 + p2;
cout << "p3.m_A = " << p3.m_A << endl;
cout << "p3.m_B = " << p3.m_B << endl;
}
int main()
{
test02();
system("pause");
return 0;
}
- 运算符重载,也可以发生函数重载
#include <iostream>
using namespace std;
class Person3
{
public:
int m_A;
int m_B;
};
Person3 operator + (Person3 & p, int num)
{
Person3 temp;
temp.m_A = p.m_A + num;
temp.m_B = p.m_B + num;
return temp;
}
void test03()
{
Person3 p1;
p1.m_A = 10;
p1.m_B = 15;
Person3 p2;
p2.m_A = 10;
p2.m_B = 15;
Person3 p3 = p1 + 10;
Person3 p4 = p2 + 20;
cout << "p3.m_A = " << p3.m_A << endl;
cout << "p3.m_B = " << p3.m_B << endl;
cout << "p4.m_A = " << p3.m_A << endl;
cout << "p4.m_B = " << p3.m_B << endl;
}
int main()
{
test03();
system("pause");
return 0;
}
4.5.2 左移运算符重载
作用:可以输出自定义数据类型
注:通常不会利用成员函数重载左移运算符,因为无法实现cout在左侧
cout类型:ostrem即标准输出流对象
#include <iostream>
using namespace std;
class Person4
{
friend ostream& operator<<(ostream& cout, Person4& p);
public:
Person4(int a, int b)
{
m_A = a;
m_B = b;
}
private:
int m_A;
int m_B;
};
// 只能通过全局函数对左移运算符进行重载
ostream& operator<<(ostream& cout, Person4& p)
{
cout << "m_A = " << p.m_A << " m_B = " << p.m_B;
return cout;
}
void test04()
{
Person4 p(10, 10);
cout << p << endl;
}
int main()
{
test04();
system("pause");
return 0;
}
4.5.3 递增运算符重载
作用:通过重载递增运算符,实现自己的整型数据
#include <iostream>
using namespace std;
class MyInteger
{
friend ostream& operator<<(ostream& cout, MyInteger myint);
public:
MyInteger()
{
m_Num = 0;
}
// 重载前置++运算符
MyInteger& operator++() // 前置递增返回引用是为了一直对一个数据进行自增操作
{
// 先进行自增
m_Num++;
// 再返回
return *this;
}
// 重载后置运算符
MyInteger operator++(int) // int代表占位参数(只能用int),用于区分前置和后置
{
// 先记录原来的值
MyInteger temp = *this;
// 后递增
m_Num++;
// 最后返回原来的值
return temp;
}
private:
int m_Num;
};
// 重载左移运算符
ostream& operator<<(ostream& cout, MyInteger myint)
{
cout << myint.m_Num;
return cout;
}
void test05()
{
MyInteger myint;
cout << ++myint << endl;
}
void test06()
{
MyInteger myint;
cout << myint++ << endl;
cout << myint << endl;
}
int main()
{
test05();
test06();
system("pause");
return 0;
}
4.5.4 赋值运算符重载
C++编译器至少给一个类添加4个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
- 负值运算符operator=,对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
#include <iostream>
using namespace std;
class Person6
{
public:
Person6(int age)
{
m_Age = new int(age);
}
~Person6()
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}
Person6& operator=(Person6& p)
{
// 编译器是浅拷贝 m_Age = p.m_Age
// 应该先判断是否有属性在堆区 如果有 先进行释放
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
m_Age = new int(*p.m_Age);
return*this;
}
int* m_Age;
};
void test07()
{
Person6 p1(18);
Person6 p2(19);
Person6 p3(20);
p3 = p2 = p1;
cout << "p1的年龄:" << *p1.m_Age << endl;
cout << "p2的年龄:" << *p2.m_Age << endl;
cout << "p3的年龄:" << *p3.m_Age << endl;
}
int main()
{
test07();
system("pause");
return 0;
}
4.5.5 关系运算符重载
作用:重载关系运算符,可以让两个自定义类型对象进行对比操作
#include <iostream>
using namespace std;
#include <string>
class Person1
{
public:
Person1(string name, int age)
{
m_Name = name;
m_Age = age;
}
string m_Name;
string m_Age;
bool operator == (Person1& p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return true;
}
return false;
}
bool operator != (Person1& p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return false;
}
return true;
}
};
void test08()
{
Person1 p1("Tom", 18);
Person1 p2("Jerry", 18);
if (p1 == p2)
{
cout << "p1 和 p2 是相等的!" << endl;
}
else
cout << "p1 和 p2 是不相等的!" << endl;
}
void test09()
{
Person1 p1("Tom", 18);
Person1 p2("Jerry", 18);
if (p1 != p2)
{
cout << "p1 和 p2 是不相等的!" << endl;
}
else
cout << "p1 和 p2 是相等的!" << endl;
}
int main()
{
test08();
test09();
system("pause");
return 0;
}
4.5.6 函数调用运算符重载
- 函数调用运算符()也可以重载
- 由于重载后使用的方式非常像函数的调用,因此称为仿函数
- 仿函数没有固定写法,非常灵活
#include <iostream>
using namespace std;
#include <string>
class MyPrint
{
public:
void operator()(string test)
{
cout << test << endl;
}
};
void test01()
{
MyPrint myprint;
myprint("hello world"); // 函数调用重载
}
void _MyPrint(string test)
{
cout << test << endl;
}
class MyAdd
{
public:
// 仿函数没有固定写法,非常灵活
int operator()(int num1, int num2)
{
return num1 + num2;
}
};
void test02()
{
MyAdd myadd;
int ret = myadd(10, 20);
cout << "ret = " << ret << endl;
cout << MyAdd()(20, 30) << endl; // 匿名函数对象
}
int main()
{
test01();
_MyPrint("Hello World"); // 函数调用
test02();
system("pause");
return 0;
}
4.6 继承
继承是面向对象三大特性之一
定义类时,下级别的成员除了拥有上一级的共性,还带有自己的特性
这时候可以考虑利用继承的技术,减少重复代码
4.6.1 继承的基本语法
例:很多网站中都有公共的头部、公共的底部甚至公共的左侧列表,只有中心内容不同
下面分别用普通写法和继承写法来实现网页中的内容,看一下继承存在的意义及好处
普通写法:
#include <iostream>
using namespace std;
// 普通写法
class Java
{
public:
void header()
{
cout << "首页、公开课、登录、注册……(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图……(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++、……(公共分类列表)" << endl;
}
void content()
{
cout << "Java学科视频" << endl;
}
};
class Python
{
public:
void header()
{
cout << "首页、公开课、登录、注册……(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图……(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++、……(公共分类列表)" << endl;
}
void content()
{
cout << "Python学科视频" << endl;
}
};
class Cpp
{
public:
void header()
{
cout << "首页、公开课、登录、注册……(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图……(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++、……(公共分类列表)" << endl;
}
void content()
{
cout << "Cpp学科视频" << endl;
}
};
void test01()
{
cout << "Java下载视频页面如下:" << endl;
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
cout << "--------------------------" << endl;
cout << "Python下载视频页面如下:" << endl;
Python py;
py.header();
py.footer();
py.left();
py.content();
cout << "--------------------------" << endl;
cout << "Cpp下载视频页面如下:" << endl;
Cpp cpp;
cpp.header();
cpp.footer();
cpp.left();
cpp.content();
cout << "--------------------------" << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
继承写法:
#include <iostream>
using namespace std;
// 继承写法
class BasePage
{
public:
void header()
{
cout << "首页、公开课、登录、注册……(公共头部)" << endl;
}
void footer()
{
cout << "帮助中心、交流合作、站内地图……(公共底部)" << endl;
}
void left()
{
cout << "Java、Python、C++、……(公共分类列表)" << endl;
}
};
// Java页面
class Java : public BasePage
{
public:
void content()
{
cout << "Java学科视频" << endl;
}
};
// Python页面
class Python : public BasePage
{
public:
void content()
{
cout << "Python学科视频" << endl;
}
};
// C++页面
class CPP : public BasePage
{
public:
void content()
{
cout << "C++学科视频" << endl;
}
};
void test01()
{
cout << "Java下载视频页面如下:" << endl;
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
cout << "--------------------------" << endl;
cout << "Python下载视频页面如下:" << endl;
Python py;
py.header();
py.footer();
py.left();
py.content();
cout << "--------------------------" << endl;
cout << "Cpp下载视频页面如下:" << endl;
CPP cpp;
cpp.header();
cpp.footer();
cpp.left();
cpp.content();
cout << "--------------------------" << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
继承的好处:减少重复代码
语法:class 子类 :继承方式 父类
子类也称为派生类,父类也成为基类