C++之面向对象
C++面向对象
续接上回“2026 前端新出路:掌握 C++ 核心语法,无缝衔接 QT 桌面开发
”,开始进入c++的面向对象的学习!
学过 Java / JavaScript 面向对象的同学,重点关注 C++ 的差异点即可。
一、C++ 中的类和对象是什么?
1.1 从现实世界看"类"和"对象"
在面向对象编程里,类和对象是两个最核心的概念。
- 类(Class):是对某一类事物的抽象描述——描述这类事物有什么属性(数据)和能做什么(行为)。类是图纸,不是实物。
- 对象(Object):是类的具体实例。图纸照进现实造出来的一个个真正存在的东西。
举个例子:
| 类 | 对象 | |
|---|---|---|
| 现实世界 | "汽车"这个抽象概念 | 你小区楼下那辆白色丰田 |
| 代码里 | class Student |
Student zhangsan; |
| 类比 | 制作饼干的模具 | 用模具压制出来的一块块饼干 |
一句话:类是模板/蓝图,对象是用模板造出来的实体。
1.2 C++ 中类和对象的关系
// 类 —— 抽象的,一个定义
class Student {
public:
string name;
int age;
void study() {
cout << name << " 在学习" << endl;
}
};
// 对象 —— 具体的,可以有无数个
int main() {
Student s1; // s1 是一个对象,有实实在在的内存空间
Student s2; // s2 是另一个对象,和 s1 互不影响
s1.name = "张三";
s1.age = 20;
s2.name = "李四";
s2.age = 21;
s1.study(); // 输出:张三 在学习
s2.study(); // 输出:李四 在学习
cout << sizeof(s1) << endl; // s1 有确定的大小(占用实际内存)
// cout << sizeof(Student) << endl; // ❌ 不行,类名不能直接取大小
return 0;
}
关键认知:
- 类本身不占用运行时内存(代码段里存定义),对象才占用栈/堆内存
- 同一个类可以创建无数个对象,各自的数据独立存储
二、类的创建(定义)
2.1 类的基本语法
class 类名 {
private: // 私有成员——只能在类内部访问
成员数据;
成员函数;
public: // 公有成员——外部可以通过对象访问
成员数据;
成员函数;
protected: // 保护成员——类内部和派生类可以访问
成员数据;
成员函数;
}; // 注意:这个分号绝对不能少!
2.2 三种访问修饰符
| 修饰符 | 类内部 | 派生类(子类) | 外部(通过对象) |
|---|---|---|---|
private |
✅ 可访问 | ❌ 不可访问 | ❌ 不可访问 |
protected |
✅ 可访问 | ✅ 可访问 | ❌ 不可访问 |
public |
✅ 可访问 | ✅ 可访问 | ✅ 可访问 |
封装的核心思想:把数据设为 private,通过 public 的成员函数来间接操作。这就是 getter/setter 的由来。
class BankAccount {
private:
double balance; // 余额——不允许外部直接改
public:
double getBalance() { // 只读访问
return balance;
}
void deposit(double amount) { // 受控的修改通道
if (amount > 0) {
balance += amount;
}
}
};
int main() {
BankAccount acc;
// acc.balance = -10000; // 编译错误!private 成员不能直接访问
acc.deposit(50000); // 通过 public 函数间接操作
cout << acc.getBalance(); // 正确
return 0;
}
2.3 成员函数的两种实现方式
方式一:类内定义(隐式内联)
class Student {
public:
void sayHello() { // 直接在类里写函数体
cout << "Hello!" << endl;
}
};
方式二:类内声明 + 类外定义
class Student {
public:
void sayHello(); // 类内只声明(接口)
};
void Student::sayHello() { // 类外定义(实现)
cout << "Hello!" << endl; // 必须用 类名:: 前缀
}
| 方式 | 优点 | 缺点 |
|---|---|---|
| 类内定义 | 简洁,自动内联 | 头文件暴露实现细节 |
| 类外定义 | 接口与实现分离(.h + .cpp) | 需要写 类名:: |
::叫作用域解析运算符,表示这个函数属于哪个类。
2.4 一个完整的类定义示例
#include <iostream>
#include <cstring>
using namespace std;
class Stu {
private:
int sno; // 学号
char sname[10]; // 姓名
double dscore; // 分数
public:
// 类内定义
void InputData() {
cout << "\n请输入学号: ";
cin >> sno;
cout << "请输入姓名: ";
cin >> sname;
cout << "请输入分数: ";
cin >> dscore;
}
// 类内声明
void OutPutData();
double daverage; // 公有数据成员(一般不建议,仅演示)
};
// 类外实现
inline void Stu::OutPutData() {
cout << "\n学生数据信息如下:" << endl;
cout << "学号: " << sno << endl;
cout << "姓名: " << sname << endl;
cout << "分数: " << dscore << endl;
}
三、对象的创建与使用
3.1 创建对象的多种方式
#include <iostream>
using namespace std;
class Stu {
public:
int sno;
Stu() { cout << "构造函数调用" << endl; }
~Stu() { cout << "析构函数调用" << endl; }
};
int main() {
// 方式 1:栈上创建(最常用)
Stu stu1; // 自动调用构造函数
stu1.sno = 1001;
// 离开 main 时自动析构——不需要手动释放
// 方式 2:堆上创建(动态分配)
Stu* pStu = new Stu; // new + 构造函数
pStu->sno = 1002;
delete pStu; // 必须手动 delete,否则内存泄漏!delete 时自动析构
// 方式 3:数组
Stu arr[3]; // 3 个 Stu 对象,调用 3 次构造函数
// 离开作用域自动析构 3 次
// 方式 4:动态数组
Stu* pArr = new Stu[3]; // 3 个对象
delete[] pArr; // 注意:必须是 delete[],不是 delete
return 0;
}
| 创建方式 | 内存位置 | 生命周期 | 访问成员 | 是否手动释放 |
|---|---|---|---|---|
Stu s; |
栈 | 离开 {} |
s.成员 |
自动 |
Stu* p = new Stu; |
堆 | 直到 delete |
p->成员 |
必须 |
Stu arr[5]; |
栈 | 离开 {} |
arr[i].成员 |
自动 |
Stu* p = new Stu[5]; |
堆 | 直到 delete[] |
p[i].成员 |
必须 |
3.2 访问对象的成员
class Demo {
public:
int value;
void show() { cout << value << endl; }
};
int main() {
// 通过对象名访问:用 .
Demo obj;
obj.value = 42;
obj.show();
// 通过指针访问:用 ->
Demo* ptr = &obj;
ptr->value = 100; // 等价于 (*ptr).value = 100;
ptr->show(); // 等价于 (*ptr).show();
// 通过引用访问:用 .
Demo& ref = obj;
ref.value = 200;
ref.show();
return 0;
}
| 访问方式 | 语法 | 场景 |
|---|---|---|
| 对象名 | obj.成员 |
栈上的对象 |
| 指针 | ptr->成员 |
堆上的对象 / 指针参数 |
| 引用 | ref.成员 |
函数引用参数 |
3.3 结合原文的完整使用示例
#include <iostream>
using namespace std;
class Stu {
private:
int sno;
char sname[10];
double dscore;
public:
void InputData() {
cout << "\n请输入学号: ";
cin >> sno;
cout << "请输入姓名: ";
cin >> sname;
cout << "请输入分数: ";
cin >> dscore;
}
void OutPutData();
double daverage;
};
// 类外实现
void Stu::OutPutData() {
cout << "\n学生数据信息如下:" << endl;
cout << "学号: " << sno << endl;
cout << "姓名: " << sname << endl;
cout << "分数: " << dscore << endl;
}
Stu stu2; // 全局对象——见第五节
void testFunc() {
stu2.daverage = 100;
cout << "全局对象 stu2 的平均分: " << stu2.daverage << endl;
}
int main() {
Stu stu1; // 创建对象
stu1.InputData(); // 调用成员函数(输入数据)
stu1.OutPutData(); // 调用成员函数(输出数据)
testFunc(); // 访问全局对象
return 0;
}
四、类作用域
4.1 什么是类作用域?
类名 {} 内部的所有成员(成员变量、成员函数、typedef、枚举等)都属于类作用域。这意味着:
- 类内成员不能从外部直接通过名字调用,必须通过对象/指针/引用 + 点运算符或箭头运算符访问。
- 即使是
public成员,也必须在类作用域外使用对象.成员的形式。
4.2 为什么要有类作用域?
类作用域是 C++ 封装的基石之一。它把属于一个类的名字"锁在"类内部,防止与其他类或全局名字冲突。两个不同类可以有同名的成员函数,互不干扰:
class Dog {
public:
void speak() { cout << "汪汪" << endl; }
};
class Cat {
public:
void speak() { cout << "喵喵" << endl; }
};
这两个 speak() 分别属于两个不同的类作用域,永远不会冲突。
4.3 不能在 main 中直接通过函数名调用
这是类作用域最直接的后果——不能直接写函数名:
class Stu {
public:
void OutPutData() { cout << "学生数据" << endl; }
};
int main() {
// 错误!OutPutData 不在全局作用域,它在 Stu 类作用域里
// OutPutData();
// 正确:必须通过对象调用
Stu s;
s.OutPutData(); // 点运算符
Stu* p = &s;
p->OutPutData(); // 箭头运算符
return 0;
}
核心规则:类成员函数不能像普通函数那样裸调,必须绑定到具体对象上调用。
4.4 类作用域中的名字查找顺序
当你在类的成员函数内部使用一个名字时,编译器按如下顺序查找:
1. 成员函数内部的局部变量
2. 该类的成员变量/成员函数
3. 全局作用域(::)
示例:
int score = 60; // 全局变量
class Stu {
private:
int score = 90; // 成员变量
public:
void showScore() {
int score = 100; // 局部变量(优先级最高)
cout << "局部: " << score << endl; // 100
cout << "成员: " << this->score << endl; // 90
cout << "全局: " << ::score << endl; // 60
}
};
4.5 类成员的访问控制 + 类作用域的叠加效果
即使 public,也必须带上对象——“外部可访问"不等于"可以直接用名字”。
五、类类型作用域(文件作用域 & 块作用域)
5.1 文件作用域(全局作用域)
文件作用域指的是定义在所有函数、所有类之外的变量/函数。它们从定义处开始,到文件末尾结束,整个文件都可以访问。
#include <iostream>
using namespace std;
// ↓ 文件作用域(全局作用域)
int globalCount = 0; // 全局变量,整个文件可见
class Counter {
public:
void increment() {
globalCount++; // 类成员函数中访问全局变量 —— 允许
}
};
void printGlobal() {
cout << "全局计数: " << globalCount << endl; // 普通函数中也允许
}
int main() {
Counter c;
c.increment();
c.increment();
printGlobal(); // 输出: 全局计数: 2
cout << globalCount << endl;// main 中也能直接访问
return 0;
}
关键点:
- 在原文中,
Stu stu2;就是一个文件作用域的全局对象 - 文件作用域里的东西可以被该文件里的任何函数、任何类访问
- 如果另一个
.cpp文件想用,需要用extern声明
5.2 块作用域
块作用域指 {} 内部定义的变量,只在那个大括号内有效。
int main() {
int a = 10; // a 的作用域从这里开始...
{
int b = 20; // b 的作用域从这个内层 {} 开始
cout << a; // a 在外层块,内层可以访问
cout << b; // b 在内层块,可以访问
} // b 的作用域到此结束
cout << a; // a 还在作用域内
// cout << b; // 编译错误!b 已经超出作用域
return 0;
}
// 类成员函数内部的块作用域
class Demo {
public:
void test() {
int x = 5; // 整个 test() 函数内有效
if (x > 0) {
int y = 10; // y 只在 if 块内有效
cout << x << y; //
}
cout << x; // 错误
// cout << y; // y 已销毁
}
};
5.3 文件作用域 vs 块作用域——名字隐藏
int value = 999; // 文件作用域
class Test {
public:
int value = 100; // 类作用域(成员变量)
void show(int value) { // 参数 value —— 块作用域(优先级最高)
cout << "参数 value: " << value << endl; // 使用参数
cout << "成员 value: " << this->value << endl; // 使用成员
cout << "全局 value: " << ::value << endl; // 使用全局
}
};
int main() {
Test t;
t.show(1); // 参数=1, 成员=100, 全局=999
return 0;
}
名字隐藏规则:内层作用域的名字会隐藏外层同名的名字。 想访问被隐藏的,就需要 this->(成员)或 ::(全局)。
六、对象的作用域(全局 / 局部 / 局部静态)
6.1 全局对象
定义在所有函数、所有类之外的对象,属于文件作用域。它在 main() 执行之前就构造好了,main() 结束之后才析构。
#include <iostream>
using namespace std;
class Stu {
public:
int sno;
Stu() { cout << "全局对象构造" << endl; }
~Stu() { cout << "全局对象析构" << endl; }
};
Stu stu2; // ← 全局对象(对应原文中的 Stu stu2)
void testFunc() {
stu2.sno = 2023001; // 任何函数都可以访问
cout << "学号: " << stu2.sno << endl;
}
int main() {
cout << "=== main 开始 ===" << endl;
testFunc();
cout << "=== main 结束 ===" << endl;
return 0;
}
/* 输出顺序:
全局对象构造 ← 在 main 之前!
=== main 开始 ===
学号: 2023001
=== main 结束 ===
全局对象析构 ← 在 main 之后!
*/
| 特点 | 说明 |
|---|---|
| 生命周期 | 整个程序运行期间 |
| 构造时机 | main() 之前 |
| 析构时机 | main() 之后 |
| 作用域 | 定义处至文件末尾(整个文件);其他文件需 extern |
| 默认初始化 | 数值类型初始化为 0 |
6.2 局部对象
在函数/块内部定义的对象,属于块作用域。当程序执行到它的定义语句时构造,离开所在的 {} 时自动析构。
#include <iostream>
using namespace std;
class Stu {
public:
int sno;
Stu() { cout << " 构造" << endl; }
~Stu() { cout << " 析构" << endl; }
};
void func() {
Stu localStu; // ← 局部对象,进来时构造
localStu.sno = 1001;
cout << " func 内部,学号: " << localStu.sno << endl;
} // ← 局部对象离开作用域,自动析构
int main() {
Stu stu1; // ← 局部对象(原文中的 Stu stu1)
stu1.sno = 9527;
cout << "main 中, 学号: " << stu1.sno << endl;
func();
// localStu 已经不存在了,不能访问
cout << "main 即将结束" << endl;
return 0;
} // stu1 在此析构
/* 输出:
构造 ← stu1
main 中, 学号: 9527
构造 ← localStu
func 内部, 学号: 1001
析构 ← localStu(func 结束)
main 即将结束
析构 ← stu1(main 结束)
*/
| 特点 | 说明 |
|---|---|
| 生命周期 | 仅在所在 {} 内有效 |
| 构造时机 | 执行到定义语句时 |
| 析构时机 | 离开所在的 {} 时(析构顺序与构造相反) |
| 作用域 | 所在块内 |
| 默认初始化 | 不初始化,成员值是未定义的(垃圾值) |
6.3 局部静态对象
在函数内部用 static 关键字修饰的局部对象。它只在第一次执行到定义语句时构造一次,直到程序结束才析构。
#include <iostream>
using namespace std;
class Stu {
public:
Stu() { cout << " 静态对象构造(仅一次)" << endl; }
~Stu() { cout << " 静态对象析构(程序结束时)" << endl; }
int sno;
void showAddr() {
cout << " 对象地址: " << this << endl;
}
};
void callMe() {
static Stu staticStu; // ← 局部静态对象!只构造一次
staticStu.sno++;
cout << " 调用次数: " << staticStu.sno << endl;
staticStu.showAddr();
}
int main() {
cout << "第 1 次调用 callMe():" << endl;
callMe(); // 构造 + sno=1
cout << "第 2 次调用 callMe():" << endl;
callMe(); // 不构造!sno=2
cout << "第 3 次调用 callMe():" << endl;
callMe(); // sno=3
cout << "main 结束" << endl;
return 0;
}
/* 输出:
第 1 次调用 callMe():
静态对象构造(仅一次)
调用次数: 1
对象地址: 0x7ff6c2345000
第 2 次调用 callMe():
调用次数: 2
对象地址: 0x7ff6c2345000 ← 同一个地址!
第 3 次调用 callMe():
调用次数: 3
对象地址: 0x7ff6c2345000 ← 还是同一个!
main 结束
静态对象析构(程序结束时)
*/
| 特点 | 说明 |
|---|---|
| 生命周期 | 整个程序运行期间(和全局对象一样长) |
| 构造时机 | 第一次执行到定义语句时(不是 main 之前) |
| 析构时机 | 程序结束时 |
| 作用域 | 仅在定义它的函数内部可见(和局部对象一样窄) |
| 初始化次数 | 只初始化一次 |
🎯 局部静态对象的精髓:全局对象的长生命周期 + 局部对象的窄可见范围。
6.4 三种对象对比总结
| 类型 | 定义位置 | 生命周期 | 可见范围 | 初始化次数 |
|---|---|---|---|---|
| 全局对象 | 所有函数/类之外 | 程序始终 | 整个文件(其他文件需 extern) |
1 次 |
| 局部对象 | 函数/块 {} 内 |
所在块内 | 所在块内 | 每次进入块都初始化 |
| 局部静态对象 | 函数内加 static |
程序始终 | 所在函数内 | 1 次(第一次进入时) |
6.5 完整示例
把原文中三种对象都包含的完整代码:
#include <iostream>
using namespace std;
class Stu {
private:
int sno;
char sname[10];
double dscore;
public:
void InputData() {
cout << "请输入学号: ";
cin >> sno;
cout << "请输入姓名: ";
cin >> sname;
cout << "请输入分数: ";
cin >> dscore;
}
void OutPutData();
double daverage;
};
Stu stu2; // ← 全局对象(文件作用域)
void Stu::OutPutData() {
cout << "\n学生数据信息如下:" << endl;
cout << "学号: " << sno << endl;
cout << "姓名: " << sname << endl;
cout << "分数: " << dscore << endl;
}
void testFunc() {
static int callCount = 0; // ← 局部静态变量(类比局部静态对象)
callCount++;
stu2.daverage = 100;
cout << "\n[testFunc 第 " << callCount << " 次调用]" << endl;
cout << "全局对象 stu2 的平均分为:" << stu2.daverage << endl;
}
int main() {
Stu stu1; // ← 局部对象(块作用域,main 内)
stu1.InputData();
stu1.OutPutData();
testFunc(); // 第 1 次
testFunc(); // 第 2 次——callCount 保留上次的值
return 0;
} // stu1 在此析构(局部对象)
// stu2 在程序结束后析构(全局对象)
七、类的嵌套
要点:
- 嵌套类本质上是一个独立的类,只是作用域在外部类内部
- 外部类和内部类的同名成员互不影响,各有独立内存空间
- 内部类成员函数在类外定义时需写
外部类::内部类::函数名
#include <iostream> // c++ 包含头文件输入输出流
#include <iomanip>
using namespace std;
class CC1 {
public:
int x;
void Func();
class CC2 {
public:
int x;
void Func();
}objc;
};
void CC1::Func() {
x = 3000;
cout << "x = " << x << endl;
}
void CC1::CC2::Func() {
x = 4000;
cout << "x = " << x << endl;
}
int main() {
class CC1 obj;
obj.Func(); // x = 3000
obj.objc.Func(); // x = 4000
cout << "\n\n";
cout << "2:x = " << obj.x << endl; // 2:x = 3000
cout << "2:x = " << obj.objc.x << endl; // 2:x = 4000
return 0;
}
要点:
- 嵌套类本质上是一个独立的类,只是作用域在外部类内部
- 外部类和内部类的同名成员互不影响,各有独立内存空间
- 内部类成员函数在类外定义时需写
外部类::内部类::函数名
八、对象引用私有数据成员
8.1 通过公有函数为私有成员赋值
最常用的封装模式——setter / getter。
#include <iostream> // c++ 包含头文件输入输出流
#include <iomanip>
using namespace std;
// 通过共有函数,为私有数据成员赋值
class Ctest {
int x, y;
public:
void setxy(int a, int b) {
x = a;
y = b;
}
void dispxy() {
cout << "x=" << x << ",y=" << y << endl << endl;
}
};
int main() {
Ctest Obj1;
Obj1.setxy(78, 90); // 调用共有成员函数为私有对象赋值
Obj1.dispxy();
return 0;
}
8.2 利用指针访问私有数据成员
通过公有函数将私有成员的值"传出"到外部变量(传指针)。
#include <iostream> // c++ 包含头文件输入输出流
#include <iomanip>
using namespace std;
// 通过共有函数,为私有数据成员赋值
class Ctest {
int x, y;
public:
void setxy(int a, int b) {
x = a;
y = b;
}
void printxy() {
cout << "x=" << x << ",y=" << y << endl << endl;
}
void getxy(int* px, int* py) {
*px = x;
*py = y;
}
};
int main() {
Ctest obj;
obj.setxy(100, 200);
obj.printxy(); // x=100,y=200
int n, m;
obj.getxy(&m, &n);
cout << "\n\nm=" << m << ",n=" << n << endl << endl; // m=100,n=200
return 0;
}
注意: 参数 int* px 是值传递(传递的是地址值本身),但通过解引用 *px 可以修改外部变量。
8.3 利用函数访问私有数据成员
最简洁的方式——通过返回值传出。
#include <iostream> // c++ 包含头文件输入输出流
#include <iomanip>
using namespace std;
// 通过函数访问私有数据成员
class Ctest {
int x, y;
public:
void setxy(int a, int b) {
x = a;
y = b;
}
void printxy() {
cout << "x=" << x << ",y=" << y << endl << endl;
}
int getX() {
return x;
}
int getY() {
return y;
}
};
int main() {
Ctest obj;
obj.setxy(100, 200);
obj.printxy();
cout << "\n\nx=" << obj.getX() << ",y=" << obj.getY() << endl << endl; // m=100,n=200
return 0;
}
8.4 利用引用访问私有数据成员
通过引用参数直接把外部变量"绑定"到私有成员的值。
#include <iostream> // c++ 包含头文件输入输出流
#include <iomanip>
using namespace std;
// 通过引用访问私有数据成员
class Ctest {
int x, y;
public:
void setxy(int a, int b) {
x = a;
y = b;
}
void printxy() {
cout << "x=" << x << ",y=" << y << endl << endl;
}
void getxy(int& px, int& py) {
px = x;
py = y;
}
};
int main() {
Ctest obj;
obj.setxy(100, 200);
obj.printxy();
int m, n;
obj.getxy(m, n);
cout << "\n\nm=" << m<< ",n=" << n << endl << endl; // m=100,n=200
return 0;
}
四种方式对比:
| 方式 | 语法 | 特点 |
|---|---|---|
| 公有函数赋值 | obj.setxy(a, b) |
最标准,封装性好 |
| 指针传出 | obj.getxy(&m, &n) |
可以一次传出多个值,调用方需取地址 |
| 返回值传出 | obj.getX() |
最简洁,一个函数只能返回一个值 |
| 引用传出 | obj.getxy(m, n) |
语法最自然,调用方无需取地址 |
九、成员函数的重载
类中的成员函数与前面介绍的普通函数一样,成员函数可以带有缺省的参数,也可以重载成员函数,重载时,函数的形参必须在类型或数目上不同。
9.1 缺省参数的成员函数
#include <iostream> // c++ 包含头文件输入输出流
#include <iomanip>
using namespace std;
class CTest {
int x, y;
int m, n;
public:
void setxy(int a, int b) {
x = a;
y = b;
}
void setxy(int a, int b, int c, int d) {
x = a;
y = b;
m = c;
n = d;
}
void dispxymn(int x) {
cout << x << "," << y << endl;
}
void dispxymn() {
cout << x << "," << y << "," << m << "," << n << endl<< endl;
}
};
int main() {
CTest obj1, obj2;
// 参数不同
obj1.setxy(10, 20);
obj2.setxy(10, 20, 30, 40);
// 参数类型不同
obj1.dispxymn(666);
obj2.dispxymn();
return 0;
}
重载规则:
- 函数名相同,参数列表(类型或个数)必须不同
- 不能仅靠返回值类型区分重载
- 编译器根据调用时的实参自动匹配最合适的版本
9.2 定义类的指针及如何用指针来引用对象
可以定义指向类对象的指针,通过 -> 运算符访问成员。
#include <iostream> // c++ 包含头文件输入输出流
#include <iomanip>
using namespace std;
class CTest {
public:
double sum() {
return x + y;
}
void setxy(double a, double b) {
x = a;
y = b;
}
void dispxy() {
cout << x << "," << y << endl;
}
private:
int x, y;
};
int main() {
CTest obj1, obj2; // 定义对象
CTest* pobj; // 定义类的指针(对象指针)
pobj = &obj1;
pobj->setxy(3.4, 8.9); // 通过指针引用对象的成员函数
pobj->dispxy();
cout << "x+y=" << pobj->sum() << endl;
return 0;
}

