C++核心编程
本阶段主要针对C++面向对象编程技术做详细讲解,探讨C++中的核心和精髓。
1 内存分区模型
C++程序在执行时,将内存大方向划分为**4个区域**
* 代码区:存放函数体的二进制代码,由操作系统进行管理的
* 全局区:存放全局变量和静态变量以及常量
* 栈区:由编译器自动分配释放, 存放函数的参数值,局部变量等
* 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收**内存四区意义:**
不同区域存放的数据,赋予不同的生命周期, 给我们更大的灵活编程
1.1 程序运行前
在程序编译后,生成了exe可执行程序,**未执行该程序前**分为两个区域
**代码区:**
存放 CPU 执行的机器指令
代码区是**共享**的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
代码区是**只读**的,使其只读的原因是防止程序意外地修改了它的指令
**全局区:**
全局变量和静态变量存放在此.
全局区还包含了常量区, 字符串常量和其他常量也存放在此.
==该区域的数据在程序结束后由操作系统释放==.
**示例:
#include <iostream> using namespace std; //全局区 // //全局变量 int g_a = 10; int g_b = 10; //全局常量 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 << "全局变量a的地址为:" << (int)&g_a << endl; cout << "全局变量b的地址为:" << (int)&g_b << endl; //静态变量 static int s_a = 10; static int s_b = 10; cout << "静态变量s_a地址为: " << (int)&s_a << endl; cout << "静态变量s_b地址为: " << (int)&s_b << endl; //常量 //字符串常量 cout << "字符串常量地址为: " << (int)&"hello world" << endl; cout << "字符串常量地址为: " << (int)&"hello world1" << endl; //全局常量 cout << "全局常量c_g_a地址为: " << (int)&c_g_a << endl; cout << "全局常量c_g_b地址为: " << (int)&c_g_b << endl; //局部常量 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; system("pause"); return 0; } #include <iostream> using namespace std; // ★★★★★ 全局区(Global/Static Area)★★★★★ // 1️⃣ 全局变量(Global Variables) int g_a = 10; // ✅ 全局区(.data 段) int g_b = 10; // ✅ 全局区(.data 段) // 2️⃣ 全局常量(Global Constants) const int c_g_a = 10; // ✅ 常量区(Constants) const int c_g_b = 10; // ✅ 常量区(Constants) int main() { // ★★★★★ 栈区(Stack)★★★★★ // 3️⃣ 局部变量(Local Variables) int a = 10; // ✅ 栈区 int b = 10; // ✅ 栈区 // 4️⃣ 局部常量(Local Constants) const int c_l_a = 10; // ✅ 栈区(注意!不是常量区!) const int c_l_b = 10; // ✅ 栈区 // ★★★★★ 全局/静态区(Static Area)★★★★★ // 5️⃣ 静态变量(Static Variables) static int s_a = 10; // ✅ 全局区(.data 段) static int s_b = 10; // ✅ 全局区(.data 段) // ★★★★★ 常量区(Constants)★★★★★ // 6️⃣ 字符串常量(String Literals) cout << "hello world"; // ✅ 常量区 cout << "hello world1"; // ✅ 常量区 // ... 输出地址 } 局部变量a的地址为: 15182748 ← 栈区(高地址) 局部变量b的地址为: 15182744 ← 栈区(相差4字节,相邻) 全局变量a的地址为: 4221232 ← 全局区(低地址) 全局变量b的地址为: 4221236 ← 全局区(相差4字节,相邻) 静态变量s_a地址为: 4221240 ← 全局区(和全局变量相邻) 静态变量s_b地址为: 4221244 ← 全局区(和全局变量相邻) 字符串常量地址为: 4349556 ← 常量区 字符串常量地址为: 4349568 ← 常量区(和全局常量相邻) 全局常量c_g_a地址为:4349580 ← 常量区 全局常量c_g_b地址为:4349584 ← 常量区 局部常量c_l_a地址为:15182736 ← 栈区(和局部变量相邻) 局部常量c_l_b地址为:15182732 ← 栈区
打印结果:
* C++中在程序运行前分为全局区和代码区
* 代码区特点是共享和只读
* 全局区中存放全局变量、静态变量、常量
* 常量区中存放 const修饰的全局常量 和 字符串常量记忆口诀
全局静态放一起,程序开始到结束
局部变量在栈上,函数结束就回收
常量字符串只读区,全局常量也在那
局部常量在栈上,记住这个别搞错
栈上地址比较大,全局区地址比较小重要结论总结
变量类型 存储区域 生命周期 地址特征 全局变量 全局区 程序启动→结束 地址小,固定 静态变量 全局区 程序启动→结束 地址小,固定 局部变量 栈区 函数开始→结束 地址大,每次运行可能不同 局部常量 栈区 函数开始→结束 地址大,和局部变量相邻 全局常量 常量区 程序启动→结束 地址中,只读 字符串常量 常量区 程序启动→结束 地址中,只读 1.2 程序运行后
**栈区:**
由编译器自动分配释放, 存放函数的参数值,局部变量等
注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
**示例:
#include<iostream> using namespace std; //栈区数据注意事项--不要返回局部变量的地址 //栈区的数据由编译器管理开辟和释放 int* func(int b) //形参数据也会放在栈区 { b = 100; int a = 10;//局部变量 存放在栈区,栈区的数据在函数执行完后自动释放 return &a; } int main() { //接收func函数的返回值 int * p = func(1); cout << *p << endl;//第一次可以打印正确的数字,是因为编译器做了保留 cout << *p << endl;//第二次这个数据就不再保留了 system("pause"); return 0; }正确做法
方法1:使用静态变量
int* func() { static int a = 10; // 静态变量在全局区,程序结束才释放 return &a; // ✅ 安全! } int main() { int* p = func(); cout << *p << endl; // 输出 10 cout << *p << endl; // 输出 10(安全) return 0; }方法2:使用动态内存(堆)
int* func() { int* a = new int(10); // 在堆上分配 return a; // ✅ 安全!但需要手动释放 } int main() { int* p = func(); cout << *p << endl; // 输出 10 cout << *p << endl; // 输出 10 delete p; // ⚠️ 必须手动释放! return 0; }方法3:返回值而不是地址
int func() { int a = 10; return a; // ✅ 返回值(复制),不是返回地址 } int main() { int value = func(); cout << value << endl; // 输出 10 cout << value << endl; // 输出 10 return 0; }方法4:使用引用参数
void func(int& result) { result = 10; // 通过引用修改外部变量 } int main() { int a = 0; func(a); cout << a << endl; // 输出 10 return 0; }堆区:
由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
在C++中主要利用new在堆区开辟内存
**示例:
1.3 new操作符
C++中利用==new==操作符在堆区开辟数据
堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符 ==delete==
语法:` new 数据类型`
利用new创建的数据,会返回该数据对应的类型的指针
**示例1: 基本语法
int* func() { int* a = new int(10); // ① 在堆区分配内存 return a; // ② 返回堆内存地址 } int main() { int *p = func(); // ③ p 指向堆内存 cout << *p << endl; // ④ 输出 10 cout << *p << endl; // ⑤ 再次输出 10 delete p; // ⑥ 释放堆内存 // cout << *p << endl; // ⑦ 错误!内存已释放 return 0; }
核心要点:
new在堆区分配内存,返回地址堆内存不会自动释放,必须手动
delete函数返回堆内存地址是安全的(内存还在)
delete后指针变成野指针,不能再使用堆内存的生命周期由程序员控制,可以跨函数
#include <iostream> using namespace std; int main() { // 1. 基本类型 int* pInt = new int(10); // new int → 返回 int* double* pDouble = new double(3.14); // new double → 返回 double* char* pChar = new char('A'); // new char → 返回 char* // 2. 数组 int* pArray = new int[5]; // new int[5] → 返回 int*(首元素地址) // 3. 结构体/类 struct Student { string name; int age; }; Student* pStu = new Student{"张三", 20}; // new Student → 返回 Student* // 4. 使用指针访问 cout << *pInt << endl; // 10 cout << pArray[0] << endl; // 数组方式访问 cout << pStu->name << endl; // 结构体成员访问 // 释放内存 delete pInt; delete pDouble; delete pChar; delete[] pArray; delete pStu; return 0; }**示例2:开辟数组
//堆区开辟数组 int main() { int* arr = new int[10]; for (int i = 0; i < 10; i++) { arr[i] = i + 100; } for (int i = 0; i < 10; i++) { cout << arr[i] << endl; } //释放数组 delete 后加 [] delete[] arr; system("pause"); return 0; }完整代码:
#include<iostream> // 引入输入输出流头文件 using namespace std; // 使用标准命名空间 // ========== 1. new 的基本语法(单个变量) ========== // 函数功能:在堆区创建并返回一个 int 类型的数据 int* func() { // 在堆区创建整型数据 // new返回的是该数据类型的指针(int*) int* p = new int(10); // 在堆区分配 4 字节,存储值 10,返回地址 return p; // 返回堆内存地址给调用者 } // 测试函数:演示单个变量的 new 和 delete void test01() { int* p = func(); // p 接收堆内存地址(假设是 0x1000) // 多次访问堆内存中的数据(多次解引用) cout << *p << endl; // 第一次:输出 10 cout << *p << endl; // 第二次:输出 10(内存还在) cout << *p << endl; // 第三次:输出 10 cout << *p << endl; // 第四次:输出 10 // 堆区的数据由程序员管理开辟,程序员管理释放 // 如果想释放堆区的数据,利用关键字 delete delete p; // 释放 0x1000 地址的内存 // cout << *p << endl; // ❌ 内存已被释放,再次访问是非法操作,会报错 } // ========== 2. 在堆区利用 new 开辟数组 ========== void test02() { // 创建容量为 10 的整型数组,在堆区分配 int* arr = new int[10]; // 10 代表数组有 10 个元素 // new int[10] 返回 int*,指向数组首元素地址 // 给数组元素赋值 for (int i = 0; i < 10; i++) { arr[i] = i + 100; // 给 10 个元素赋值:100-109 // arr[i] 等价于 *(arr + i) } // 打印数组元素 for (int i = 0; i < 10; i++) { cout << arr[i] << endl; // 输出 100 到 109 } // 释放数组:delete 后加 [] delete[] arr; // 释放整个数组内存 } int main() { test01(); // 测试单个变量 test02(); // 测试数组 system("pause"); return 0; }2 引用
2.1 引用的基本使用
**作用: **给变量起别名
**语法:** `数据类型 &别名 = 原名`
**示例:
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 引用注意事项
* 引用必须初始化
* 引用在初始化后,不可以改变示例:
int main() { int a = 10; int b = 20; //int &c; //错误,引用必须初始化 int &c = a; //一旦初始化后,就不可以更改 c = b; //这是赋值操作,不是更改引用 cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl; system("pause"); return 0; }
2.3 引用做函数参数
**作用:**函数传参时,可以利用引用的技术让形参修饰实参
**优点:**可以简化指针修改实参
#include <iostream> using namespace std; // ========== 1. 值传递 ========== // 形参是普通变量,接收实参的副本 void mySwap01(int a, int b) { // a 和 b 是实参的副本,在栈上新分配内存 int temp = a; // temp = 10 a = b; // a = 20(修改的是副本) b = temp; // b = 10(修改的是副本) // 函数结束,a、b、temp 全部销毁 // 实参不受影响! } // ========== 2. 地址传递 ========== // 形参是指针,接收实参的地址 void mySwap02(int* a, int* b) { // a 和 b 是存储地址的指针变量 int temp = *a; // temp = *a = 10(解引用获取值) *a = *b; // *a = 20(修改 a 指向的内存) *b = temp; // *b = 10(修改 b 指向的内存) // 通过地址间接修改了实参! } // ========== 3. 引用传递 ========== // 形参是引用,是实参的别名 void mySwap03(int& a, int& b) { // a 和 b 是实参的别名(引用),直接操作实参 int temp = a; // temp = 10(直接读取实参 a) a = b; // a = 20(直接修改实参 a) b = temp; // b = 10(直接修改实参 b) // 直接修改了实参! } int main() { int a = 10; int b = 20; // 1. 值传递:传入变量值 mySwap01(a, b); cout << "值传递后 a:" << a << " b:" << b << endl; // a:10 b:20(不变) // 2. 地址传递:传入变量地址 mySwap02(&a, &b); cout << "地址传递后 a:" << a << " b:" << b << endl; // a:20 b:10(已交换) // 3. 引用传递:传入变量本身(引用) mySwap03(a, b); cout << "引用传递后 a:" << a << " b:" << b << endl; // a:10 b:20(又换回来了) system("pause"); return 0; }总结:通过引用参数产生的效果同按地址传递是一样的。引用的语法更清楚简单
2.4 引用做函数返回值
作用:引用是可以作为函数的返回值存在的
注意:**不要返回局部变量引用**
用法:函数调用作为左值
**示例:
#include <iostream> using namespace std; // ========== 1. 返回局部变量的引用(危险!) ========== // 函数功能:返回局部变量的引用 int& test01() { int a = 10; // 局部变量,在栈上分配 return a; // ❌ 危险!返回局部变量的引用 // 函数结束时,a 被销毁,返回的引用变成"悬空引用" } // ========== 2. 返回静态变量的引用(安全) ========== // 函数功能:返回静态变量的引用 int& test02() { static int a = 20; // 静态变量,在全局区分配 return a; // ✅ 安全!静态变量生命周期是整个程序 // 函数结束,a 依然存在 } int main() { // ========== 测试1:返回局部变量引用 ========== cout << "===== 测试 test01(局部变量) =====" << endl; int& ref = test01(); // ref 是 test01 中局部变量 a 的引用 // 此时 a 已经被销毁,ref 是"悬空引用" cout << "ref = " << ref << endl; // 第一次:可能输出 10(侥幸) cout << "ref = " << ref << endl; // 第二次:可能输出随机值(危险!) // ⚠️ 这是未定义行为! // ========== 测试2:返回静态变量引用 ========== cout << "\n===== 测试 test02(静态变量) =====" << endl; int& ref2 = test02(); // ref2 是 test02 中静态变量 a 的引用 // ✅ 安全!静态变量一直存在 cout << "ref2 = " << ref2 << endl; // 输出:20 cout << "ref2 = " << ref2 << endl; // 输出:20 // ========== 函数调用作为左值 ========== cout << "\n===== 函数调用作为左值 =====" << endl; test02() = 1000; // ✅ 函数返回引用,可以作为左值修改静态变量 // test02() 返回的是静态变量的引用,所以可以赋值 cout << "ref2 = " << ref2 << endl; // 输出:1000(已被修改) cout << "ref2 = " << ref2 << endl; // 输出:1000 system("pause"); return 0; }2.5 引用的本质
本质:**引用的本质在c++内部实现是一个指针常量.**
讲解示例:
//发现是引用,转换为 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); return 0; }
结论:C++推荐用引用技术,因为语法方便,引用本质是指针常量,但是所有的指针操作编译器都帮我们做了
2.6 常量引用
**作用:**常量引用主要用来修饰形参,防止误操作
在函数形参列表中,可以加==const修饰形参==,防止形参改变实参
**示例:
#include <iostream> using namespace std; // ========== const 引用作为形参 ========== // 功能:只读访问,不能修改实参 void showValue(const int& v) { // v 是 const 引用 // v += 10; // ❌ 错误!const 引用不能修改 cout << v << endl; // ✅ 只能读取 } int main() { // ========== 1. const 引用绑定字面量 ========== // int& ref = 10; // ❌ 错误!引用本身需要一个合法的内存空间 // 原因:10 是字面量(右值),没有内存地址 // ✅ 加入 const 就可以了 const int& ref = 10; // 编译器优化:int temp = 10; const int& ref = temp; // 创建临时变量 temp,ref 引用这个临时变量 // ref = 100; // ❌ 错误!const 引用不能修改 cout << ref << endl; // 输出:10 // ========== 2. const 引用保护实参 ========== int a = 10; showValue(a); // 传递 a 的引用,但函数内不能修改 system("pause"); return 0; }3 函数提高
3.1 函数默认参数
在C++中,函数的形参列表中的形参是可以有默认值的。
语法:` 返回值类型 函数名 (参数= 默认值){}`
**示例:
int func(int a, int b = 10, int c = 10) { return a + b + c; } //1. 如果某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值 //2. 如果函数声明有默认值,函数实现的时候就不能有默认参数 //3.函数声明和函数实现只能有一个有默认参数 int func2(int a = 10, int b = 10); int func2(int a, int b) { return a + b; } int main() { cout << "ret = " << func(20, 20) << endl; cout << "ret = " << func(100) << endl; system("pause"); return 0; }3.2 函数占位参数
C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置
语法:返回值类型 函数名 (数据类型){}
在现阶段函数的占位参数存在意义不大,但是后面的课程中会用到该技术
**示例:
#include <iostream> using namespace std; // ========== 函数占位参数 ========== // 语法:在参数列表中只写类型,不写参数名 void func(int a, int) { // 第一个参数有名字 a,第二个参数是占位参数 cout << "this is func" << endl; // 注意:占位参数在函数体内无法使用(因为没有名字) } int main() { // 调用时:占位参数必须传值 func(10, 10); // 第一个参数 10 给 a,第二个 10 给占位参数 // func(10); // ❌ 错误!占位参数必须填补 // func(10, 20, 30); // ❌ 错误!参数数量不匹配 system("pause"); return 0; }3.3 函数重载
3.3.1 函数重载概述
**作用:**函数名可以相同,提高复用性
**函数重载满足条件:**
* 同一个作用域下
* 函数名称相同
* 函数参数**类型不同** 或者 **个数不同** 或者 **顺序不同****注意:** 函数的返回值不可以作为函数重载的条件
**示例:
//函数重载需要函数都在同一个作用域下 void func() { cout << "func 的调用!" << endl; } void func(int a) { cout << "func (int a) 的调用!" << endl; } void func(double a) { cout << "func (double a)的调用!" << endl; } void func(int a ,double b) { cout << "func (int a ,double b) 的调用!" << endl; } void func(double a ,int b) { cout << "func (double a ,int b)的调用!" << endl; } //函数返回值不可以作为函数重载条件 //int func(double a, int b) //{ // cout << "func (double a ,int b)的调用!" << endl; //} int main() { func(); func(10); func(3.14); func(10,3.14); func(3.14 , 10); system("pause"); return 0; }3.3.2 函数重载注意事项
* 引用作为重载条件
* 函数重载碰到函数默认参数**示例:
#include <iostream> using namespace std; // ========== 1. 引用作为重载条件 ========== // 重载版本1:int&(普通引用) void func(int &a) // 参数:int 类型的引用 { cout << "func (int &a) 调用 " << endl; } // 重载版本2:const int&(常量引用) void func(const int &a) // 参数:const int 类型的引用 { cout << "func (const int &a) 调用 " << endl; } // ========== 2. 函数重载碰到默认参数 ========== // 重载版本1:两个参数,第二个有默认值 void func2(int a, int b = 10) { cout << "func2(int a, int b = 10) 调用" << endl; } // 重载版本2:一个参数 void func2(int a) { cout << "func2(int a) 调用" << endl; } int main() { int a = 10; // ========== 测试引用重载 ========== func(a); // ✅ 调用无 const 版本:func(int &a) func(10); // ✅ 调用有 const 版本:func(const int &a) // ========== 测试默认参数重载 ========== // func2(10); // ❌ 错误!产生二义性,编译失败 func2(10,20);//正确可运行 system("pause"); return 0; }4 类和对象
C++面向对象的三大特性为:==封装、继承、多态==
C++认为==万事万物都皆为对象==,对象上有其属性和行为
**例如:**
人可以作为对象,属性有姓名、年龄、身高、体重...,行为有走、跑、跳、吃饭、唱歌...
车也可以作为对象,属性有轮胎、方向盘、车灯...,行为有载人、放音乐、放空调...
具有相同性质的==对象==,我们可以抽象称为==类==,人属于人类,车属于车类
4.1 封装
4.1.1 封装的意义
封装是C++面向对象三大特性之一
封装的意义:
* 将属性和行为作为一个整体,表现生活中的事物
* 将属性和行为加以权限控制**封装意义一:**
在设计类的时候,属性和行为写在一起,表现事物
**语法:** `class 类名{ 访问权限: 属性 / 行为 };`
**示例1:**设计一个圆类,求圆的周长
**示例代码:**
//圆周率 const double PI = 3.14; //1、封装的意义 //将属性和行为作为一个整体,用来表现生活中的事物 //封装一个圆类,求圆的周长 //class代表设计一个类,后面跟着的是类名 class Circle { public: //访问权限 公共的权限 //属性 int m_r;//半径 //行为 //获取到圆的周长 double calculateZC() { //2 * pi * r //获取圆的周长 return 2 * PI * m_r; } }; int main() { //通过圆类,创建圆的对象 // c1就是一个具体的圆 Circle c1; c1.m_r = 10; //给圆对象的半径 进行赋值操作 //2 * pi * 10 = = 62.8 cout << "圆的周长为: " << c1.calculateZC() << endl; system("pause"); return 0; }**示例2:**设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号
**示例2代码:
//学生类 class Student { public: void setName(string name) { m_name = name; } void setID(int id) { m_id = id; } void showStudent() { cout << "name:" << m_name << " ID:" << m_id << endl; } public: string m_name; int m_id; }; int main() { Student stu; stu.setName("德玛西亚"); stu.setID(250); stu.showStudent(); system("pause"); return 0; }封装意义二:
类在设计时,可以把属性和行为放在不同的权限下,加以控制
访问权限有三种:
1. public 公共权限
2. protected 保护权限
3. private 私有权限**示例:
//三种权限 //公共权限 public 类内可以访问 类外可以访问 //保护权限 protected 类内可以访问 类外不可以访问 //私有权限 private 类内可以访问 类外不可以访问 class Person { //姓名 公共权限 public: string m_Name; //汽车 保护权限 protected: string m_Car; //银行卡密码 私有权限 private: int m_Password; public: void func() { m_Name = "张三"; m_Car = "拖拉机"; m_Password = 123456; } }; int main() { Person p; p.m_Name = "李四"; //p.m_Car = "奔驰"; //保护权限类外访问不到 //p.m_Password = 123; //私有权限类外访问不到 system("pause"); return 0; }
更多推荐
打印结果:


核心要点:
2.3 引用做函数参数
结论:C++推荐用引用技术,因为语法方便,引用本质是指针常量,但是所有的指针操作编译器都帮我们做了
所有评论(0)