C++核心编程全解析:从函数到面向对象的深度探索

目录

  • 前言
  • 一、函数
  • 1.1 格式
  • 1.2 调用方式
  • 1.3 函数传参方式
  • 1.4 字符串操作函数
  • 二、c++中特殊函数 
  • 2.1 内联函数(inline)
  • 2.2 函数重载(overload)
  • 2.3 函数的默认参数
  • 三、引用变量
  • 3.1 定义
  • 3.2 格式
  • 3.3 特性
  • 3.4 示例代码解析
  • 3.5 指针和引用的区别
  • 四、string 类详解
  • 4.1 基本概念
  • 4.2 初始化方式
  • 4.3 常用操作
  • 4.3.1 字符串拼接
  • 4.3.2 长度与容量
  • 4.3.3 字符访问
  • 4.3.4 子串与查找
  • 4.3.5 比较与转换
  • 4.3.6 清空与交换
  • 4.4 与C风格字符串互操作
  • 4.5 注意事项
  • 4.6 完整示例代码
  • 五、new和delete
  • 5.1 基本概念
  • 5.2 内存泄漏详解
  • 5.3 代码示例
  • 六、名字空间 (Namespace)
  • 6.1 作用
  • 6.2 定义格式
  • 6.3 声明格式
  • 6.4 使用方式
  • 6.5 示例代码解析
  • 6.6 扩展应用
  • 七、面向对象特征
  • 7.1 面向对象三大核心特征
  • 7.2 面向过程 vs 面向对象对比
  • 7.3案例解析
  • 7.3.1 图书管理系统(面向对象设计)
  • 7.3.2 大象装冰箱问题(python)
  • 八、类和对象
  • 8.1 核心概念
  • 8.2 类定义格式
  • 8.3 对象创建方式
  • 8.4 成员初始化方法
  • 8.5 代码示例 
  • 九、构造函数
  • 9.1 定义
  • 9.2 语法格式
  • 9.3 特性
  • 9.4 特殊用法
  • 9.4.1 成员初始化列表
  • 9.4.2 禁止隐式构造
  • 9.4.3 拷贝构造函数
  • 9.4.3.1 深拷贝 vs 浅拷贝
  • 十、new、delete 与 malloc、free的区别
  • 10.1 语言和类型
  • 10.2 构造与析构
  • 10.3 内存分配位置
  • 10.4 错误处理
  • 10.5 内存重新分配
  • 10.6 重载机制
  • 10.7 使用场景
  • 10.8 示例对比
  • 10.9 总结
  • 总结 

前言

在上一期的技术解析中,我们系统性地探讨了指针这一C++核心机制的内在逻辑,深入剖析了指针在内存管理、数据结构实现以及高效算法设计中的关键作用。作为承前启后的重要章节,在今天的深度解析中,我们将以更专业的视角继续推进C++知识体系的构建。本次探讨不仅将延续指针相关的高级话题,更将开启对C++现代编程范式的全面解读,通过精心设计的案例分析和底层原理图示,我们将共同解构这门经典语言的演进脉络,揭示其在高性能计算、系统编程等关键领域的独特优势,帮助您掌握C++编程的艺术,在软件工程的实践中实现从语法认知到工程思维的跨越式提升。


一、函数

函数是完成特定功能的独立代码模块,程序代码独立。

1.1 格式

(存储类型) 数据类型 函数名(形式参数)
{
    函数体;
    return 常量、变量或表达式;
}

补充说明

  • 存储类型:可选,如 static 表示静态函数(仅当前文件可见)。
  • 数据类型:返回值类型,void 表示无返回值。
  • 返回类型:可返回基本类型、指针、引用或对象。若返回局部变量的指针/引用会导致未定义行为。

1.2 调用方式

不接收返回值

函数名(实参);

接收返回值

数据类型 变量名 = 函数名(实参);

示例

#include 
using namespace std;

void print() {
    cout << "hello world!" << endl;
}

// 函数声明
int add(int a, int b);

int main() {
    print();                // 不接收返回值
    int sum = add(3, 4);    // 接收返回值
    cout << sum << endl;
    return 0;               // C++中可省略,默认返回0
}

// 函数定义
int add(int a, int b) {
    return a + b;
}

1.3 函数传参方式

  1. 值传递
    单向传递,形参是实参的副本,修改形参不影响实参。
  2. 地址传递
    双向传递,通过指针操作实参内存地址。
  3. 引用传递(C++特有)
    双向传递,形参是实参的别名,更简洁安全。

示例(交换函数)