9.3 定义类的数组及数组中元素的引用
可以定义类的数组,每个元素都是一个独立的对象。
#include <iostream>
using namespace std;
class CTest {
int x, y;
public:
void setxy(int a, int b) { x = a; y = b; }
void print() { cout << "(" << x << ", " << y << ")" << endl; }
};
int main() {
CTest arr[3]; // 对象数组,3 个 CTest 对象
arr[0].setxy(10, 20);
arr[1].setxy(30, 40);
arr[2].setxy(50, 60);
for (int i = 0; i < 3; i++) {
cout << "arr[" << i << "] = ";
arr[i].print();
}
// 对象数组 + 指针
CTest* p = arr; // 指向数组首元素
p->print(); // arr[0] → (10, 20)
(p + 1)->print(); // arr[1] → (30, 40)
return 0;
}
十、this指针
不同对象占据内存中不同区域,它们所保存的数据各不相同,但是成员数据进行操作的成员函数的程序代码均是一样的,当对一个对象调用成员函数时,编译程序先将对象的地址赋给this指针,然后调用成员函数,每次成员函数存取数据成员时候,也隐含使用this指针。
10.1 基本原理
每个非静态成员函数都隐式包含一个 this 指针,指向调用该函数的对象.
- 当
obj.setx(888)被调用时,编译器将obj的地址赋给this。 - 成员函数内访问
x等价于this->x.
#include <iostream> // c++ 包含头文件输入输出流
#include <iomanip>
using namespace std;
class CTest {
private:
int x;
public:
int getx() const {
return x;
}
void setx(int x) {
this->x = x;
cout << "this指针存储的内存地址为:" << this << endl << endl;
}
};
int main() {
CTest obj;
obj.setx(888);
cout << "对象obj在内存的地址为:" << &obj << endl;
cout << "对象obj所保存的值为多少:" << obj.getx() << endl;
return 0;
}

