C++ 面向对象的三大特性为 : 封装, 继承, 多态.
万事万物均可作为对象, 每个对象都有其属性和行为.
对于某些具有 相同属性 和 相同行为 的对象, 可以抽象为 类.
比如所有的圆形都属于圆类, 人都属于人类.
封装的意义在于 :
将 属性 和 行为 作为一个整体, 用于表现生活中的事物 ;
并将属性和行为加以权限控制.
圆形都有 半径, 直径, 周长, 面积等属性, 通过半径属性和圆周率, 可以求得周长和面积.
求周长和面积的过程就称为 行为.
类 的属性 就像结构体的属性, 类 的行为 就像函数.
类 的 属性 和 行为 , 统称为 类 的 成员.
属性又被称为 成员属性 / 成员变量 ;
行为又被称为 成员函数 / 成员方法 .
语法:
//定义一个类
class 类名称
{
访问权限 : //冒号
属性
行为
}; //记住末尾这个分号
//创建一个对象(也叫类的实例化、具象化)
类名称 对象名称; //和结构体一样.
//访问属性
对象名称.属性名称 = 0; //将该对象的该属性赋值为0
//访问行为
对象名称.行为函数(); //特指行为函数为无参无返类函数时
示例1 : 封装圆类, 给出半径, 求圆的周长.
示例2 : 封装学生类, 由用户对学生的 姓名, 年龄, 性别, 成绩 进行赋值, 然后打印.
#include
using namespace std;
#include
//将圆周率设为全局常量
const double PI = 3.141592653589;
//创建一个类
class Circle
{
//访问权限
public: //公共权限
//属性
double radius; //半径
//行为(函数)
double perimeter()
{
return 2 * radius * PI; //返回周长的值
}
}; //注意类结束后需要分号, 和结构体一样
class Student
{
public:
string name;
int age;
string sex;
double score;
void allocate()
{
cout << "请输入学生姓名: " << endl;
getline(cin, name);
cout << "请输入学生年龄: " << endl;
cin >> age;
cout << "请输入学生性别: " << endl;
cin.ignore(); //输入字符串前需要把用户之前输入的换行符忽略掉
getline(cin, sex);
cout << "请输入学生成绩: " << endl;
cin >> score; //直接读取数字, 所以不需要忽略换行符
}
void printInfo()
{
cout << "学生姓名为: " << name << endl;
cout << "学生年龄为: " << age << endl;
cout << "学生性别为: " << sex << endl;
cout << "学生成绩为: " << score << endl;
}
};
int main()
{
//创建一个圆类对象 (实例化 / 具象化)
Circle c1;
c1.radius = 5;
cout << c1.perimeter() << endl; //相当于调用函数, 但这个函数不在全局区中, 而在类中
Student s1;
s1.allocate();
s1.printInfo();
return 0;
}
其实就是将 函数 封装到 结构体 里面.
类 在封装时, 可以将属性和行为放在 不同的权限 下, 加以控制.
访问权限分为以下三种 :
public 公共权限, 类内可以访问, 类外也可以访问
protected 保护权限, 类内可以访问, 但类外无法访问
private 私有权限, 类内可以访问, 但类外无法访问
其中 protected 和 private 的区别, 在后面学习 继承 的时候会体现.
简单的说, 子类 可以访问 父类 的 protected , 但是无法访问 父类 的 private.
class Person
{
public:
string name;
void function() //注意, 函数本身也在 public 权限下
{
name = "张三";
car = "拖拉机"; //类内, 可以访问
password = 123456; //类内, 可以访问
}
protected:
string car;
private:
int password;
};
int main()
{
Person p1;
p1.name = "李四"; //public 权限, 类外可以访问
//p1.car = "奔驰"; //报错, protected 权限, 类外不能访问
//p1.password = 654321; //报错, private 权限, 类外不能访问
p1.function(); //但是函数本身是 public 权限, 因此可以访问.
return 0;
}
在 C++ 中, struct 和 class 唯一的区别就在于 默认访问权限不同.
struct 默认权限为 公共权限.
class 默认权限为 私有权限.
所以上面的代码经常用到 public , 因为在 class 中, 没有写权限的属性默认为 私有权限.
在 C++ 中, 可以通过 将成员属性设置为私有 的方式, 划分读写权限.
已知 私有权限 无法在类外访问, 所以还需要写 公共权限 的接口函数.
这样 main 函数 无法直接访问成员属性, 必须通过 公共权限 的接口函数来操作属性.
如此就可以根据功能需求写函数, 划分读写权限.
同时, 可以在接口函数中加入条件判断, 检查 写入的合法性.
class Permissions
{
//将成员属性全部设为私有权限, 方便读写权限设置
private:
string m_name; //需要将姓名设置为可读可写
int m_age; //需要将年龄设置为只读
string m_secret; //需要将秘密设置为只写
public: //公共权限接口
//姓名可写
void setName(string name)
{
m_name = name; //将调用函数时传入的姓名作为成员姓名, 因为这个操作在类内实现,所以合法
}
//姓名可读
string getName()
{
return m_name; //直接返回成员姓名作为函数表达式的值, 因为这个操作在类内实现,所以合法
}
//年龄只读
int getAge()
{
m_age = 0; //写年龄封装在行为函数中, main 无法修改
return m_age; //只留下了 读取年龄 的公共函数接口, main函数中无法直接访问 m_name, 也没有对应函数接口写年龄.
}
//秘密只写
void setSecret(string secret) //读写权限的另一个作用, 检查 写入内容 的合法性
{
if (secret == "非法") //因为这是函数, 所以可以加入条件判断语句,来检查 main 或者 用户 写入的内容
{
cout << "您违法了!" << endl;
return; //写入非法的时候, 警告并退出函数
}
m_secret = secret; //写入合法的时候, 才进行写入
}
};
int main()
{
Permissions pm1;
pm1.setName("张三"); //写姓名
cout << pm1.getName() << endl; //读姓名
cout << pm1.getAge() << endl; //读年龄
pm1.setSecret("非法"); //非法写入,被警告,写入失败
pm1.setSecret("合法"); //合法写入
return 0;
}
需求: 设计立方体类, 求立方体面积和体积, 分别通过 全局函 数和 成员函数 判断两个立方体的长宽高是否完全相等.
源代码:
#include
using namespace std;
//立方体类
class Cube
{
private:
double m_length;
double m_width;
double m_hight;
public:
void setLength(double length)
{
m_length = length;
//cout << "立方体长已设置为" << m_length << endl;
}
double getLength()
{
//cout << "立方体的长为" << m_length << endl;
return m_length;
}
void setWidth(double width)
{
m_width = width;
//cout << "立方体宽已设置为" << m_width << endl;
}
double getWidth()
{
//cout << "立方体的宽为" << m_width << endl;
return m_width;
}
void setHight(double hight)
{
m_hight = hight;
//cout << "立方体高已设置为" << m_hight << endl;
}
double getHight()
{
//cout << "立方体的高为" << m_hight << endl;
return m_hight;
}
bool isSameByClass(Cube & c)
{
if (m_length == c.getLength() && m_width == c.getWidth() && m_hight == c.getHight())
{
cout << "两个立方体的长宽高均相等!(成员函数)" << endl;
return true;
}
cout << "两个立方体的长宽高并不完全相等!(成员函数)" << endl;
return false;
}
double volume()
{
return m_length * m_width * m_hight;
}
double superficial_area()
{
return 2 * (m_length * m_width + m_length * m_hight + m_width * m_hight);
}
};
//全局函数
bool isSame(Cube& c1, Cube& c2)
{
if (c1.getLength() == c2.getLength() && c1.getWidth() == c2.getWidth() && c1.getHight() == c2.getHight())
{
cout << "两个立方体的长宽高均相等!(全局函数)" << endl;
return true;
}
cout << "两个立方体的长宽高并不完全相等!(全局函数)" << endl;
return false;
}
int main()
{
Cube cube1;
Cube cube2;
cube1.setLength(10);
cube1.setWidth(10);
cube1.setHight(10);
cout << "立方体 1 的体积为:" << cube1.volume() << endl;
cout << "立方体 1 的表面积为:" << cube1.superficial_area() << endl;
cube2.setLength(10);
cube2.setWidth(10);
cube2.setHight(11);
cout << "立方体 2 的体积为:" << cube2.volume() << endl;
cout << "立方体 2 的表面积为:" << cube2.superficial_area() << endl;
bool b1 = cube1.isSameByClass(cube2);
bool b2 = isSame(cube1, cube2);
return 0;
}
需求: 设计 圆类 和 点类, 分别用全局函数和成员函数判断点和圆的关系 (圆内 / 圆上 / 圆外)
进阶: 圆类 改为 球类
拓展:
C++中的平方、开方、绝对值怎么计算_cpp平方_赵大寳Note的博客-CSDN博客
c++ 如何开N次方?速解 - 知乎 (zhihu.com)
源代码:
#include
using namespace std;
#include
//等下球类成员函数要用到点类, 所以先写点类
class Point
{
private: //点的空间直角坐标
double m_x;
double m_y;
double m_z;
public:
void set_x(double x) //通过公共函数接口读写坐标
{
m_x = x;
}
double get_x()
{
return m_x;
}
void set_y(double y)
{
m_y = y;
}
double get_y()
{
return m_y;
}
void set_z(double z)
{
m_z = z;
}
double get_z()
{
return m_z;
}
};
class Ball
{
private:
double m_center_x; //球心的空间直角坐标
double m_center_y;
double m_center_z;
double m_radius; //球的半径
public:
void setCenter_x(double center_x) //球心与点同理
{
m_center_x = center_x;
}
double getCenter_x()
{
return m_center_x;
}
void setCenter_y(double center_y)
{
m_center_y = center_y;
}
double getCenter_y()
{
return m_center_y;
}
void setCenter_z(double center_z)
{
m_center_z = center_z;
}
double getCenter_z()
{
return m_center_z;
}
void setRadius(double radius) //半径读写
{
m_radius = radius;
}
double getRadius()
{
return m_radius;
}
void isInBallByClass(Point & p) //成员函数判断球内外
{
//空间直角坐标系的点距离计算: 求出两点间x,y,z差值, 三个差值分别平方后相加, 所得和再开平方
//函数 pow(x, 2) 代表平方x , pow(x, 1.0/2) 代表开平方x
if (pow((pow(p.get_x() - m_center_x, 2) + pow(p.get_y() - m_center_y, 2) + pow(p.get_z() - m_center_z, 2)), 1.0 / 2) < m_radius)
{
cout << "点在球内(成员函数)" << endl;
}
else if (pow((pow(p.get_x() - m_center_x, 2) + pow(p.get_y() - m_center_y, 2) + pow(p.get_z() - m_center_z, 2)), 1.0 / 2) == m_radius)
{
cout << "点在球面上(成员函数)" << endl;
}
else
{
cout << "点在球外(成员函数)" << endl;
}
}
};
//全局函数判断球内外
void isInBall(Ball & b, Point& p)
{
if (pow((pow(p.get_x() - b.getCenter_x(), 2) + pow(p.get_y() - b.getCenter_y(), 2) + pow(p.get_z() - b.getCenter_z(), 2)), 1.0 / 2) < b.getRadius())
{
cout << "点在球内(全局函数)" << endl;
}
else if (pow((pow(p.get_x() - b.getCenter_x(), 2) + pow(p.get_y() - b.getCenter_y(), 2) + pow(p.get_z() - b.getCenter_z(), 2)), 1.0 / 2) == b.getRadius())
{
cout << "点在球面上(全局函数)" << endl;
}
else
{
cout << "点在球外(全局函数)" << endl;
}
}
int main()
{
Ball ball; //创建球类实体
ball.setCenter_x(0); //写入球心空间直角坐标, 半径
ball.setCenter_y(0);
ball.setCenter_z(0);
ball.setRadius(1);
Point p; //创建球类实体
p.set_x(0.5); //写入点的空间直角坐标
p.set_y(0.5);
p.set_z(0.5);
ball.isInBallByClass(p); //成员函数判断球内外
isInBall(ball, p); //全局函数判断球内外
return 0;
}
写完才发现, 球心也是点, 干了好多重复工作, 艹
类中是可以嵌套类的, 比如可以给 Ball 类 增加一个 Point 类型的成员
当代码量越来越大时, 若还将所有代码放在同一个文件中, 则不利于阅读和维护.
C++ 函数_AusrEnder的博客-CSDN博客
此篇第六节简单介绍了函数的分文件编写.
本节主要介绍类的分文件编写.
补充一点, 可以在头文件开始 加上
#pragma once
来防止一个头文件被多次包含.
C/C++ 中的 #pragma once 作用是什么?_程序员编程指南的博客-CSDN博客
和函数分文件编写相同的是, 类的分文件编写也需要 至少 3 个文件.
头文件 :
#pragama once
#include
using namespace std;
//类的声明
class Point
{
private:
double m_x;
double m_y;
double m_z;
public:
void set_x(double x); //这里的函数声明就可以了
void set_y(double y);
void set_z(double z);
double get_x():
double get_y():
double get_z():
};
如代码所示, 类的分文件编写需要写 类的声明.
类的声明包含 权限 / 属性 / 成员函数的声明.
源文件 (子函数) :
#include "头文件名称.h"
using namespace 类名;
//类的成员函数定义
//不需要写类了, 也不用写权限, 把各个行为函数的定义写出来就好了
void set_x(double x)
{
m_x = x;
}
……
该源文件中不需要声明 类 和 类的成员 , 只需要写函数的具体实现.
函数的分文件编写是 可以不写命名空间 的.
但是 类 中的成员函数, 必须要写命名空间. 若函数分文件编写中, 存在子函数, 则也要写命名空间.
命名空间有两种写法.
第一种, using namespace 命名空间 ; (暂时没搞懂,不要使用)
这就好比你没写 using namespace std; 的时候 要写很多 std:: 一样.
第二种, 函数名前加作用域名, 比如
void Point::set_x(double x)
{
m_x = x;
}
注意, 作用域名 位于 返回值类型之后, 函数名之前.
c++类的分文件编写规则_c++ class 分文件_milaiko的博客-CSDN博客
main 函数文件就不多说了.
#include
using namespace std;
#include "头文件名.h"
若一个变量没有初始化, 则对其使用时, 后果未知;
使用完一个对象或者变量, 没有及时清理, 也会造成一定的安全问题
C++ 利用构造函数和析构函数解决了上述两个问题, 这两个函数会被编译器自动调用, 完成对象的初始化和清理工作.
对象的初始化和清理 是必须要做的工作, 如果程序员不提供构造和析构, 编译器会自动提供.
编译器提供的构造函数和析构函数为空实现. (函数后的大括号中没有内容)
构造函数 : 创建对象时,为对象的成员属性赋值.
析构函数 : 销毁对象时, 执行清理工作.
//构造函数
类名 {}
class Person
{
Person() //构造
~Person() //析构
};
注意, 构造函数与析构函数 没有返回值, 连 void 也不需要写.
构造函数名称与类名相同, 析构函数则需要在类名前加 "~" (波浪号)
构造函数可以有参数, 可以发生重载. 析构函数不允许有参数, 因此无法重载.
构造函数在程序调用对象时自动调用, 无需手动调用, 且只会调用一次
析构函数在程序销毁对象前自动调用, 无需手动调用, 且只会调用一次
构造与析构需要写到 类 里面, 而且必须在 public 权限下.(若不写 public , 默认权限为 private )
完成 类定义 后, 需要实例化一个对象, 才能调用构造与析构.
下面是调用构造与析构的详细代码.
#include
using namespace std;
class Person
{
public:
Person()
{
cout << "构造函数的调用" << endl;
}
~Person()
{
cout << "析构函数的调用" << endl;
}
};
void test01()
{
Person p; //创建一个类的对象, 才能调用构造与析构
//对象 p 在函数中, 为局部变量, 存放在栈区.
//调用 test01 时会调用构造函数, test01 结束前会调用析构函数.
}
int main()
{
test01(); //这一行中就发生了构造和析构的调用.
//如果在 main 中创建 p , 则 main 结束前都不会调用析构,但程序结束的瞬间可以看到析构 "一闪而过"
return 0;
}
构造函数有两种分类方式 :
按参数分 : 有参构造 和 无参构造(默认构造) (有参数和无参数)
按类型分 : 普通构造 和 拷贝构造 (若不是拷贝构造, 就是普通构造)
无参构造又称为默认构造, 若程序员不提供构造函数, 则编译器自动调用无参构造作为默认构造.
有参构造的注意事项:
使用有参构造时, 编译器认为已经存在构造函数, 就不再提供默认构造. 这会使无参调用报错.
class Person
{
public:
Person(int a) //写了构造函数之后, 编译器就不再提供默认的无参构造
{
cout << a << endl;
cout << "有参构造函数的调用" << endl;
}
~Person()
{
cout << "析构函数的调用" << endl;
}
};
void test01()
{
Person p(11); //创建对象时会调用构造, 一定要给参数
//Person p; //因为构造函数是有参函数, 这里又没给参数, 所以就会报错
}
int main()
{
test01();
return 0;
}
当然也可以使用 带默认参数 的有参构造来解决这个问题.
但是不要同时使用 默认构造 和 全默认参数的有参构造.
否则, 若你调用时没有给参数, 编译器不知道该调用哪个构造函数.
class Person
{
public:
Person(int a = 10) //写了默认参数, 调用的时候就可以不写参数了
{
cout << a << endl;
cout << "有参构造函数的调用" << endl;
}
~Person()
{
cout << "析构函数的调用" << endl;
}
};
void test01()
{
Person p; //现在就不会报错了, 正常构造析构
}
int main()
{
test01();
return 0;
}
拷贝构造函数的注意事项:
拷贝构造函数相当于 将另一个对象的成员属性拿到当前构造的对象中来
这一行为的前提是 被拷贝者 不能发生改变, 所以最好 const 同时修饰 变量 和 指针
#include
using namespace std;
class Person
{
public:
int age; //注意这里新加了一个age成员
Person()
{
cout << "无参构造函数的调用" << endl;
}
Person(int a )
{
age = a;
cout << a <<" " ;
cout << "有参构造函数的调用" << endl;
}
Person(const Person& p) //相当于 cosnt Person * const p
{
age = p.age; //将引用对象的成员 拷贝至 正在构造的对象的成员
cout << "age = :" << age << endl;
cout << " 拷贝构造函数的调用" << endl;
}
~Person()
{
cout << "析构函数的调用" << endl;
}
};
void test01()
{
Person p1; //创建一个类的对象, 才能调用构造与析构
//对象 p 在函数中, 为局部变量, 存放在栈区.
//调用 test01 时会调用构造函数, test01 结束前会调用析构函数.
Person p2(11);//传入11 , 调用有参构造
Person p3(p2);//传入p2 , 调用拷贝构造
}
int main()
{
test01(); //这一行中就发生了构造和析构的调用.
//如果在 main 中创建 p , 则 main 结束前都不会调用析构,但程序结束的瞬间可以看到析构 "一闪而过"
return 0;
}
构造函数有三种调用方式 :
括号法 , 显示法 , 隐式转换法
上面的代码就是括号法调用.
需要注意的是, 调用默认构造时, 不要写括号.
因为 Person p(); 这样的命令在编译器看来是 函数的声明, Person 就是其返回值类型, 函数无参.
Person p ; //正确地调用默认构造
显示法:
void test02()
{
//显示法调用
cout << "显示法" << endl;
Person p4; //默认构造
Person p5 = Person(12); //有参构造
Person p6 = Person(p5); //拷贝构造
cout << "匿名对象" << endl;
Person(10); //匿名对象,当前行执行结束后,系统立即回收
cout << "回收验证" << endl; //在输出回收验证之前,系统已经输出析构了
//不要利用拷贝构造函数来初始化一个匿名对象
//Person(p4); 编译器会忽略(),认为这是 p4 的默认构造, 或者说 对象 p4 的声明
}
显示法的右表达式是一个 匿名对象.
匿名对象的特点是, 匿名对象当前所在行执行完后, 就会被系统回收(析构)掉.
需要注意的是, 不要直接用拷贝构造初始化匿名对象(但拷贝构造的匿名对象作为右值是可以的).
因为 Person(p4); 在编译器看来, 是 对象 p4 的声明.(编译器忽略了括号)
Person p6 = Person(p5); 才是正确的显示法调用拷贝构造.
隐式转换法:
本质上是显示法的一种简写. 隐式转换法会被编译器自动转换为显示法.
void test03()
{
//隐式转换法调用
cout << "隐式转换法" << endl;
//编译器会自动转换为显示法
Person p7 = 13; //有参构造
Person p8 = p7; //拷贝构造
}
这个就不多说了, 上节的拷贝构造已经讲的很清楚了.
#include
using namespace std;
class Person
{
public:
int m_age;
Person()
{
cout << "Person的无参构造调用" << endl;
}
Person(int age)
{
m_age = age;
cout << "Person的有参构造调用" << endl;
}
Person(const Person &p)
{
m_age = p.m_age;
cout << "Person的拷贝构造调用" << endl;
}
~Person()
{
cout << "Person的析构函数调用" << endl;
}
};
void function1()
{
Person p1(5); //创建对象 p1, 调用有参构造 , p1.m_age == 5
Person p2(p1); //创建对象 p2, 调用拷贝构造, 这就是利用已经创建完毕的对象 p1 来初始化 p2
}
int main()
{
function1();
return 0;
}
其实这就是 函数-值传递 中所说的, 值传递会占用内存创建一个临时副本.
void doWork(Person p) //将 Person 类数据 p 值传给函数 doWork, 值传递就是拷贝构造
//但是平时用拷贝构造最好加上 const 和引用
{
}
void function1()
{
Person p1(5); //创建对象 p1, 调用有参构造 , p1.m_age == 5
Person p2(p1); //创建对象 p2, 调用拷贝构造, 这就是利用已经创建完毕的对象 p1 来初始化 p2
doWork(p2); //将 实参 p2 传递给 形参 p ,这就是利用值传递的方式给函数参数传值, 依然是调用拷贝构造
}
就像返回值类型是 int 的函数, 可以用 return 的方式 返回一个 int 类型的值
当返回值类型是 类 类型 时, return 返回一个 类 类型的值.
Person doWork2()
{
Person p1; //调用默认构造
cout << &p1 << endl;
return p1; //调用拷贝构造,但 C++ 11 中已经优化了这种拷贝构造
}
void function1()
{
Person p3 = doWork2();
cout << &p3 << endl;
}
有的教程中说, return p1 的时候会调用拷贝构造, 将这个临时副本返回.
所以 p1 和 p3 的地址会不同.
但经过本人实测, Visual Studio 2022 Community 中, 既没有调用拷贝构造, 且两个地址仍相同.
该技术已被 C++11 优化, 称为 copy elision (复制省略 / 复制消除)
浅谈C++11标准中的复制省略(copy elision,也叫RVO返回值优化)_利用复制省略提高性能_知行合一2018的博客-CSDN博客
该文章的结论是, 返回值优化彻底消除了拷贝构造的调用.
创建一个 类 时, 编译器都会自动提供至少 3 个函数
1. 默认构造 (无参, 空实现)
2.析构函数 (无参, 空实现)
3.拷贝构造 (对全部属性进行拷贝)
当程序员不写任何构造函数时, 编译器会自动提供上述三个函数.
当程序员定义了有参构造, 编译器就不再提供 默认构造, 但仍提供 拷贝构造;(详见3.3.1)
当程序员定义了拷贝构造, 编译器就仅提供析构函数.
class Person
{
public:
int m_age;
Person()
{
cout << "Person的无参构造调用" << endl;
}
Person(int age)
{
m_age = age;
cout << "Person的有参构造调用" << endl;
}
//Person(const Person &p) //拷贝构造被我注释掉了
//{
// m_age = p.m_age;
// cout << "Person的拷贝构造调用" << endl;
//}
~Person()
{
cout << "Person的析构函数调用" << endl;
}
};
void function1()
{
Person p1(5);
Person p2(p1);
cout << p2.m_age << endl; //依然能输出5 , 说明程序默认提供了拷贝构造.
可以看到, 在我没有写拷贝构造的情况下, 依然能够调用拷贝构造, p2.m_age 就拷贝自 p1.
不同于自定义拷贝构造函数的 可以仅拷贝部分属性, 默认拷贝构造函数会拷贝所有属性.
首先, 程序提供的默认拷贝构造函数为浅拷贝.
当 类 的成员包含指针变量时, 根据指针是否初始化可以分为以下两种情况 :
第一, 有初始化 (指向栈区)
class Person
{
public:
int m_age;
int a;
int* m_height =&a; //m_height 是一个有初始化(初始指向)的指针
Person()
{
cout << "Person的无参构造调用" << endl;
}
Person(int age, int height)
{
m_age = age;
*m_height = height;
cout << "Person的有参构造调用" << endl;
}
};
void function1()
{
Person p1(5,160); //创建对象 p1, 调用有参构造
Person p2(p1); //创建对象 p2, 调用默认拷贝构造(因为这次我没写拷贝构造)
cout << p2.m_age << " " << *p2.m_height << endl; //访问指针指向的值需要解引用
}
第二种, 无初始化 (指向堆区)
int m_age;
int* m_height;
这样的代码虽然能够通过编译, 执行时也不会报错, 但是执行到一半就中止了.
因为在有参构造中, 程序不知道把 160 这个值放在哪里, 因为我们没有给 m_height 一个明确指向.
这时, 可以通过开辟堆区内存来解决这个问题 :
Person(int age, int height)
{
m_age = age;
m_height = new int(height);
cout << "Person的有参构造调用" << endl;
}
完整的写法应该是 int * m_height = new int(值); 但前面已经定义过 m_height, 这里就不重定义了.
这样程序就可以正常运行.
但是, 堆区的内存并没有回收 !
这时候就需要利用析构函数来释放堆区内存.
~Person()
{
if(m_height != nullptr)
{
delete m_height; //释放堆区内存
m_height = nullptr; //指针指向空处
}
cout << "Person的析构函数调用" << endl;
}
有的教程中会让你写 m_height = NULL, 但 C++ 中 NULL 就是0, 所以空指针最好还是用 nullptr.
这时候编译没问题, 结果一运行就异常了.
这是因为, 浅拷贝只是简单粗暴地将 p1 的内容 复制到 p2 中
这导致 p1.m_height 和 p2.m_height 这两个指针指向同一处.
根据栈区 先进后出 的原则, 第一次执行析构函数,
会释放 p2.m_height 的堆区内存, 然后 p2.m_height 指向空处.
但是, p1.m_height 仍然指向堆区内存地址, 程序在析构 p1 的时候, p1.m_height 不是空指针,
if 条件仍然满足, 这会导致堆区内存被释放两次, 从而使程序崩溃
总的来说, 浅拷贝 会导致 堆区内存重复释放 问题.
那么这时候就需要用 深拷贝 来解决.
Person(const Person &p)
{
m_age = p.m_age;
//m_height = p.m_height 浅拷贝, 也是编译器默认拷贝会写的内容
m_height = new int(*p.m_height); //深拷贝, 在堆区申请新内存, 把 p 中指针解引用得到的值作为新对象指向的值
cout << "Person的拷贝构造调用" << endl;
}
这样就不会报错了. 其实关键就在于, 在堆区申请新的内存.
C++ 浅复制、深复制详解_c++深复制_杨 戬的博客-CSDN博客
总结: 如果属性有在堆区开辟的, 一定要自己写拷贝函数, 对应的属性要写成深拷贝
就像函数有默认参数一样, 类 的成员也可以写初始化属性
而 类 的初始化属性要写在构造函数中.
#include
using namespace std;
class Person
{
public:
int m_A;
int m_B;
int m_C;
Person() //传统初始化方式
{
m_A = 10;
m_B = 20;
m_C = 30;
}
};
void function()
{
Person p1;
cout << "m_A = :" << p1.m_A << endl;
cout << "m_B = :" << p1.m_B << endl;
cout << "m_C = :" << p1.m_C << endl;
}
int main()
{
function();
return 0;
}
但是这样写行数较多, C++ 中允许写初始化列表, 这种方式行数较少.
而初始化列表分为无参构造和有参构造两种. 通常来说, 有参构造更加灵活.
初始化列表的本质是 多行 隐式转换法.
但要注意, 一旦写了有参构造, 编译器就不再提供默认构造.
如果还需要调用无参构造, 请自己再写一个无参构造.
#include
using namespace std;
class Person
{
public:
int m_A;
int m_B;
int m_C;
//Person() //传统初始化方式
//{
// m_A = 10;
// m_B = 20;
// m_C = 30;
//}
Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) //有参构造的初始化列表
{
}
Person() :m_A(30), m_B(20), m_C(10) //无参构造的初始化列表
{
}
};
void function()
{
Person p1; //无参构造
cout << "m_A = :" << p1.m_A << endl;
cout << "m_B = :" << p1.m_B << endl;
cout << "m_C = :" << p1.m_C << endl;
Person p2(10,20,30); //有参构造
cout << "m_A = :" << p2.m_A << endl;
cout << "m_B = :" << p2.m_B << endl;
cout << "m_C = :" << p2.m_C << endl;
}
int main()
{
function();
return 0;
}
C++ 类中的对象可以作为另一个类中的成员. 这种成员称为 对象成员
(就是一个类中包含另一个类,作为自己的属性.)
class A
{
};
class B
{
A a;
}
创建大类时, 优先调用小类的构造函数, 再调用大类的构造函数.
因为小类没有构造完, 大类就不是完整的.
但是根据栈区 先进后出 原则, 析构时先析构大类, 再析构小类.
这就像 人穿多件衣服 一样, 先穿里面的, 再套外面的. 先脱外面的, 再脱里面的.
静态成员 是类中一种特殊的成员, 通过在普通成员和函数前加关键字 static, 就可以成为静态成员
静态成员分为:
1.静态成员变量
· 所有该类对象共享这个静态成员变量值, 若 p1 和 p2 同属于一类, 且 m_A 为静态成员变量, 则 p1.m_A 改变时 p2.m_A也改变.(本质是共享了内存空间)
· 静态成员变量在编译阶段就会被分配内存.
· 无论权限如何, 静态成员变量必须在类内声明一次, 然后在类外用作用域声明一次, 且必须要写到类的后面(最好顺便初始化), 否则无法正常使用. 类外要加作用域是为了编译器将其与全局变量区分开.
· 静态成员变量也是有访问权限的, 私有权限的静态成员变量在类外无法访问.
· 静态成员变量可以通过具体对象访问, 也可以通过类名进行访问.
2.静态成员函数
· 所有该类对象共享这个静态成员函数.
· 静态成员函数不需要在类外进行声明.
· 静态成员函数也是有访问权限的, 私有权限的静态成员函数在类外无法访问.
· 静态成员函数可以通过具体对象访问, 也可以通过类名进行访问.
· 静态成员函数只能访问静态成员变量, 但非静态成员函数可以访问静态成员变量.
#include
using namespace std;
class Person
{
public: //静态成员变量也是有访问权限的
static int m_A;
int m_C; //非静态成员变量
static void function() //静态成员函数也是有访问权限的
{
//cout << m_C << endl; //静态成员函数只能访问静态成员变量, 非静态成员变量无法访问
cout << m_B << endl; //类内可以访问私有权限
cout << "static void funtion 的调用" << endl;
}
void function2()
{
cout << "m_A = :" << m_A << endl; //但是非静态成员函数可以访问静态成员变量
}
private:
static int m_B; //静态成员变量,但是在私有权限下
static void staticfuntion()
{
cout << "static void staticfuntion 的调用" << endl;
}
};
//int Person::m_A; //静态成员变量必须在类外声明一次,否则报错,最好经过初始化, 不初始化默认为 0
int Person::m_A = 100; //必须要用类名写上作用域, 如 Person:: , 否则编译器认为是全局变量
int Person::m_B = 10; //即便是在类内调用私有权限的静态成员变量, 也需要在类外进行声明,否则报错
//静态成员函数不需要在类外声明
//静态成员变量
void test01()
{
//1.通过对象访问静态成员变量
Person p1;
cout << p1.m_A << endl;
//cout << p1.m_B << endl; //私有权限, 类外无法访问
Person p2;
p2.m_A = 200;
cout << p1.m_A << endl; //200, 因为静态成员变量的值,是该类所有对象共享的
//改变对象 p1 中的某个静态成员变量, 则所有 Person 类对象下该变量都一起改变
//2.通过类名访问静态成员变量
cout << Person::m_A << endl; //200,因为静态成员变量是该类所有对象共享的, 所以也可以用类名进行访问
p1.function2();
}
void test02()
{
//1.通过对象访问静态成员函数
Person p3;
p3.function();
//p3.staticfuntion(); //私有权限, 类外无法访问
//2.通过类名访问静态成员函数
Person::function();
}
int main()
{
test01();
test02();
return 0;
}
在C++中, 类 的 成员变量 和 成员函数 是分开存储的.
并且, 静态成员不在 类 的对象上.
也就是说, 通过 sizeof 求对象占用的内存空间大小, 仅包括非静态成员变量.
//成员存储方式
class A
{
};
class B
{
int m_A;
};
class C
{
void Cfunction()
{
}
static int mc_A;
static void staticCfunction()
{
}
};
int C::mc_A;
void test03()
{
A a1;
A a2;
cout << sizeof(a1) << endl; //空对象固定占据 1 个字节内存
cout << sizeof(a2) << endl; //空对象固定占据 1 个字节内存
cout << sizeof(A) << endl; //空类固定占据 1 个字节内存
B b1;
B b2;
cout << sizeof(b1) << endl; //4,仅包含一个 int 变量
cout << sizeof(b2) << endl; //4,仅包含一个 int 变量
cout << sizeof(B) << endl; //4,仅包含一个 int 变量
C c1;
cout << sizeof(c1) << endl; //1,因为成员函数无论静态与否都不存放在对象上
//静态成员变量也不存放在对象上
//所以 C 实际上是一个空类, 空类就占 1字节
}
int main()
{
test03();
return 0;
}
对于 类 中的 每个非静态成员函数, 都只会生成一个函数实例. 也就是说, 无论同类对象有多少个, 使用的都是同一段函数代码.
那么非静态成员函数是如何区分到底是哪个对象在调用它呢?
这就需要 对象指针 this 指针来解决.
this 指向被调用的成员函数所属的对象.
如 p1.function(), 则此时function()中的 this ==&p1 , 且有 *this == p1.
this 指针是隐含于每个非静态成员函数中的一种指针. "隐含"指的是隐含定义.
因此在 非静态成员函数中 , this 指针无需经过定义就可以使用.
this 指针有以下两种用途:
若调用成员函数来为成员属性赋值, 通常需要一定的命名规范.
未规范命名容易引起命名冲突, 导致赋值无效. 如下
#include
using namespace std;
class Person
{
public:
int money;
Person(int money)
{
money = money; //命名不规范, 形参值没有传递给成员属性
}
};
void test01()
{
Person p1(10);
cout << "p1.money 的值为: " << p1.money << endl;
}
int main()
{
test01();
return 0;
}
通常来说更推荐用规范命名来解决, 如 成员属性 添加前缀 "m_" (m意为 member)
class Person
{
public:
int m_money;
Person(int money)
{
m_money = money; //命名不规范, 形参值没有传递给成员属性
}
};
void test01()
{
Person p1(10);
cout << "p1.money 的值为: " << p1.m_money << endl;
}
但是也可以通过 this 指针来解决, 前面已经说过, this 指向调用的对象本身.
由于 this 是指针, 所以需要 -> 运算符来指向. 当然, 也可以通过解引用的方式来解决.
class Person
{
public:
//int m_money; //规范命名
int money;
Person(int money) //有参构造
{
//m_money = money; //规范命名
//money = money; //命名不规范, 形参值没有传递给成员属性
this->money = money;
//(*this).money = money; //与上一行等价
}
};
this 的另一个用途是在 非静态成员函数 中返回对象本身.
若函数返回对象本身, 那么通过连续的" . " 就可以单行实现多次函数套用.
这就是 链式编程思想.
假如想创建一个 p2, 并让 p1 的 money 加在 p2 的身上. ( p1 的 money 不变.)
class Person
{
public:
//int m_money; //规范命名
int money;
Person(int money) //有参构造
{
//m_money = money; //规范命名
//money = money; //命名不规范, 形参值没有传递给成员属性
this->money = money;
//(*this).money = money; //与上一行等价
}
void personAddMoney(Person& p) //将 p1 的 money 加到 p2 上
{
this->money += p.money;
}
};
void test01()
{
//解决命名冲突
Person p1(10);
cout << "p1.money 的值为: " << p1.money << endl;
//返回对象本身与链式编程思想
Person p2(10);
p2.personAddMoney(p1);
cout << "p2.money 的值为: " << p2.money << endl;
}
这时, 如果想加多次, 能不能多次调用函数呢?
p2.personAddMoney(p1).personAddMoney(p1).personAddMoney(p1); //不行, 返回值类型是 void
答案是不行, 因为这个函数的返回值类型是void.
那么将返回值类型修改为 Person , 并且 return *this
class Person
{
public:
//int m_money; //规范命名
int money;
Person(int money) //有参构造
{
//m_money = money; //规范命名
//money = money; //命名不规范, 形参值没有传递给成员属性
this->money = money;
//(*this).money = money; //与上一行等价
}
Person personAddMoney(Person& p) //将 p1 的 money 加到 p2 上
{
this->money += p.money;
return *this;
}
};
void test01()
{
//解决命名冲突
Person p1(10);
cout << "p1.money 的值为: " << p1.money << endl;
//返回对象本身与链式编程思想
Person p2(10);
//p2.personAddMoney(p1);
p2.personAddMoney(p1).personAddMoney(p1).personAddMoney(p1);
cout << "p2.money 的值为: " << p2.money << endl;
}
结果是20, 不是我们想要的40.
来看看程序都做了什么 :
首先, p2.personAddMoney(p1) , 函数执行过程中, this 指向 p2, 所以 p2. money 加10.
但是在C++中, 以值的方式返回局部对象, 会调用拷贝构造函数, 最终返回的不是 p2 本身, 而是一个新的匿名对象.
p2.personAddMoney(p1) 这就是新的匿名对象
p2.personAddMoney(p1).personAddMoney(p1)
等于
新的匿名对象.personAddMoney(p1)
那么下一个 this 就会指向新的匿名对象, 然后 this 不断地指向更新的匿名对象, p2.money 无变化.
所以最后得到 p2.money 只加了最开始那一次 10.
为了解决这个问题, 只需要在返回值类型后加 &. 这表示引用返回.
Person& personAddMoney(Person& p) //将 p1 的 money 加到 p2 上
//一定要返回引用, 直接返回值会返回 p2 的拷贝副本, 导致下个指针不指向 p2
{
this->money += p.money;
return *this;
}
};
那么编译器就不会返回 p2 的副本, 而是切实地返回 p2 本身.
输出值也变成我们想要的 40 了.
p2.personAddMoney(p1) 引用返回,返回的还是p2
p2.personAddMoney(p1).personAddMoney(p1)
等于
p2.personAddMoney(p1)
在 C++ 中, 允许使用 对象指针 中的 空指针 访问 成员函数.
比如 int * 代表指向整型数据的指针, 那么 类名 * 就代表指向 类 类型(对象)的指针, 或者叫对象指针.
空指针访问成员函数是有条件的 : 成员函数中不能用到 this 指针.
下面这段代码就有一处违反了条件 , 程序虽然可以编译运行, 但是main返回值不为 0 ,说明有异常.
#include
using namespace std;
class Person
{
public:
int m_age;
void showClassName()
{
cout << "类名为: Person" << endl;
}
void showPersonAge()
{
cout << m_age << endl;
}
};
void test01()
{
Person* p = nullptr;
p->showClassName();
p->showPersonAge();
}
int main()
{
test01();
return 0;
}
明明没有写 this 指针, 为什么还是违反规则了呢?
这是因为, 在非静态成员函数中, 自动对成员属性隐含了 this 指针.
void showPersonAge()
{
//cout << m_age << endl; //等于下面这行
cout << this->m_age << endl; //非静态成员函数中隐含 this 指针
}
因为 p 本身就是一个空指针, 没有指向明确对象, 当然无法访问对象中的属性了.
但是 showClassName() 这个函数执行时没有用到成员属性, 也没有 this 指针, 所以没有违反规则.
这就是空指针访问成员函数.
解决 空指针与 this 冲突有以下几种方式 :
1. 前置判断(推荐)
void showPersonAge()
{
if (this == nullptr)
{
return;
}
cout << m_age << endl; //等价于 cout << this->m_age <
检测到 this(也就是 p) 为空指针的时候, 自动退出这个函数.
因为返回值类型是 void , 所以 return 后面直接分号, 不能加其他东西.
2. 静态成员
静态成员函数中不允许存在 this 指针, 当然就不会冲突, 但静态成员函数只能使用静态成员变量
class Person
{
public:
static int m_age;
void showClassName()
{
cout << "类名为: Person" << endl;
}
static void showPersonAge()
{
cout << m_age << endl; //等价于 cout << this->m_age <
3. 规范代码
注意函数中不要调用成员属性, 也不要调用 this 指针, 或者明确指针对象, 主动避免问题.
在成员函数的 函数名() 后面加 const 修饰 可以使其变为常函数.
常函数内, 不可以修改普通成员属性. 若想修改, 需要在属性前加 mutable 修饰.
声明对象时, 在 对象名前面加 const 修饰 可以使其变为常对象.
常对象只能调用常函数.
#include
using namespace std;
class Person
{
public:
int m_A;
mutable int m_B;
void Personcosnt() const //隐含this 是指针常量, 指向不允许改变, 如 Person * const this
//现在又加了 const , 变成了 const Person * const this, 指向的值也不允许改变了
{
//m_A = 100; //报错, 常函数内不允许修改普通成员变量
m_B = 100; //正常, 常函数内可以修改 mutable 修饰的成员变量
//this = nullptr; //this 的指向本身就是不允许修改的
}
void function() //非常函数
{
}
};
void test01()
{
Person p1;
p1.Personcosnt(); //非常对象可以访问常函数
p1.function(); //非常对象可以访问非常函数
const Person p2;
p2.Personcosnt(); //常对象可以访问常函数
//p2.function(); //常对象不允许访问非常函数
}
int main()
{
test01();
return 0;
}
友元的目的是让一个 函数 或者一个 类 去访问 另一个类 中的 私有成员.
友元的关键字为 friend
即使用全局函数访问类中的私有成员.
这样做需要在目标类中写 友元声明, 其实就是函数声明前面加一个 friend.
友元声明不需要写权限就能生效, 且声明中的内容实现不需要写在该类的前面.
首先需要实例化一个对象, 然后将对象传给全局函数的参数.
根据传递方式的不同, 可以分为 值传递, 地址传递 和 引用传递.
若使用地址传递, 请注意传递的是地址, 而不是对象, 因此需要用 -> 操作符.
#include
using namespace std;
class Person
{
friend void functionptr(Person* p);
friend void functionref(Person& p);
friend void functionval(Person p);
public:
Person()
{
name = "张三";
age = 18;
}
string name;
private:
int age;
};
void functionptr(Person* p)
{
cout << "name = : " << p->name << endl;
cout << "age = : " << p->age << endl;
}
void functionref(Person& p)
{
cout << "name = : " << p.name << endl;
cout << "age = : " << p.age << endl;
}
void functionval(Person p)
{
cout << "name = : " << p.name << endl;
cout << "age = : " << p.age << endl;
}
void test01()
{
Person p;
functionptr(&p);
functionref(p);
functionval(p);
}
int main()
{
test01();
return 0;
}
可以看到三种传递方式都成功访问到了私有属性.
即 类 做友元. 友元类可以访问目标类中的私有属性成员.
类做友元时, 友元类不需要在被访问的类之前声明. 如下面代码中, Brotheres 是 Person 的友元,
Brotheres 中的成员属性和成员函数可以访问 Person 中的私有属性, 尽管 Person 的定义包含
friend class Brotheres ; 这一行, 也不需要在 Person 之前就定义 Brotheres.
总而言之, 友元声明对其中涉及的类没有前置声明需求.这一点很重要.
有两种方式, 第一种需要分别将友元类和目标类进行实例化.
此处 Person 的构造函数采用类类外实现的方式, 即类内声明, 类外实现.
#include
using namespace std;
class Person
{
friend class Brotheres;
public:
Person();
string m_Name;
private:
string m_Room;
};
class Brotheres
{
public:
void visit(Person &p)
{
cout << p.m_Name << endl;
cout << p.m_Room << endl;
}
};
Person::Person()
{
m_Name = "张三";
m_Room = "卧室";
}
void test01()
{
Person p;
Brotheres b;
b.visit(p);
}
int main()
{
test01();
return 0;
}
第二种只需要对友元类进行实例化, 实现起来麻烦一些, 但是不用再临时实例化一个目标类对象.
在友元类 Brotheres 中添加 目标类指针 (Person * 类型), 同时在友元类 Brotheres 默认构造中开辟堆区内存, 存放数据类型为目标类类型 (Person 类型), 并将指针指向该地址.
其实就是在友元类 Brotheres 中通过函数的方式隐含了目标类 Person 的实例化.
这样只需要调用 Brotheres 的函数, 且无需传参, 就能完成整个私有成员访问的过程.
#include
using namespace std;
class Person
{
friend class Brotheres;
public:
Person();
string m_Name;
private:
string m_Room;
};
class Brotheres
{
public:
Brotheres()
{
p = new Person;
}
void visit()
{
cout << p->m_Name << endl;
cout << p->m_Room << endl;
}
Person* p;
};
Person::Person()
{
m_Name = "张三";
m_Room = "卧室";
}
void test01()
{
Brotheres b;
b.visit();
}
int main()
{
test01();
return 0;
}
学到这里容易迷糊, 为什么 Brotheres 里面非得用 Person 的指针, 还得在堆区开辟内存?
首先, 这里可以不用指针, 但在下面的成员函数做友元时, 你不得不用指针.因此在写友元类(即类内属性可以访问它类私有属性的类)的时候, 如果需要将其他类的对象作为自己的成员, 最好写成指针的形式, 这是一个好习惯.
然后, new 开辟堆区内存是因为, 如果单纯的 Person * p ; 实际上并没有给指针明确指向, 而且你也没有对目标类进行实例化, 又怎么访问目标类中的私有属性呢? 对象 b 睁眼一看, 你这 p 是个空指针啊, 你让我 visit , 我 visit 谁啊? 所以, 必须调用 Brotheres 的构造函数来给 p 赋值, 比如说 new 一块堆区内存, new Person 代表这块内存存储 Person 类型的数据, 而这个表达式会返回一个 Person * 类型的数据, 恰好和 p 的数据类型相符. 而在这个过程中, 也会调用 Person 的构造函数, 那么也就相当于完成了实例化, 等下 b 再找过来的时候, 就不会出现找不到的问题了.
如果你不想这么麻烦的话, 那还是老老实实地把友元类和目标类都实例化一遍吧.
但如果在这方面偷懒, 接下来的成员函数做友元学起来就更困难了.
成员函数做友元 原理非常简单, 都是同一个模板, 加个作用域就完事了!
friend Brotheres::visit();
但是实际实现起来却有一堆问题, 你会被各种报错搞到怀疑人生.
而这一切都是声明引起的.
之前我们说过, 友元声明对前置声明没有需求.
但是, 作用域有需求, 而且声明都不行, 还必须定义.
你用 Brotheres:: 这个作用域, Brotheres 就必须是已经定义过的类.
那就只能把 Brotheres 的定义写在 Person 上面.
然后你又发现, Brotheres 里面有 Person 的对象, 又报错.
又尝试在 Brotheres 前面再加 Person 的声明, 还报错.
因为你必须先定义 Person 才能用 Person 的对象.
这下尴尬了, 你俩没完了 ? ! 那到底怎么写 ?
首先, 作用域这个事是没办法的, Brotheres 必须在 Person 前面定义. 然后, 我们可以通过 前置声明 的方式, 来使用一个不完全的类(即经过声明, 但尚未定义的类)
C++里类的前置声明分析_爱就是恒久忍耐的博客-CSDN博客
对于一个不完全的类, 只能用以下两种方式使用 :
1. 定义指向这种类型的数据的指针.
Person * p ;
那能不能直接在创建指针的时候 new , 一步到位呢?
不行. new Person 需要定义过的 Person 类.
2. 声明一个返回值类型为该类型的函数.
Person function ();
是的, 只能是声明, 定义都不行.
这下知道为什么要大费周章地用 指针 和 new 了吧?
说白了就是 Brotheres 不得不在 Person 前面, 那么对于 Brotheres 的属性来说, Person 必然是不完全类型, 因此只能用 Person * , 你还不能 Person * p = new Person 一步到位, 因为 new Person 必须放在 Person 的定义后面, 所以还得把 Brotheres 的构造函数在类外实现, 还得把这个构造函数写在 Person 定义的后面.
绷不住了.
总结一下, 就是
Person 声明 -> Brotheres 定义(+构造函数声明) -> Person 定义 -> Brotheres 构造函数定义
#include
using namespace std;
class Person;
class Brotheres
{
public:
Brotheres();
void visit();
private:
Person* p;
};
class Person
{
//friend class Brotheres;
friend void Brotheres::visit();
public:
Person();
string m_Name;
private:
string m_Room;
};
Person::Person()
{
m_Name = "张三";
m_Room = "卧室";
}
Brotheres::Brotheres()
{
p = new Person;
}
void Brotheres::visit()
{
cout << p->m_Name << endl;
cout << p->m_Room << endl;
}
void test01()
{
//Person p;
Brotheres b;
b.visit();
}
int main()
{
test01();
return 0;
}
C++ 类声明 类前置声明范例_c++ 类模板 前置声明_zhangatong的博客-CSDN博客
C++知识分享:前置声明及其解析_头文件前置声明_一起学编程的博客-CSDN博客
通常来说, 两个整型数据可以相加, 但是两个类的对象无法相加, 因为编译器不知道怎么处理对象的属性.比如类 A 里面有两个属性, 实例化两个 A 类对象, 分别为 a1, a2.
若给出 a1 + a2 的指令, 那么这四个属性之间到底怎么相加呢? 得到的结果又怎么和新对象对应呢?
这时候就需要用到运算符重载.
运算符重载的作用是, 让程序员自己定义未定义的操作符运算.
原始的运算符重载 :
上面的问题, 可以通过函数来进行解决.
class A
{
public:
int m_A ;
int m_B ;
A()
{
m_A = 10;
m_B = 10;
}
};
A add_A(A & a1, A & a2)
{
A temp ;
temp.m_A = a1.m_A + a2.m_A ;
temp.m_B = a1.m_B + a2.m_B ;
return temp;
}
int main()
{
A a1;
A a2;
A a3 = add_A(a1,a2);
cout << a3.m_A << a3.m_B << endl;
return 0;
}