// 地址传递
void swap_ptr(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 引用传递
void swap_ref(int &a, int &b) {
    int temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 3, y = 4;
    swap_ptr(&x, &y);   // 需取地址
    swap_ref(x, y);     // 直接传变量
    cout << x << " " << y << endl;  // 输出 4 3
    return 0;
}

1.4 字符串操作函数

C风格字符串(需包含

  • strlen:计算字符串长度(不含 \0)。
  • strcpy:复制字符串(存在溢出风险)。
  • strncpy:安全版本,限制复制长度。
  • strcat:拼接字符串。
  • strcmp:按字典序比较字符串。
#include 
#include   // 包含字符串操作函数头文件
using namespace std;

int main(void)
{
    char buf[32] = "world";  // 声明并初始化32字节字符数组,注意字符串末尾自动添加'\0'
    char str[32] = "world";  // 声明并初始化第二个32字节字符数组

    cout << sizeof(buf) << endl;    // 输出数组总大小:32(sizeof计算内存大小,包含未使用的空间)
    cout << strlen(buf) << endl;    // 输出字符串有效长度:5(strlen计算到'\0'前的字符数)

    // 字符串操作示例(当前被注释)
    // strcpy(buf, "world123");      // 字符串复制:将第二个参数复制到第一个参数指向的数组
    // strcat(buf, "!");             // 字符串拼接:将第二个参数追加到第一个参数字符串末尾
    // cout << buf << endl;          // 输出操作后的buf内容

    cout << strcmp(buf, str) << endl; // 字符串比较:按字典序比较
                                      // 返回正数(buf>str),负数(buf

二、c++中特殊函数 

2.1 内联函数(inline

定义
在函数定义前添加 inline 关键字,建议编译器将函数体直接展开到调用处,避免函数调用的开销(如压栈、跳转和返回操作)。

适用场景

  • 函数体短小(如 5 行以内)且频繁调用。
  • 不含复杂控制语句(如循环、递归、switch)。
  • 常用于存取类成员的简单函数(如 getter/setter)。

注意事项

  • inline 是建议性关键字,编译器可能忽略(如函数体过大或含递归)。
  • 内联函数通常定义在头文件中(编译器需在调用处看到完整定义)。
  • 优缺点:减少调用开销,但可能增加代码体积(滥用会导致代码膨胀)。

示例

// 头文件声明
inline int max(int a, int b) {
    return a > b ? a : b;
}

2.2 函数重载(overload

规则

  • 同一作用域内,函数名相同但参数列表不同(类型、个数、顺序)。
  • 返回值不同不能构成重载
  • 依赖 名称修饰(Name Mangling):编译器通过参数列表生成唯一标识符。

示例

// 演示函数重载的示例程序

// 两个整数相加的函数重载
// 参数:a - 第一个整数,b - 第二个整数
// 返回值:两个整数的和
int add(int a, int b)
{
    return a + b;
}

// 三个整数相加的函数重载
// 参数:a - 第一个整数,b - 第二个整数,c - 第三个整数
// 返回值:三个整数的和
int add(int a, int b, int c)
{
    return a + b + c;
}

// 两个双精度浮点数相加的函数重载
// 参数:c - 第一个双精度数,d - 第二个双精度数
// 返回值:两个双精度数的和
double add(double c, double d)
{
    return c + d;
}

int main(void)
{
    // 调用两个整数相加版本(参数类型和数量匹配)
    cout << add(3, 4) << endl;        // 输出 7
    
    // 调用三个整数相加版本(参数数量匹配)
    cout << add(3, 4, 5) << endl;     // 输出 12
    
    // 调用双精度版本(参数类型匹配,自动选择浮点数版本)
    cout << add(1.23, 2.34) << endl;  // 输出 3.57
    
    return 0;
}

/*
代码特点说明:
1. 通过参数的数量和类型不同实现函数重载
2. 编译器根据调用时的参数自动选择匹配的函数版本
3. 演示了C++中不同类型和参数数量的函数重载方式
4. 浮点数版本使用不同的参数名(c,d)说明参数命名不影响重载解析
*/

注意事项

避免隐式转换导致的歧义:

add(1, 2.5); // 歧义:可能调用 int add(int, int) 或 double add(double, double)
  • 作用域限制:不同作用域的同名函数不构成重载(如类内/外、不同命名空间)。

2.3 函数的默认参数

规则

  • 默认参数只能在函数声明中指定(若声明与定义分离)。
  • 向右原则:默认参数必须从右向左连续设置。
  • 避免与函数重载同时使用,可能引发二义性。

示例

// 正确声明
int add(int a, int b = 1, int c = 2);

// 错误:非默认参数在默认参数后
int add(int a, int b = 1, int c);

// 二义性示例
void print(int x) {}
void print(int x, int y = 0) {}
print(10); // 错误:编译器无法确定调用哪个版本

高级特性

多次声明可扩展默认参数(不可修改已存在的默认值):

void func(int a, int b, int c = 3);
void func(int a, int b = 2, int c); // 合法:扩展 b 的默认值

三、引用变量

3.1 定义

引用(Reference)是 C++ 中为变量起的一个别名,不独立开辟内存空间,而是共享被引用变量的内存地址。引用是 C++ 特有的语法特性,常用于函数参数传递、返回值优化等场景。


3.2 格式

数据类型 &引用名 = 被引用变量;  // 必须初始化!

示例:

int a = 10;i
nt &ref = a;  // ref 是 a 的别名

3.3 特性

必须初始化
引用在定义时必须绑定到一个变量,且之后不可更改绑定对象。

int x = 5;
int &r1 = x;  // 正确
int &r2;       // 错误:未初始化

通过引用修改变量
对引用的操作等同于直接操作被引用的变量。

int a = 10;
int &ref = a;
ref = 20;      // a 的值变为 20

const 引用

int a = 10;
const int &b = a;  // 不可通过 b 修改 a
a = 20;            // 合法:通过原变量修改

const int &c = 100;      // 合法:绑定到字面量
const int &d = a + 1;    // 合法:绑定到表达式结果
  • const 修饰的引用不能通过引用修改值,但原变量仍可修改。
  • 可以绑定到常量、表达式或临时对象(需加 const)。
  1. 引用的本质
    底层通过指针实现,但语法上隐藏了指针操作,更安全直观。


3.4 示例代码解析

#include 
using namespace std;

int main() {
    int a = 10;
    const int &b = a;  // const 引用
    // b = 20;         // 错误:不能通过 const 引用修改值
    a = 20;            // 合法:直接修改原变量
    cout << a << " " << b << endl;  // 输出 20 20

    const int &c = 100;  // 绑定到常量
    cout << c << endl;    // 输出 100
    return 0;
}

3.5 指针和引用的区别

区别点 指针 引用
初始化 可以不初始化(但危险) 必须初始化
空值 可指向 nullptr 不能为空
重绑定 可修改指向的地址 一旦绑定,不可更改
内存占用 有自己的内存地址(存储指针值) 无独立内存(与被引用变量共享地址)
操作语法 需用 * 解引用 直接使用,无需特殊符号
多级间接访问 支持多级(如 int **p 仅一级
安全性 可能悬空或越界 更安全(非空且类型严格匹配)
函数参数 需显式传递地址 隐式传递地址,代码更简洁

关键总结

  1. 引用是“安全版指针”,强制初始化且不可为空。
  2. 引用更适用于参数传递和返回值,避免拷贝开销;指针更灵活,适合动态内存管理。
  3. 从底层看,引用通过指针实现,但语法上隐藏了间接访问的复杂性。

四、string 类详解

4.1 基本概念

  • 头文件#include (必须显式包含,即使使用 using namespace std;
  • 定位:替代C风格字符串(char*/char[]),提供自动内存管理、丰富的成员函数和运算符重载。
  • 底层:本质是 basic_string 的别名,动态分配内存,无需手动管理。

4.2 初始化方式

string s1;            // 空字符串
string s2 = "hello";  // 字面量初始化
string s3("world");   // 构造函数初始化
string s4(5, 'a');    // 生成 "aaaaa"
string s5(s2);        // 拷贝构造

4.3 常用操作

4.3.1 字符串拼接

string s = "Hello";
s += " World";        // 运算符重载(推荐)
s.append("!");        // 追加字符串
s.push_back('!');     // 追加单个字符
s.insert(2, "xyz");   // 在索引2插入"xyz",原内容后移

4.3.2 长度与容量

s.size();     // 实际字符数(与 s.length() 等价)
s.empty();    // 判断是否为空(等价于 s.size() == 0)
s.capacity(); // 当前分配的内存容量
s.resize(10); // 调整字符串长度(截断或填充默认字符)
s.reserve(100); // 预分配内存,避免频繁扩容

4.3.3 字符访问

s[5] = 'X';      // 快速访问(无边界检查,越界行为未定义)
s.at(5) = 'X';   // 带边界检查(越界抛出 std::out_of_range)
s.front();       // 首字符
s.back();        // 末字符

4.3.4 子串与查找

string sub = s.substr(3, 5); // 从索引3开始取5个字符
size_t pos = s.find("lo");   // 返回首次出现的位置(未找到返回 string::npos)
pos = s.rfind("l");          // 反向查找
s.replace(0, 5, "Hi");       // 替换前5个字符为"Hi"

4.3.5 比较与转换

if (s == "Hello") { ... }   // 运算符重载比较(区分大小写)
int cmp = s.compare("Hi");  // 返回0(相等)、正数(s大)或负数

// 数值转换(C++11)
int num = stoi("123");
double d = stod("3.14");
string str = to_string(42);

4.3.6 清空与交换

s.clear();        // 清空内容(size=0,capacity不变)
s.swap(s2);       // 高效交换两个字符串内容

4.4 与C风格字符串互操作

const char* cstr = s.c_str(); // 获取C风格字符串(只读,以'\0'结尾)
char* data = s.data();        // 获取字符数组(C++17后可写,但需谨慎)
s = "New String";             // 直接赋值C字符串

4.5 注意事项

  1. 越界访问operator[] 不检查边界,at() 会抛出异常。
  2. 迭代器失效:修改字符串可能导致指向其内容的指针/迭代器失效。
  3. 性能优化:频繁拼接时,使用 reserve() 预分配内存减少扩容开销。
  4. 字面量后缀:C++14支持 "Hello"s 后缀(需 using namespace std::string_literals;),直接生成 string 对象。

4.6 完整示例代码

#include 
#include 
using namespace std;

int main() {
    string s = "Hello";
    s += " C++";                  // s = "Hello C++"
    s.insert(5, " Awesome");      // s = "Hello Awesome C++"
    s.replace(6, 7, "Standard");  // s = "Hello Standard C++"
    
    cout << "Length: " << s.size() << endl;  // 输出 18
    cout << "Substr: " << s.substr(6, 7) << endl; // "Standard"
    
    if (s.find("C++") != string::npos) {
        cout << "Found 'C++'" << endl;
    }
    
    return 0;
}

五、newdelete

5.1 基本概念

new 关键字

  • 用于在堆区动态分配内存空间
  • 支持分配单个对象和数组空间
  • 自动计算类型大小,返回对应类型的指针

delete 关键字

  • 用于释放通过 new 分配的内存
  • 必须与 new 配对使用
  • 数组释放需使用 delete[] 语法

5.2 内存泄漏详解

定义

内存泄漏指程序未能正确释放已申请的堆内存,导致系统无法重新利用该内存区域,长期积累可能引发程序性能下降或崩溃。

常见原因

  • 未调用 delete/delete[] 释放内存
  • 指针被重新赋值导致原内存地址丢失
  • 异常处理路径中未释放内存
  • 循环中分配内存但未释放
  • 类析构函数未正确释放成员指针
[分配] → [使用] → [释放]
  ↑           ↓
  └─泄漏点───┘

5.3 代码示例

// 基本用法
int main() 
{
    // 单个整型分配(带初始化)
    int* ptr = new int(10);  // 分配并初始化为10
    cout << *ptr << endl;
    delete ptr;             // 正确释放
    
    // 数组分配
    char* str = new char[10];
    strcpy(str, "hello");
    cout << str << endl;
    delete[] str;           // 数组必须使用delete[]
    
    // 安全指针处理
    ptr = nullptr;          // 释放后置空避免野指针
    str = nullptr;
    
    return 0;
}

六、名字空间 (Namespace)

6.1 作用

提供逻辑分组机制,解决不同模块/库之间的命名冲突问题。尤其在以下场景至关重要:

  • 大型项目多人协作开发
  • 使用第三方库时避免符号重复
  • 模块化代码组织

6.2 定义格式

namespace 名称 {
    // 函数、变量、类、类型别名等声明/定义
    // 支持嵌套定义(C++11起支持内联命名空间)
}

特性说明:

  • 允许跨多个文件扩展定义(编译器自动合并)
  • 支持匿名名字空间(文件作用域可见性)
  • 可定义别名:namespace短名 = 原长名字空间名;

6.3 声明格式

using namespace 名称;   // 引入整个名字空间(慎用)
using 名称::具体成员;   // 仅引入特定成员(推荐方式)

6.4 使用方式

显式限定(推荐用于关键代码)

名字空间::成员;

作用域限定符解析

::全局变量;          // 访问全局作用域
名字空间::成员;      // 访问指定名字空间

6.5 示例代码解析

#include 

// 全局变量
int a = 10; 

namespace demo {
    int a = 100;     // 隐藏全局变量
    int c = 30;
    
    void print() {
        std::cout << "Namespace function" << std::endl;
    }
}

// using namespace demo;  // 慎用全局引入

int main() {
    // 访问全局变量
    std::cout << "Global a: " << ::a << std::endl;  // 10
    
    // 完全限定访问
    std::cout << "demo::a: " << demo::a << std::endl;  // 100
    
    // 局部使用声明
    using demo::c;
    std::cout << "Local c: " << c << std::endl;      // 30
    
    // 函数调用
    demo::print();
    
    return 0;
}

6.6 扩展应用

// 名字空间别名
namespace d = demo;

// 跨文件扩展(头文件中常用)
namespace demo {
    void new_function();
}

// 匿名名字空间(替代static)
namespace {
    int internal_var;  // 文件内可见
}

七、面向对象特征

7.1 面向对象三大核心特征

特征 定义 作用 示例
封装 将数据(属性)和操作数据的方法(行为)绑定为一个独立的对象,隐藏内部实现细节 提高安全性(数据保护)、简化外部调用、降低模块耦合性 学生对象:封装姓名、成绩等属性,提供修改成绩()方法
继承 子类继承父类的属性和方法,并可扩展或重写父类功能 实现代码复用、建立类层次结构、支持多态 动物类派生出猫类狗类,复用eat()方法并重写sound()方法
多态 同一操作作用于不同对象时产生不同行为(静态多态、动态多态) 提高代码灵活性、支持接口统一调用、增强系统扩展性 父类Shape定义draw(),子类CircleSquare各自实现不同的draw()

7.2 面向过程 vs 面向对象对比

维度 面向过程(Procedure-Oriented) 面向对象(Object-Oriented)
程序结构 以函数为中心,程序由一系列顺序调用的函数组成 以对象为中心,程序由交互的对象构成
数据与行为关系 数据与函数分离,数据作为参数传递 数据和方法绑定在对象内部,通过对象操作数据
核心关注点 “如何做”(How to do),聚焦具体执行步骤 “谁来做”(Who does it),聚焦对象职责划分
代码复用 通过函数复用逻辑,但数据复用性差 通过继承、组合复用对象和代码
维护性 修改可能影响全局逻辑,维护成本高 对象独立性高,修改局部不影响整体
典型场景 简单算法、数学计算、一次性脚本 复杂系统、GUI开发、业务模型抽象

7.3案例解析

7.3.1 图书管理系统(面向对象设计)

  • 对象划分
    • Book类:封装书名、ISBN、借阅状态等属性,提供借阅()归还()方法。
    • User类:封装用户信息,管理借阅记录。
    • Library类:聚合图书和用户,实现全局管理逻辑。
  • 优势:新增功能(如逾期罚款)只需扩展相关类,无需重构整体逻辑。

7.3.2 大象装冰箱问题(python)

面向过程实现

def open_door(): ...
def put_elephant(): ...
def close_door(): 
open_door()
put_elephant()
close_door()

面向对象实现

class Refrigerator:
    def open(self): ...
    def store(self, obj): ...
    def close(self): ...

fridge = Refrigerator()
elephant = Elephant()
fridge.open()
fridge.store(elephant)
fridge.close()
  • 核心差异:面向对象通过Refrigerator对象封装行为,调用者无需关心冰箱内部机制。

八、类和对象

面向对象三大特性
├─ 封装(本节重点)
├─ 继承
└─ 多态

类成员类型
├─ 数据成员(属性)
├─ 成员函数(方法)
├─ 构造函数/析构函数
└─ 静态成员

8.1 核心概念

  • 抽象模板:描述一类事物的共性特征(如"手机"的通用属性),包含属性和行为。
  • 构成要素:
    • 属性(成员变量/数据成员):描述静态特征(如品牌重量),一般为名词。
    • 行为(成员函数/方法):描述动态操作(如拨打电话),一般为动词。

对象

  • 类的具体实例(如"我的华为手机"),通过类定义创建,占用实际内存空间。

8.2 类定义格式

class 类名 {
访问权限:
    成员变量;
    成员函数(参数){...}
};

访问权限

权限 类内访问 类外访问 继承特性
private 默认权限,子类不可访问
protected 子类可访问
public 子类可访问

示例与注意事项

class MobilePhone {
private:    // 成员变量通常设为私有(封装性)
    float weight;
    string brand;  // 建议使用string替代char数组
public:     // 成员函数通常设为公有(提供接口)
    void call() { 
        cout << "拨打电话中..." << endl; 
    }
};

// 错误示例:外部直接访问私有成员
int main() {
    MobilePhone mp;
    // mp.weight = 3.45;  // 编译错误!私有成员不可外部访问
    mp.call();            // 公有方法可正常调用
    return 0;
}

8.3 对象创建方式

类型 生命周期管理 语法示例 内存分配区域
栈内存对象 超出作用域自动销毁 MobilePhone mp; 栈空间
堆内存对象 需手动new/delete管理 MobilePhone* p = new MobilePhone(); 堆空间

代码示例

int main() {
    // 栈对象(自动回收)
    MobilePhone mp;
    mp.call();

    // 堆对象(需手动管理)
    MobilePhone* p = new MobilePhone();
    p->call();
    delete p;  // 避免内存泄漏

    return 0;
}

8.4 成员初始化方法

方式一:类内直接初始化(C++11+)

class MobilePhone {
private:
    float weight = 150.0;  // 初始值
    string brand = "Huawei";
};

方式二:构造函数初始化(推荐)

class MobilePhone {
private:
    float weight;
    string brand;
public:
    // 构造函数
    MobilePhone(float w, string b) : weight(w), brand(b) {}  // 初始化列表

    // Setter方法(可选)
    void setBrand(string b) { brand = b; }
};

int main() {
    MobilePhone mp(182.0, "Xiaomi");  // 通过构造函数初始化
    return 0;
}

方式三:成员函数赋值

class MobilePhone {
public:
    void initData(float w, string b) {
        weight = w;
        brand = b;
    }
};

8.5 代码示例 

#include 
#include 

using namespace std;

/**
 * 手机类
 * 用于表示手机设备的基本属性和行为
 */
class MobilePhone{
private:
    float weight;    // 手机重量(单位由实现定义)
    char brand[32];  // 品牌名称(最大存储31个字符+结束符)
    char color;      // 颜色标识(使用单个字符表示,如'r'=红色)

public:
    /**
     * 模拟打电话功能
     */
    void callTel(){
        cout << "打电话" << endl;
    }

    /**
     * 设置手机属性值
     * @param w 重量值
     * @param b 品牌字符串(需保证长度不超过31字符)
     * @param c 颜色标识字符
     */
    void setValue(float w, char *b, char c){
        weight = w;
        strcpy(brand, b);  // 注意:存在缓冲区溢出风险,需保证b长度合法
        color = c;
    }

    /**
     * 输出手机属性信息
     * 注意:当前实现不显示颜色信息
     */
    void getValue(){
        cout << "weight:" << weight << endl;
        cout << "brand:" << brand << endl;
    }
};

int main(void)
{
    MobilePhone mp;         // 创建手机实例
    mp.setValue(12.5, "huawei", 'b');  // 设置属性(重量12.5,品牌华为,颜色'b')
    mp.getValue();          // 输出属性信息
    return 0;
}

九、构造函数

9.1 定义

构造函数是类的特殊成员函数,在对象创建时自动调用,用于初始化类的数据成员。
特点:

  • 无返回值类型(包括void
  • 函数名与类名完全一致
  • 若未手动定义,编译器会生成默认构造函数

9.2 语法格式

类名(参数列表) {  
    // 初始化逻辑  
}  

示例:

class Example {  
public:  
    Example() {}               // 默认构造  
    Example(int a, int b) {}   // 带参构造  
};  

9.3 特性

  1. 自动生成规则

    • 若未定义任何构造函数,编译器生成无参默认构造
    • 若定义了任意构造函数,编译器不再生成默认构造。
  2. 重载与默认参数

    • 支持函数重载和参数默认值。
    • 示例:
      MobilePhone(float w, string b, char c = 'b') { /*...*/ }
  3. 初始化职责

    • 必须确保所有成员变量被合理初始化(基础类型可能为随机值)。
  4. 特殊成员初始化

    • const成员、引用成员或类类型成员(无默认构造),必须使用成员初始化列表

示例 

/*
 * 移动电话类 - 描述手机基本属性和功能
 */
class MobilePhone {
private:
    float weight;    // 手机重量(单位未指定,假设为克)
    string brand;   // 手机品牌(如xiaomi, huawei)
    char color;     // 颜色标识('b'=黑色,'w'=白色等,默认黑色)

public:
    /**
     * 构造函数(支持默认参数)
     * @param w 手机重量
     * @param b 品牌名称
     * @param c 颜色标识(默认值'b'表示黑色)
     */
    MobilePhone(float w, string b, char c = 'b') {
        weight = w;
        brand = b;
        color = c;
    }

    // 以下是被注释掉的构造函数重载示例(参数列表不同)
    // MobilePhone(float w, string b) {
    //     weight = w;
    //     brand = b;
    //     color = 'b';  // 不指定时也默认黑色
    // }

    /// 模拟打电话功能
    void callTel() {
        cout << "打电话" << endl;
    }

    /// 打印手机属性信息
    void getValue() {
        cout << "weight:" << weight << endl;
        cout << "brand:" << brand << endl;
        cout << "color:" << color << endl;
    }
};

int main(void)
{
    // 创建手机对象(显式指定所有参数)
    MobilePhone mp(10, "xiaomi", 'w');  // 白色小米手机
    // 创建手机对象(使用color默认参数)
    MobilePhone mp1(20, "huawei");      // 黑色华为手机(默认颜色)

    // 输出两个手机的信息
    mp.getValue();
    mp1.getValue();

    return 0;
}

9.4 特殊用法

9.4.1 成员初始化列表

  • 作用:直接初始化成员变量,避免先默认构造再赋值。
  • 语法
    类名(参数列表) : 成员1(值1), 成员2(值2) { /*...*/ }
  • 注意:初始化顺序由成员声明顺序决定,与列表顺序无关。

示例:

MobilePhone(float w, string b, char c) : weight(w), brand(b), color(c) {}

9.4.2 禁止隐式构造

  • 隐式调用场景
    MobilePhone mp = 200.5;  // 隐式调用 MobilePhone(float w)
  • 禁止方法:使用 explicit 关键字。
    explicit MobilePhone(float w) : weight(w) {}

示例 

/*
 * 这是一个演示 explicit 构造函数作用的示例程序
 */

#include 
using namespace std;

class MobilePhone {
private:
    float weight;  // 手机重量属性
    
public:
    // explicit 关键字禁止隐式类型转换
    // 只能通过显式调用来创建对象
    explicit MobilePhone(float w) : weight(w) {
        cout << "creater" << endl;  // 对象创建时输出提示信息
    }
    
    void getValue() {
        cout << "weight:" << weight << endl;  // 输出重量信息
    }
};

int main(void)
{
    // 错误示例:尝试隐式转换(编译将失败)
    // explicit构造函数禁止这种形式的初始化
    // MobilePhone mp1 = 200.5;  // 错误:不能将float隐式转换为MobilePhone对象
    
    // 正确用法1:显式调用构造函数
    MobilePhone mp1(200.5);
    
    // 正确用法2:使用拷贝初始化语法(实际仍是显式构造)
    // MobilePhone mp1 = MobilePhone(200.5);
    
    mp1.getValue();
    
    return 0;
}

/*
 * 关键点解释:
 * 1. explicit关键字的作用:
 *    - 只能用于单参数的构造函数
 *    - 防止编译器进行隐式的类型转换
 *    - 要求必须显式调用构造函数
 * 
 * 2. 错误行分析:
 *    MobilePhone mp1 = 200.5 实际上尝试执行:
 *    MobilePhone mp1 = MobilePhone(200.5)
 *    但由于explicit存在,这种隐式转换会被阻止
 * 
 * 3. 解决方法:
 *    - 使用直接初始化语法:MobilePhone mp1(200.5)
 *    - 或者显式构造临时对象:MobilePhone mp1 = MobilePhone(200.5)
 */

9.4.3 拷贝构造函数

  • 定义:用于通过同类型对象初始化新对象,形式为 类名(const 类名& other)
  • 默认行为:逐成员浅拷贝。
  • 自定义场景:类管理动态资源(如堆内存)时需深拷贝。

代码示例 

#include 
using namespace std;

// 定义手机类
class MobilePhone{
private:
    float weight; // 私有成员变量,表示手机的重量(单位:克)

public:
    // 构造函数,使用初始化列表初始化weight
    MobilePhone(float w):weight(w){
        cout << "creater" << endl; // 对象创建时输出构造提示
    }

    // 拷贝构造函数(参数应为const引用更规范,此处保留原始实现)
    MobilePhone(MobilePhone &mp){ // 通过现有对象创建新对象
        cout << "copy creater" << endl; // 拷贝构造时输出提示
        weight = mp.weight; // 复制重量属性
    }

    // 成员函数:显示手机重量信息
    void getValue(){
        cout << "weight:" << weight << endl;
    }
};

int main(void)
{
    MobilePhone mp1(10.5);    // 通过普通构造函数创建对象(输出creater)
    MobilePhone mp2(mp1);     // 通过拷贝构造函数创建对象(输出copy creater)
    
    mp1.getValue(); // 显示第一个对象的重量(10.5)
    mp2.getValue(); // 显示第二个对象的重量(同样为10.5)
    
    return 0;
}

9.4.3.1 深拷贝 vs 浅拷贝
类型 行为 适用场景
浅拷贝 复制指针值(共享同一内存) 无动态资源的简单对象
深拷贝 复制指针指向的内容(独立内存) 含动态资源(如堆内存)

浅拷贝风险

  • 双重释放内存
  • 数据意外修改(如示例中 mp2.setValue() 影响 mp1)。

示例 

/*
 * 内容: 演示深拷贝与浅拷贝区别的移动电话类实现
 */

#include 
#include   // 用于字符串操作函数

using namespace std;

// 移动电话类
class MobilePhone {
private:
    float weight;   // 手机重量(单位:克)
    char *brand;    // 手机品牌(使用动态内存分配)

public:
    // 参数化构造函数
    MobilePhone(float w, char *b) : weight(w) {
        // 为brand分配足够的内存(包含空终止符)
        brand = new char[strlen(b) + 1];
        // 深拷贝品牌字符串
        strcpy(brand, b);
    }

    // 拷贝构造函数
    MobilePhone(MobilePhone &mp) {
        cout << "copy creator" << endl;
        weight = mp.weight;
        
        // 深拷贝实现:
        // 1. 分配新内存
        // 2. 复制字符串内容
        brand = new char[strlen(mp.brand) + 1];
        strcpy(brand, mp.brand);
        
        /* 危险!浅拷贝实现(已注释)
         * 会导致两个对象共享同一内存地址
         * 可能引发双重释放或数据篡改
         */
        // brand = mp.brand;
    }

    // 注意:类缺少析构函数!
    // 建议添加:~MobilePhone() { delete[] brand; }

    // 修改品牌值的方法
    void setValue() {
        // 注意:存在潜在缓冲区溢出风险
        // 建议:使用strncpy或动态调整内存大小
        strcpy(brand, "huawei");
    }

    // 显示手机属性
    void getValue() {
        cout << "weight: " << weight << endl;
        cout << "brand: " << brand << endl;
    }
};

int main(void) {
    char buf[] = "xiaomi";  // 初始品牌
    
    // 创建原始对象
    MobilePhone mp1(10.5, buf);
    // 通过拷贝构造函数创建新对象
    MobilePhone mp2(mp1);   // 此时会调用深拷贝
    
    mp2.setValue();         // 修改拷贝对象的品牌
    
    // 验证深拷贝效果:
    mp1.getValue();         // 预期输出 xiaomi(原始品牌)
    mp2.getValue();         // 预期输出 huawei(修改后品牌)
    
    return 0;
}

/*
 * 潜在问题:
 * 1. 内存泄漏:未实现析构函数释放brand内存
 * 2. setValue()存在缓冲区溢出风险
 * 3. 建议使用std::string替代char*以简化内存管理
 */

十、newdeletemallocfree的区别

10.1 语言和类型

  • new/delete
    • C++ 运算符,直接支持类型安全。
    • 自动计算内存大小(如 new int),返回具体类型的指针(无需类型转换)。
  • malloc/free
    • C 语言标准库函数,类型不安全。
    • 需手动计算内存大小(如 sizeof(int)),返回 void*(需显式类型转换)。

10.2 构造与析构

  • new:调用对象的构造函数(初始化对象)。
  • delete:调用对象的析构函数(清理资源)。
  • malloc/free:仅分配/释放原始内存,不处理构造和析构。

10.3 内存分配位置

  • new 从自由存储区(free store)分配内存。
  • malloc 从堆(heap)分配内存。
    • 注:自由存储区和堆的具体实现可能由编译器决定,通常视为同一区域,但概念上不同。

10.4 错误处理

  • new 失败时抛出 std::bad_alloc 异常(可用 nothrow 版本返回 nullptr)。
  • malloc 失败时返回 NULL,需手动检查返回值。

10.5 内存重新分配

  • malloc 可通过 realloc 调整内存块大小
  • new 无直接扩展内存的机制,需手动分配新内存并拷贝数据。

10.6 重载机制

  • new/delete 可被重载,支持自定义内存管理策略。
  • malloc/free 不可重载。

10.7 使用场景

  • C++ 对象:必须使用 new/delete(确保构造/析构正确调用)。
  • 原始内存(如基本类型、无构造/析构的结构):可混用 malloc/free,但不建议(可能导致未定义行为)。

10.8 示例对比

// C++ 风格(推荐)
int* p1 = new int(10);     // 分配并初始化 int
delete p1;                 // 释放并调用析构函数(若有)

// C 风格(不推荐用于对象)
int* p2 = (int*)malloc(sizeof(int)); 
free(p2); 

// 错误示例:混用
int* p3 = (int*)malloc(sizeof(int)); 
delete p3;  // 未定义行为(未调用构造函数,但调用了析构函数)

10.9 总结

new/delete malloc/free
语言 C++ C
类型安全
构造/析构 自动调用 不处理
错误处理 异常 返回 NULL
内存调整 不支持 realloc
重载 支持 不支持

始终在 C++ 中对对象使用 new/delete,避免混用!


总结 

        本文系统解析C++编程核心知识,涵盖函数定义与传参方式(值传递、地址传递、引用传递)、字符串操作函数及C++特性(内联函数、函数重载、默认参数)。深入探讨引用变量本质及其与指针的差异,详解string类的动态内存管理与操作。对比new/delete与malloc/free的内存管理机制,剖析内存泄漏成因与防范。阐述名字空间的作用及面向对象三大特征(封装、继承、多态),结合类与对象设计实践,解析构造函数(含深/浅拷贝)、成员初始化列表等关键机制,最终通过代码示例展现C++高效、安全的编程范式。

 

你可能感兴趣的:(c++,c++,内存管理,面向对象编程,引用与指针,类构造函数,名字空间,字符串处理)