this的地址与&obj完全一致。
10.2 this 的主要用途
class CTest {
int x;
public:
// 1. 区分成员变量与形参同名的情况
void setx(int x) {
this->x = x; // this->x = 成员变量, x = 形参
}
// 2. 返回对象自身的引用(链式调用)
CTest& add(int val) {
x += val;
return *this; // 返回当前对象
}
// 3. 比较是否同一个对象
bool isSame(const CTest& other) const {
return this == &other;
}
};
// 链式调用示例
CTest obj;
obj.add(10).add(20).add(30);
const 成员函数与 this
class CTest {
int x;
public:
int getx() const { // const 成员函数
return x; // this 的类型是 const CTest*
}
// 在 const 函数中不能修改成员变量,不能调用非 const 成员函数
};
总结:
| 概念 | 说明 |
|---|---|
this 是什么 |
指向当前对象的指针,类型为类名* const |
| 何时产生 | 非静态成员函数被调用时自动传入 |
| 主要用途 | 区分同名变量、返回自身引用、对象比较 |
const 函数中 |
this 变为 const 类名* const,不可修改成员 |
十一、静态成员
11.1 静态数据成员
通常,每当说明一个对象的时候,把该类中的有关成员数据拷贝到该对象中,即同一类不同对象,其成员数据之间是相互独立的。
#include <iostream>
using namespace std;
class CTest {
int x, y;
public:
void Setxy(int a, int b) {
x = a;
y = b;
}
};
int main() {
CTest a1, a2;
a1.Setxy(1, 2);
a1.Setxy(3, 4);
return 0;
}
当将类的某一个数据成员的存储类型指定为静态类型时,则由该类所产生的所有对象,其静态成员均共享一个存储空间,这个空间是在编译的时候分配的,在说明对象的时候,并不为静态类型的成员分配空间。在定义中,用关键字static修饰的数据成员称为静态数据成员
说明:
-
类的静态数据成员是静态分配存储空间的,当类中定义了静态数据成员时,在编译时,就要为类的静态数据成员分配存储空间。
-
必须在文件作用域中,对静态数据成员做一次且只能做一次定义性说明。

