常对象与常对象成员
常对象
定义
const 修饰的类对象称为常对象,核心是对象的所有数据成员具备只读属性,且必须在定义时完成初始化。有两种等价的定义格式:
const 类名 对象名(初始化参数); // 标准格式
类名 const 对象名(初始化参数); // 等价格式
核心特性
- 必须初始化:定义时需通过类的构造函数完成初始化,不能后续赋值;
- 数据不可变:所有非
mutable数据成员变为只读,无法通过常对象修改;mutable是 C++ 专属关键字,仅用于修饰类的非静态数据成员,核心作用是豁免 const 的只读限制
- 调用限制:只能调用类的
const成员函数(避免函数修改数据),无法调用非常成员函数。
示例代码
#include <iostream>
using namespace std;class Cube {
public:int A, B; // 普通数据成员/*** @brief 默认构造函数,初始化Cube对象的A和B* @return 无返回值(构造函数无返回类型)* @note 常对象若使用默认构造,也需确保数据成员被初始化*/Cube() : A(11), B(22) {cout << "默认构造函数执行" << endl;}/*** @brief 参数化构造函数,自定义初始化Cube对象的A和B* @param a 初始化数据成员A的整数值* @param b 初始化数据成员B的整数值* @return 无返回值(构造函数无返回类型)* @note 常对象必须通过构造函数完成初始化,此为最常用方式*/Cube(int a, int b) : A(a), B(b) {cout << "参数化构造函数执行" << endl;}/*** @brief 常成员函数,展示Cube对象的A和B值* @return 无返回值* @note const修饰表示该函数不修改对象数据,仅读取,可被常对象调用*/void show() const {cout << "A=" << A << ", B=" << B << endl;// A = 100; // 错误:常成员函数不能修改数据成员}/*** @brief 非常成员函数,尝试修改数据成员* @return 无返回值* @note 常对象无法调用此函数,仅非常对象可调用*/void modify(int a, int b) {A = a;B = b;}
};int main() {// 正确:常对象通过参数化构造初始化const Cube obj(10, 20);// obj.A = 30; // 错误:常对象的Data成员只读,无法修改// obj.modify(5,6); // 错误:常对象不能调用非常成员函数obj.show(); // 正确:常对象可调用常成员函数// 非常对象:可调用常/非常成员函数Cube obj2;obj2.modify(33, 44); // 调用非常成员函数修改数据obj2.show(); // 调用常成员函数展示数据return 0;
}
常对象成员
常对象成员分为常数据成员和常成员函数,是类中限制数据修改、保证代码健壮性的核心机制。
常数据成员
定义
const 修饰的类数据成员,代表该成员在对象生命周期内不可修改。
核心规则
必须通过构造函数的初始化列表初始化,不能在构造函数体内部赋值(构造函数体执行时,数据成员已完成初始化,const 成员无法二次赋值)。
示例代码
#include <iostream>
using namespace std;class Test {
private:/*** @brief 常数据成员,代表对象的唯一标识,生命周期内不可修改* @note 必须通过构造函数初始化列表赋值,无法在构造函数体修改*/const int ID;public:/*** @brief 构造函数,初始化常数据成员ID* @param id 初始化ID的整数值* @return 无返回值(构造函数无返回类型)* @note 常数据成员ID必须放在初始化列表中初始化,此为唯一合法方式*/Test(int id) : ID(id) {// ID = id; // 错误:构造函数体中无法给const成员赋值}/*** @brief 常成员函数,获取常数据成员ID的值* @return int 返回ID的整数值* @note 仅读取数据,不修改,符合常成员函数规则*/int getID() const {return ID;}
};int main() {Test t1(1001); // 正确:初始化常数据成员ID为1001cout << "ID: " << t1.getID() << endl; // 输出:ID: 1001// Test t2; // 错误:默认构造函数未初始化const成员IDreturn 0;
}
常成员函数
定义
函数声明末尾加 const 修饰的成员函数,本质是 “承诺该函数不会修改对象的非 mutable 数据成员”。
核心规则
- 不能修改对象的非
mutable数据成员; - 不能调用类中的非常成员函数(避免间接修改数据),但非常成员函数可调用常成员函数;
- 常对象只能调用常成员函数,非常对象可调用常 / 非常成员函数;
const可参与函数重载(区分常对象 / 非常对象的调用)。
示例代码
#include <iostream>
using namespace std;class Account {
private:double balance; // 账户余额(普通数据成员)public:/*** @brief 构造函数,初始化账户余额* @param bal 初始余额的浮点数值* @return 无返回值(构造函数无返回类型)*/Account(double bal) : balance(bal) {}/*** @brief 常成员函数,获取账户余额* @return double 返回当前账户余额* @note const修饰,承诺不修改balance,可被常对象调用*/double getBalance() const {// balance = 0; // 错误:常成员函数不能修改数据成员return balance;}/*** @brief 非常成员函数,修改账户余额* @param bal 新的账户余额值* @return 无返回值* @note 仅非常对象可调用,可调用常成员函数*/void setBalance(double bal) {balance = bal;cout << "新余额:" << getBalance() << endl; // 调用常成员函数}
};// 常成员函数重载示例
class Demo {
public:/*** @brief 非常版本的func函数,供非常对象调用* @return 无返回值*/void func() {cout << "普通版本func(非常对象调用)" << endl;}/*** @brief const版本的func函数,供常对象调用* @return 无返回值* @note const修饰,与普通版本构成重载,编译器根据对象类型匹配*/void func() const {cout << "const版本func(常对象调用)" << endl;}
};int main() {// 常对象调用常成员函数const Account acc1(1000.5);cout << "常对象账户余额:" << acc1.getBalance() << endl;// acc1.setBalance(2000); // 错误:常对象不能调用非常成员函数// 非常对象调用常/非常成员函数Account acc2(500);acc2.setBalance(1500); // 调用非常成员函数cout << "非常对象账户余额:" << acc2.getBalance() << endl; // 调用常成员函数// 常成员函数重载测试Demo d1; // 非常对象const Demo d2; // 常对象d1.func(); // 匹配普通版本:输出"普通版本func(非常对象调用)"d2.func(); // 匹配const版本:输出"const版本func(常对象调用)"return 0;
}
运算符重载
基本概念
本质
运算符重载是特殊的函数重载,目的是让自定义类对象能像内置类型(int/float 等)一样使用运算符(如 +/>/= 等)。
语法格式
返回类型 operator运算符符号(参数列表);
核心限制(必须遵守)
- 不能改变原运算符的优先级(如
+始终低于 ); - 不能改变原运算符的结合性(如
=是右结合,+是左结合); - 不能使用默认参数(运算符操作数个数固定,默认参数会破坏逻辑);
- 不能改变原运算符的操作数个数(如
+是双目运算符,重载后仍需两个操作数); - 不能改变运算符的语义(如
+不能实现减法逻辑); - 可重载用于:自定义类对象之间、自定义类对象与内置类型之间;
- 不可重载的运算符:
.(成员访问)、.*(指向成员的指针访问)、::(作用域解析)、?:(三目条件)、sizeof(大小计算)。
典型运算符的实现方式
运算符重载主要有三种实现方式:成员函数、友元函数、普通函数,不同运算符适配不同方式(如 = 只能用成员函数)。
算术运算符(以 + 为例)
方式 1:成员函数实现
#include <iostream>
using namespace std;class Vector {
private:int x, y; // 向量的x、y分量public:/*** @brief 构造函数,初始化向量的x、y分量* @param x 向量x分量的整数值* @param y 向量y分量的整数值* @return 无返回值(构造函数无返回类型)*/Vector(int x = 0, int y = 0) : x(x), y(y) {}/*** @brief 成员函数重载+运算符,实现两个向量相加* @param v 右操作数(待相加的另一个Vector对象)* const:避免修改传入的对象;引用:避免拷贝,提升效率* @return Vector 返回两个向量相加后的新Vector对象* @note 成员函数隐含this指针(指向左操作数),因此仅需一个显式参数*/Vector operator+(const Vector& v) const {// this->x 是左操作数的x,v.x是右操作数的x,相加后构造新对象返回return Vector(this->x + v.x, this->y + v.y);}/*** @brief 常成员函数,展示向量的x、y分量* @return 无返回值*/void show() const {cout << "Vector(" << x << ", " << y << ")" << endl;}
};int main() {Vector v1(1, 2), v2(3, 4);Vector v3 = v1 + v2; // 等价于v1.operator+(v2)v3.show(); // 输出:Vector(4, 6)return 0;
}
方式 2:友元函数实现
#include <iostream>
using namespace std;class Vector {
private:int x, y; // 私有分量,需友元函数访问/*** @brief 友元函数重载+运算符,实现两个向量相加* @param v1 左操作数(第一个Vector对象)* const:避免修改;引用:避免拷贝* @param v2 右操作数(第二个Vector对象)* const:避免修改;引用:避免拷贝* @return Vector 返回相加后的新Vector对象* @note 友元函数无this指针,需显式传入两个操作数,可直接访问私有成员*/friend Vector operator+(const Vector& v1, const Vector& v2);public:Vector(int x = 0, int y = 0) : x(x), y(y) {}void show() const {cout << "Vector(" << x << ", " << y << ")" << endl;}
};// 友元函数的实现(类外定义,无需加Vector::)
Vector operator+(const Vector& v1, const Vector& v2) {return Vector(v1.x + v2.x, v1.y + v2.y);
}int main() {Vector v1(1, 2), v2(3, 4);Vector v3 = v1 + v2; // 等价于operator+(v1, v2)v3.show(); // 输出:Vector(4, 6)return 0;
}
方式 3:普通函数实现
#include <iostream>
using namespace std;class Vector {
public:int x, y; // 需公开数据成员,否则普通函数无法访问Vector(int x = 0, int y = 0) : x(x), y(y) {}void show() const {cout << "Vector(" << x << ", " << y << ")" << endl;}
};/*** @brief 普通函数重载+运算符,实现两个向量相加* @param v1 左操作数(第一个Vector对象)* const:避免修改;引用:避免拷贝* @param v2 右操作数(第二个Vector对象)* const:避免修改;引用:避免拷贝* @return Vector 返回相加后的新Vector对象* @note 普通函数无this指针,需两个显式参数,仅能访问类的公有成员*/
Vector operator+(const Vector& v1, const Vector& v2) {return Vector(v1.x + v2.x, v1.y + v2.y);
}int main() {Vector v1(1, 2), v2(3, 4);Vector v3 = v1 + v2; // 等价于operator+(v1, v2)v3.show(); // 输出:Vector(4, 6)return 0;
}
关系运算符(以 > 为例)
方式 1:成员函数实现
#include <iostream>
using namespace std;class Person {
private:int age; // 私有年龄成员public:/*** @brief 构造函数,初始化Person的年龄* @param age 年龄整数值* @return 无返回值(构造函数无返回类型)*/Person(int age) : age(age) {}/*** @brief 成员函数重载>运算符,比较两个Person的年龄* @param p 右操作数(待比较的另一个Person对象)* const:避免修改;引用:避免拷贝* @return bool 比较结果:true表示当前对象年龄更大,false反之* @note 隐含this指针指向左操作数,访问私有成员age无需额外权限*/bool operator>(const Person& p) const {return this->age > p.age;}
};int main() {Person p1(25), p2(20);// 等价于p1.operator>(p2)if (p1 > p2) {cout << "p1年龄更大" << endl; // 输出此内容}return 0;
}
方式 2:友元函数实现
#include <iostream>
using namespace std;class Person {
private:int age; // 私有年龄成员/*** @brief 友元函数重载>运算符,比较两个Person的年龄* @param p1 左操作数(第一个Person对象)* const:避免修改;引用:避免拷贝* @param p2 右操作数(第二个Person对象)* const:避免修改;引用:避免拷贝* @return bool 比较结果:true表示p1年龄更大,false反之* @note 无this指针,需两个显式参数,可直接访问私有成员age*/friend bool operator>(const Person& p1, const Person& p2);public:Person(int age) : age(age) {}
};// 友元函数实现
bool operator>(const Person& p1, const Person& p2) {return p1.age > p2.age;
}int main() {Person p1(25), p2(20);if (p1 > p2) { // 等价于operator>(p1, p2)cout << "p1年龄更大" << endl;}return 0;
}
方式 3:普通函数实现
#include <iostream>
using namespace std;class Person {
public:int age; // 公开年龄成员,供普通函数访问Person(int age) : age(age) {}
};/*** @brief 普通函数重载>运算符,比较两个Person的年龄* @param p1 左操作数(第一个Person对象)* const:避免修改;引用:避免拷贝* @param p2 右操作数(第二个Person对象)* const:避免修改;引用:避免拷贝* @return bool 比较结果:true表示p1年龄更大,false反之* @note 仅能访问类的公有成员,无this指针*/
bool operator>(const Person& p1, const Person& p2) {return p1.age > p2.age;
}int main() {Person p1(25), p2(20);if (p1 > p2) { // 等价于operator>(p1, p2)cout << "p1年龄更大" << endl;}return 0;
}
赋值运算符(=)
核心规则
= 只能以成员函数实现(C++ 语法强制规定),且必须实现深拷贝(避免浅拷贝导致的内存泄漏 / 重复释放)。
示例代码(深拷贝版)
#include <iostream>
#include <algorithm> // 包含std::copy函数using namespace std;class Array {
private:int* data; // 动态数组指针int size; // 数组大小public:/*** @brief 默认构造函数,初始化空数组* @return 无返回值(构造函数无返回类型)* @note data初始化为nullptr,size初始化为0,避免野指针*/Array() : data(nullptr), size(0) {cout << "默认构造函数执行" << endl;}/*** @brief 析构函数,释放动态数组内存* @return 无返回值* @note 避免内存泄漏,必须释放data指向的堆内存*/~Array() {delete[] data;cout << "析构函数执行,释放内存" << endl;}/*** @brief 拷贝构造函数,实现深拷贝* @param other 待拷贝的源Array对象* const:避免修改源对象;引用:避免递归拷贝* @return 无返回值(构造函数无返回类型)* @note 重新分配内存,拷贝源对象的数据,避免浅拷贝导致的内存共享*/Array(const Array& other) : size(other.size) {// 分配新内存data = new int[size];// 拷贝源对象的数据到新内存std::copy(other.data, other.data + size, data);cout << "拷贝构造函数执行(深拷贝)" << endl;}/*** @brief 成员函数重载=运算符,实现深拷贝赋值* @param other 待拷贝的源Array对象* const:避免修改源对象;引用:避免拷贝,提升效率* @return Array& 返回当前对象的引用,支持连续赋值(如a = b = c)* @note 核心步骤:1. 检查自赋值;2. 释放原有内存;3. 分配新内存;4. 拷贝数据;5. 返回自身引用*/Array& operator=(const Array& other) {// 1. 检查自赋值(避免释放自身内存后拷贝)if (this != &other) {// 2. 释放当前对象的原有内存delete[] data;// 3. 分配新内存,匹配源对象大小size = other.size;data = new int[size];// 4. 拷贝源对象的数据到新内存std::copy(other.data, other.data + size, data);cout << "赋值运算符执行(深拷贝)" << endl;}// 5. 返回自身引用,支持连续赋值return *this;}/*** @brief 初始化数组数据(辅助函数)* @param arr 待拷贝的整型数组* @param s 数组大小* @return 无返回值*/void init(int* arr, int s) {delete[] data; // 释放原有内存size = s;data = new int[size];std::copy(arr, arr + size, data);}/*** @brief 展示数组内容(辅助函数)* @return 无返回值*/void show() const {for (int i = 0; i < size; ++i) {cout << data[i] << " ";}cout << endl;}
};int main() {int arr1[] = {1, 2, 3};int arr2[] = {4, 5, 6, 7};Array a1, a2;a1.init(arr1, 3);a2.init(arr2, 4);a1 = a2; // 调用赋值运算符(深拷贝)a1.show(); // 输出:4 5 6 7Array a3 = a2; // 调用拷贝构造函数(非赋值运算符)a3.show(); // 输出:4 5 6 7return 0;
}
输入输出运算符(<</>>)
输出运算符(<<):友元函数实现(推荐)
#include <iostream>
#include <string>
using namespace std;class Person {
private:string name; // 私有姓名int age; // 私有年龄/*** @brief 友元函数重载<<运算符,输出Person对象信息* @param os 输出流对象(如cout)* 引用:ostream对象无法拷贝(拷贝构造函数protected),必须传引用* @param p 待输出的Person对象* const:避免修改;引用:避免拷贝* @return ostream& 返回输出流引用,支持连续输出(如cout << p1 << p2)* @note 无法用成员函数实现(成员函数的this指针会占据左侧操作数位置,而<<左侧需是ostream对象)*/friend ostream& operator<<(ostream& os, const Person& p);public:/*** @brief 构造函数,初始化Person的姓名和年龄* @param name 姓名字符串* @param age 年龄整数值* @return 无返回值(构造函数无返回类型)*/Person(string name, int age) : name(name), age(age) {}
};// 友元函数实现
ostream& operator<<(ostream& os, const Person& p) {os << "姓名:" << p.name << ",年龄:" << p.age;return os; // 返回流引用,支持连续输出
}int main() {Person p("张三", 25);// 等价于operator<<(cout, p),连续输出cout << p << " | 额外信息" << endl; // 输出:姓名:张三,年龄:25 | 额外信息return 0;
}
输入运算符(>>):友元函数实现(推荐)
#include <iostream>
#include <string>
using namespace std;class Person {
private:string name;int age;/*** @brief 友元函数重载>>运算符,读取输入到Person对象* @param is 输入流对象(如cin)* 引用:istream对象无法拷贝,必须传引用* @param p 待赋值的Person对象* 引用:需要修改对象的成员,不能加const* @return istream& 返回输入流引用,支持连续输入(如cin >> p1 >> p2)*/friend istream& operator>>(istream& is, Person& p);public:Person() : name(""), age(0) {}void show() const {cout << "姓名:" << name << ",年龄:" << age << endl;}
};// 友元函数实现
istream& operator>>(istream& is, Person& p) {// 读取姓名和年龄到p的私有成员is >> p.name >> p.age;return is; // 返回流引用,支持连续输入
}int main() {Person p;cout << "请输入姓名和年龄:";cin >> p; // 等价于operator>>(cin, p)p.show(); // 输出输入的姓名和年龄return 0;
}
递增运算符(++)
核心规则
- 前置
++:无参数,返回对象引用(支持连续递增++(++a)); - 后置
++:带int哑元参数(仅用于区分重载),返回对象副本(先返回原值,再自增)。
方式 1:成员函数实现
#include <iostream>
using namespace std;class Counter {
private:int value; // 计数器值public:/*** @brief 构造函数,初始化计数器值* @param val 初始计数值,默认0* @return 无返回值(构造函数无返回类型)*/Counter(int val = 0) : value(val) {}/*** @brief 成员函数重载前置++运算符,实现计数器自增* @return Counter& 返回当前对象的引用,支持连续递增* @note 先自增,再返回自身引用,无参数*/Counter& operator++() {++value; // 先自增return *this; // 返回自身引用}/*** @brief 成员函数重载后置++运算符,实现计数器自增* @param int 哑元参数(无实际意义,仅区分前置/后置重载)* @return Counter 返回自增前的对象副本* @note 先保存副本,再自增,最后返回副本*/const Counter operator++(int) {Counter temp = *this; // 保存当前状态(自增前的值)++value; // 自增return temp; // 返回副本}/*** @brief 常成员函数,获取计数器值* @return int 当前计数值*/int getValue() const {return value;}
};int main() {Counter c(5);// 前置++:先自增,再取值++c;cout << "前置++后:" << c.getValue() << endl; // 输出:6// 后置++:先取值,再自增Counter c2 = c++;cout << "后置++后c的值:" << c.getValue() << endl; // 输出:7cout << "后置++返回的c2值:" << c2.getValue() << endl; // 输出:6// 连续前置++++(++c);cout << "连续前置++后:" << c.getValue() << endl; // 输出:9return 0;
}
方式 2:友元函数实现
#include <iostream>
using namespace std;class Counter {
private:int value;/*** @brief 友元函数重载前置++运算符* @param c 待自增的Counter对象* 引用:需要修改对象,且避免拷贝* @return Counter& 返回对象引用,支持连续递增*/friend Counter& operator++(Counter& c);/*** @brief 友元函数重载后置++运算符* @param c 待自增的Counter对象* 引用:需要修改对象,且避免拷贝* @param int 哑元参数,区分前置/后置* @return Counter 返回自增前的对象副本*/friend Counter operator++(Counter& c, int);public:Counter(int val = 0) : value(val) {}int getValue() const {return value;}
};// 前置++实现
Counter& operator++(Counter& c) {++c.value;return c;
}// 后置++实现
Counter operator++(Counter& c, int) {Counter temp = c;++c.value;return temp;
}int main() {Counter c(5);++c;cout << "前置++后:" << c.getValue() << endl; // 6Counter c2 = c++;cout << "后置++后c的值:" << c.getValue() << endl; // 7cout << "后置++返回的c2值:" << c2.getValue() << endl; // 6return 0;
}
右移运算符(>>)
场景 1:位运算右移(成员函数实现)
#include <iostream>
using namespace std;class BitMask {
private:int value; // 位掩码值public:/*** @brief 构造函数,初始化位掩码值* @param val 位掩码初始值* @return 无返回值(构造函数无返回类型)*/BitMask(int val) : value(val) {}/*** @brief 成员函数重载>>运算符,实现位运算右移* @param shift 右移的位数(整数值)* @return BitMask 返回右移后的新BitMask对象* @note 隐含this指针指向左操作数(BitMask对象),右操作数是移位位数*/BitMask operator>>(int shift) const {return BitMask(value >> shift);}/*** @brief 常成员函数,获取位掩码的十进制值* @return int 十进制值*/int getValue() const {return value;}
};int main() {BitMask bm(8); // 8的二进制:1000BitMask bm2 = bm >> 2; // 右移2位,二进制:0010(十进制2)cout << "右移2位后的值:" << bm2.getValue() << endl; // 输出:2return 0;
}
运算符重载实现方式对比表
| 运算符 | 成员函数实现特点 | 友元函数实现特点 | 普通函数实现限制 |
|---|---|---|---|
+ |
隐含 this 指针(左操作数),仅需 1 个显式参数; 可直接访问私有成员 |
无 this 指针,需 2 个显式参数; 可直接访问私有成员(需友元声明) |
无 this 指针,需 2 个显式参数; 仅能访问公有成员 |
> |
隐含 this 指针(左操作数),仅需 1 个显式参数; 可直接访问私有成员 |
无 this 指针,需 2 个显式参数; 可直接访问私有成员(需友元声明) |
无 this 指针,需 2 个显式参数; 仅能访问公有成员 |
= |
必须作为成员函数实现; 隐含 this 指针(左操作数); 支持深拷贝; 返回自身引用支持连续赋值 |
不允许(C++ 语法强制限制) | 不允许(C++ 语法强制限制) |
<<(输出流) |
无法实现(左侧需是 ostream 对象, 成员函数的 this 指针会占据左侧位置) |
标准实现方式; 无 this 指针,需 2 个显式参数; 可直接访问私有成员 |
需访问公有成员; 无 this 指针,需 2 个显式参数 |
>>(输入流) |
无法实现(左侧需是 istream 对象) | 标准实现方式; 无 this 指针,需 2 个显式参数; 可直接修改对象成员 |
需访问 / 修改公有成员; 无 this 指针,需 2 个显式参数 |
++ |
前置无参数 / 后置带 int 哑元; 隐含 this 指针; 可直接访问私有成员 |
前置 / 后置参数与成员函数一致; 需友元声明; 可直接访问私有成员 |
需访问公有成员; 参数与友元函数一致 |
>>(位运算) |
隐含 this 指针(左操作数), 右操作数为移位位数; 可直接访问私有成员 |
无 this 指针,需 2 个显式参数(对象 + 移位位数); 需友元声明 |
仅能访问公有成员; 需 2 个显式参数 |