-
在对静态数据成员定义性的说明的时候也可以指定一个初值。
-
全局变量在程序中的任何位置都可以访问,静态数据成员受到访问权限的约束,必须是
public权限时,才可能在类外进行访问。 -
为了保持静态数据成员取值的一致性,通常在构造函数中不给静态数据成员置初值,而是在对静态数据成员的定义性说明时指定初值。
#include <iostream>
using namespace std;
class CTest {
public:
CTest(int x = 0) {
i = x;
icount++;
}
void disp() {
cout << "i=" << i << ",icount=" << icount << endl;
}
private:
int i;
static int icount;
};
// 静态成员定义性说明
int CTest::icount = 0;
int main() {
CTest obj1[100];
obj1->disp();
CTest obj2[5];
obj2->disp();
return 0;
}

11.2 静态成员函数
可以将类的成员函数定义为静态的成员函数,使用关键字static来修饰成员函数。
static void disp() {
.....
}
- 与静态数据成员一样,在类的程序代码中,通过类名加上作用域操作符,可直接调用静态成员函数。
- 静态成员函数只能直接使用本类的静态数据成员或静态成员函数,但不能直接使用非静态的数据成员(可以引用使用)
- 不包含对象地址的this指向。
- 静态成员函数的实现部分在类定义之外定义时,其前面不能加修饰符static,这是由于关键字static不是数据类型的组成部分,因此,在类外面定义静态成员函数的实现部分时,不能使用。
- 不能把静态成员函数定义为虚函数。
- 可将静态成员函数定义为内敛的inline,其定义方法与非静态成员函数完全相同。
#include <iostream>
using namespace std;
class CTest {
public:
CTest(int a) {
x = a;
y = y + x;
}
static void disp(CTest obj) {
cout << "x=" << obj.x << ",y=" << y << endl;
}
private:
int x;
static int y;
};
int CTest::y = 10;
int main() {
CTest obj1(5), obj2(10);
CTest::disp(obj1);
CTest::disp(obj2);
return 0;
}

十二、指向类的指针
一个指向C++类的指针与指向结构的指针类似,访问指向类的指针的成员需要使用->,就像访问指向结构的指针一样。与所有的指针一样,必须使用指针之前,对指针进行初始化。
#include <iostream>
using namespace std;
class CTest {
public:
CTest(int a) {
x = a;
y = y + x;
}
static void disp(CTest obj) {
cout << "x=" << obj.x << ",y=" << y << endl;
}
private:
int x;
static int y;
};
int CTest::y = 10;
int main() {
CTest obj1(5), obj2(10);
CTest::disp(obj1);
CTest::disp(obj2);
CTest* pobj;
pobj = &obj1;
pobj->disp(obj1);
return 0;
}

知行合一,拒绝不动手的空想~
更多推荐
所有评论(0)