本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:《C++编程语言第三版》是Y. Daniel Liang编著的经典教材,系统讲解了C++的基础语法与面向对象编程核心概念,适合初学者与进阶开发者学习。本书配套源代码集合了书中所有示例程序,便于读者快速理解与实践C++编程。通过这些源码的学习与修改,读者可以深入掌握变量、函数、指针、类、继承、模板、STL等关键知识点,提升实际编程能力,为开发高性能C++应用程序打下坚实基础。

1. C++基础语法详解与示例

C++作为一门静态类型、面向对象的编程语言,其语法结构严谨且高效,广泛应用于系统开发、游戏引擎、嵌入式系统等领域。掌握其基础语法是深入学习的前提。

本章将从最基础的程序结构入手,讲解C++代码的基本组成形式,包括主函数、语句、表达式与程序入口点。随后,我们将介绍注释的书写方式,包括单行注释 // 与多行注释 /* */ ,以及关键字的使用规范。此外,还将演示如何使用标准输入输出流 cin cout 进行数据的输入与输出操作,辅以简单易懂的示例代码,帮助读者快速上手编写第一个C++程序。

2. 变量声明与数据类型应用

在C++中,变量是程序中存储数据的基本单元。变量的声明与数据类型的正确使用不仅关系到程序的运行效率,也影响代码的可读性和可维护性。本章将从变量的基本概念入手,逐步深入到基本数据类型、复合数据类型以及其在实际项目中的应用,帮助开发者理解C++变量与数据类型的全貌,并掌握其在复杂场景下的应用技巧。

2.1 变量的基本概念与声明方式

变量是程序中用于存储数据的标识符,它在程序运行时占据内存空间,并通过变量名进行访问。C++是一种静态类型语言,因此在使用变量前必须声明其类型。

2.1.1 变量命名规则与作用域

C++的变量命名需遵循以下规则:

  • 变量名只能由字母、数字和下划线组成;
  • 变量名不能以数字开头;
  • 变量名不能是C++关键字;
  • 变量名区分大小写(例如 age Age 不同);
  • 推荐使用有意义的名称,如 studentName score 等。

作用域 是指变量在程序中可以被访问的范围。C++中常见的作用域包括:

作用域类型 描述
局部作用域 在函数或代码块内部定义的变量,仅在该函数或块内有效
全局作用域 在函数外部定义的变量,整个程序中都可访问
类作用域 类的成员变量,在类的成员函数中可访问
命名空间作用域 在命名空间中定义的变量,可通过作用域解析符访问
示例代码
#include <iostream>
using namespace std;

int globalVar = 100; // 全局变量

void func() {
    int localVar = 200; // 局部变量
    cout << "Local variable: " << localVar << endl;
}

int main() {
    func();
    cout << "Global variable: " << globalVar << endl;
    return 0;
}

逐行分析:

  • 第3行:定义一个全局变量 globalVar
  • 第6行:定义一个函数 func()
  • 第7行:在函数内部定义局部变量 localVar
  • 第8行:输出局部变量;
  • 第11行:调用函数 func()
  • 第12行:访问全局变量并输出;
  • 第13行:主函数返回0,程序结束。

2.1.2 变量的声明与初始化

变量在使用前必须进行 声明 ,声明格式为:

type variable_name;

同时,可以一并进行 初始化 操作:

type variable_name = value;
示例代码
#include <iostream>
using namespace std;

int main() {
    int age;           // 声明
    age = 25;          // 初始化

    double salary = 5000.50; // 声明并初始化

    cout << "Age: " << age << endl;
    cout << "Salary: " << salary << endl;

    return 0;
}

逻辑分析:

  • 第6行:先声明变量 age ,第7行再赋值;
  • 第9行:声明并初始化 salary
  • 第11、12行:输出变量值;
  • 变量初始化可避免未定义行为(如访问未初始化变量)。

2.2 C++基本数据类型详解

C++的基本数据类型主要包括整型、浮点型、字符型、布尔型和void类型。它们是构建更复杂数据结构的基础。

2.2.1 整型、浮点型与字符型

C++中整型包括 int short long long long 等;浮点型包括 float double ;字符型为 char

示例代码
#include <iostream>
using namespace std;

int main() {
    int age = 25;
    float price = 9.99f;
    double pi = 3.141592653589793;
    char grade = 'A';

    cout << "Age: " << age << endl;
    cout << "Price: " << price << endl;
    cout << "Pi: " << pi << endl;
    cout << "Grade: " << grade << endl;

    return 0;
}

参数说明:

  • int :用于存储整数,通常为4字节;
  • float :单精度浮点数,后缀 f 用于明确为 float 类型;
  • double :双精度浮点数,精度更高;
  • char :用于存储字符,占1字节。

2.2.2 布尔型与void类型

布尔型 bool 只有两个值: true false void 类型通常用于函数无返回值的情况。

示例代码
#include <iostream>
using namespace std;

bool isEven(int num) {
    return num % 2 == 0;
}

void printMessage() {
    cout << "This is a void function." << endl;
}

int main() {
    int number = 4;
    if (isEven(number)) {
        cout << number << " is even." << endl;
    }

    printMessage();
    return 0;
}

逻辑分析:

  • 函数 isEven 返回布尔值;
  • printMessage 无返回值,返回类型为 void
  • if 语句根据布尔值执行不同逻辑。

2.2.3 类型转换与sizeof运算符

C++允许在不同数据类型之间进行 显式 隐式 类型转换。 sizeof 运算符用于获取变量或类型在内存中所占字节数。

示例代码
#include <iostream>
using namespace std;

int main() {
    int i = 100;
    double d = i; // 隐式转换

    cout << "Implicit conversion: " << d << endl;

    double d2 = 3.14;
    int i2 = static_cast<int>(d2); // 显式转换
    cout << "Explicit conversion: " << i2 << endl;

    cout << "Size of int: " << sizeof(int) << " bytes" << endl;
    cout << "Size of double: " << sizeof(double) << " bytes" << endl;

    return 0;
}

逻辑分析:

  • 第7行:将 int 赋值给 double ,自动完成隐式转换;
  • 第10行:使用 static_cast 进行显式转换;
  • 第14、15行:使用 sizeof 查看不同类型所占字节数。

2.3 复合数据类型的使用

复合数据类型是由基本类型组合而成的更复杂的数据结构,主要包括数组、结构体、枚举等。

2.3.1 数组的定义与访问

数组是一组相同类型的数据集合。数组下标从0开始。

示例代码
#include <iostream>
using namespace std;

int main() {
    int scores[5] = {85, 90, 78, 92, 88};

    for(int i = 0; i < 5; ++i) {
        cout << "Score " << i + 1 << ": " << scores[i] << endl;
    }

    return 0;
}

逻辑分析:

  • 第6行:定义一个长度为5的整型数组并初始化;
  • 第8行:通过 for 循环访问数组元素;
  • 数组访问使用 array[index] 语法。

2.3.2 枚举与结构体的基本应用

枚举 enum )用于定义一组命名的整型常量,常用于状态码或选项集合。

结构体 struct )允许将多个不同类型的数据组合成一个整体。

示例代码
#include <iostream>
#include <string>
using namespace std;

enum class Color { Red, Green, Blue };

struct Student {
    string name;
    int age;
    double gpa;
};

int main() {
    Student s1;
    s1.name = "Alice";
    s1.age = 20;
    s1.gpa = 3.8;

    cout << "Student: " << s1.name << ", Age: " << s1.age << ", GPA: " << s1.gpa << endl;

    Color c = Color::Green;
    cout << "Selected color: " << static_cast<int>(c) << endl;

    return 0;
}

逻辑分析:

  • 第8行:定义枚举类 Color ,使用 enum class 防止命名冲突;
  • 第11行:定义结构体 Student ,包含三个成员;
  • 第17行:访问结构体成员;
  • 第20行:使用 static_cast 将枚举转为整型输出。

2.3.3 typedef与类型别名的使用

typedef 用于为已有类型定义别名,提高代码可读性。C++11引入了 using 关键字也可实现类似功能。

示例代码
#include <iostream>
using namespace std;

typedef int MyInteger;
using MyDouble = double;

int main() {
    MyInteger a = 42;
    MyDouble b = 3.1415;

    cout << "a: " << a << ", b: " << b << endl;

    return 0;
}

逻辑分析:

  • typedef int MyInteger; int 别名为 MyInteger
  • using MyDouble = double; 是C++11中等价写法;
  • 使用类型别名后代码更具可读性,尤其在复杂类型中(如指针、结构体)。

2.4 数据类型的实际应用案例

2.4.1 不同数据类型在内存中的表现

了解数据类型在内存中的表现有助于优化程序性能。可以通过 sizeof 和指针操作观察变量在内存中的布局。

示例代码
#include <iostream>
using namespace std;

int main() {
    int a = 0x12345678;
    char* p = reinterpret_cast<char*>(&a);

    cout << "Size of int: " << sizeof(int) << " bytes" << endl;
    cout << "Memory layout (hex):" << endl;

    for(size_t i = 0; i < sizeof(int); ++i) {
        printf("%02X ", static_cast<unsigned char>(p[i]));
    }
    cout << endl;

    return 0;
}

逻辑分析:

  • 第6行:将 int 指针转为 char* ,以便逐字节访问;
  • 第10行:使用 printf 以十六进制格式输出每个字节;
  • 此示例可观察到字节序(小端或大端)在内存中的表现。

2.4.2 使用数据类型优化程序性能

选择合适的数据类型可以减少内存占用、提升运行效率。例如,在不需要浮点数的场合使用整型;在空间受限的嵌入式系统中使用 short 代替 int

示例代码(性能对比)
#include <iostream>
#include <chrono>
using namespace std;
using namespace std::chrono;

int main() {
    const int N = 1e7;

    // 使用int
    auto start = high_resolution_clock::now();
    int sum1 = 0;
    for(int i = 0; i < N; ++i) {
        sum1 += i;
    }
    auto end = high_resolution_clock::now();
    auto duration1 = duration_cast<milliseconds>(end - start);

    // 使用double
    start = high_resolution_clock::now();
    double sum2 = 0.0;
    for(int i = 0; i < N; ++i) {
        sum2 += i;
    }
    end = high_resolution_clock::now();
    auto duration2 = duration_cast<milliseconds>(end - start);

    cout << "Int loop time: " << duration1.count() << " ms" << endl;
    cout << "Double loop time: " << duration2.count() << " ms" << endl;

    return 0;
}

逻辑分析:

  • 分别使用 int double 进行1千万次累加;
  • 浮点运算通常比整型运算慢;
  • 输出结果可比较两种类型在时间上的差异。

小结(非总结,仅为章节内容回顾)

  • 变量是C++中数据存储的基本单位,其命名与作用域影响程序行为;
  • 基本数据类型包括整型、浮点型、字符型、布尔型和void;
  • 类型转换和 sizeof 有助于理解和控制数据在内存中的表现;
  • 数组、结构体、枚举等复合类型扩展了数据组织方式;
  • 合理选择数据类型可以优化程序性能和内存使用;
  • 实际开发中,应结合具体场景选择最合适的数据类型,提升代码效率与可维护性。

本章内容为后续深入学习指针、函数、类等高级特性打下了坚实基础。下一章将围绕C++中的运算符与流程控制结构展开,帮助开发者掌握程序逻辑的构建方式。

3. 运算符与流程控制结构

运算符与流程控制结构是C++程序设计的核心基础之一。它们决定了程序的逻辑走向和数据处理方式。本章将系统地介绍C++中各种运算符的分类、使用方式与优先级规则,并深入讲解条件判断与循环控制语句的语法结构与应用场景。通过本章内容,读者将能够编写结构清晰、逻辑严密的C++程序,并掌握流程控制在实际开发中的应用技巧。

3.1 运算符的分类与优先级

运算符是C++语言中用于对数据进行操作的符号,它们可以用于执行算术、逻辑、比较、赋值等操作。理解运算符的分类和优先级是编写高效且逻辑正确的程序的关键。

3.1.1 算术运算符与逻辑运算符

算术运算符 用于执行基本的数学运算,包括加法(+)、减法(-)、乘法(*)、除法(/)和取模(%)。

#include <iostream>
using namespace std;

int main() {
    int a = 10, b = 3;
    cout << "a + b = " << a + b << endl; // 加法
    cout << "a - b = " << a - b << endl; // 减法
    cout << "a * b = " << a * b << endl; // 乘法
    cout << "a / b = " << a / b << endl; // 除法(整数除法)
    cout << "a % b = " << a % b << endl; // 取模
    return 0;
}

逐行分析:

  • int a = 10, b = 3; :定义两个整型变量 a b ,并赋初值。
  • a + b :加法运算,结果为13。
  • a / b :整数除法,结果为3,因为两个操作数都是整数。
  • a % b :取模运算,结果为1。

逻辑运算符 用于组合或判断布尔表达式的真假,包括逻辑与(&&)、逻辑或(||)和逻辑非(!)。

bool x = true, y = false;
cout << "x && y = " << (x && y) << endl; // false
cout << "x || y = " << (x || y) << endl; // true
cout << "!x = " << (!x) << endl;         // false

参数说明:
- && :只有两个表达式都为 true 时,结果才为 true
- || :只要有一个表达式为 true ,结果即为 true
- ! :取反操作符,将布尔值取反。

3.1.2 赋值运算符与关系运算符

赋值运算符 用于将值赋给变量,包括简单赋值(=)和复合赋值(+=、-=、*=、/=、%=等)。

int a = 5;
a += 3; // 等价于 a = a + 3;
cout << "a = " << a << endl; // 输出 8

关系运算符 用于比较两个值之间的关系,包括等于(==)、不等于(!=)、大于(>)、小于(<)、大于等于(>=)、小于等于(<=)。

int x = 10, y = 5;
cout << "x > y = " << (x > y) << endl;   // true
cout << "x == y = " << (x == y) << endl; // false

3.1.3 条件运算符与位运算符

条件运算符 (?:)是唯一的三元运算符,其语法为:

condition ? expression1 : expression2;

示例:

int age = 20;
string result = (age >= 18) ? "Adult" : "Minor";
cout << result << endl; // 输出 Adult

位运算符 用于对整数类型的二进制位进行操作,包括按位与(&)、按位或(|)、按位异或(^)、按位非(~)、左移(<<)、右移(>>)。

int a = 5;  // 二进制: 0101
int b = 3;  // 二进制: 0011
cout << "a & b = " << (a & b) << endl; // 0001 = 1
cout << "a | b = " << (a | b) << endl; // 0111 = 7
cout << "a << 1 = " << (a << 1) << endl; // 1010 = 10

运算符优先级表格

优先级 运算符 类型 结合性
1 () [] -> . 成员访问 左到右
2 ! ~ ++ -- + - * & 单目 右到左
3 * / % 算术 左到右
4 + - 算术 左到右
5 << >> 位移 左到右
6 < <= > >= 关系 左到右
7 == != 关系 左到右
8 & 位与 左到右
9 ^ 位异或 左到右
10 | 位或 左到右
11 && 逻辑与 左到右
12 || 逻辑或 左到右
13 ?: 条件 右到左
14 = += -= *= /= %= <<= >>= &= ^= |= 赋值 右到左
15 , 逗号 左到右

3.2 程序流程控制语句

流程控制语句决定了程序的执行路径,包括条件判断语句(if、switch)和循环语句(for、while、do-while),以及控制流程跳转的语句(break、continue、goto)。

3.2.1 if语句与switch语句的使用

if语句 用于根据条件判断执行特定代码块。

int score = 85;
if(score >= 90) {
    cout << "A" << endl;
} else if(score >= 80) {
    cout << "B" << endl;
} else {
    cout << "C" << endl;
}

switch语句 适用于多个固定值的判断,常用于菜单选择、状态机等场景。

int choice = 2;
switch(choice) {
    case 1:
        cout << "Option 1 selected" << endl;
        break;
    case 2:
        cout << "Option 2 selected" << endl;
        break;
    default:
        cout << "Invalid option" << endl;
}

注意: 每个 case 后需加 break ,否则会继续执行后续代码(fall-through)。

3.2.2 for循环与while循环的结构

for循环 适合已知循环次数的场景。

for(int i = 1; i <= 5; i++) {
    cout << i << " ";
}
// 输出:1 2 3 4 5

while循环 适合未知循环次数,但需满足条件时继续执行。

int i = 1;
while(i <= 5) {
    cout << i << " ";
    i++;
}
// 输出:1 2 3 4 5

do-while循环 while 类似,但先执行一次循环体,再判断条件。

int i = 1;
do {
    cout << i << " ";
    i++;
} while(i <= 5);
// 输出:1 2 3 4 5

3.2.3 break、continue与goto的控制流程

break :用于立即退出当前循环或 switch 语句。

for(int i = 1; i <= 10; i++) {
    if(i == 5) break;
    cout << i << " ";
}
// 输出:1 2 3 4

continue :跳过当前循环体的剩余部分,继续下一次循环。

for(int i = 1; i <= 5; i++) {
    if(i == 3) continue;
    cout << i << " ";
}
// 输出:1 2 4 5

goto :无条件跳转语句,不推荐使用,容易造成程序结构混乱。

int i = 1;
loop:
if(i <= 5) {
    cout << i << " ";
    i++;
    goto loop;
}
// 输出:1 2 3 4 5

3.3 综合案例分析

通过实际案例来巩固运算符与流程控制语句的使用,有助于加深理解并提高编程能力。

3.3.1 判断闰年的程序实现

需求: 判断某年是否为闰年。

逻辑分析:
- 若年份能被4整除但不能被100整除,则为闰年;
- 或者年份能被400整除,则也为闰年。

#include <iostream>
using namespace std;

int main() {
    int year;
    cout << "请输入年份:";
    cin >> year;

    if((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
        cout << year << " 是闰年。" << endl;
    else
        cout << year << " 不是闰年。" << endl;

    return 0;
}

流程图:

graph TD
    A[输入年份] --> B{能被4整除且不能被100整除?}
    B -->|是| C[是闰年]
    B -->|否| D{能被400整除?}
    D -->|是| C
    D -->|否| E[不是闰年]

3.3.2 计算阶乘与最大公约数

1. 计算阶乘:

long long factorial(int n) {
    long long result = 1;
    for(int i = 1; i <= n; i++)
        result *= i;
    return result;
}

int main() {
    int n;
    cout << "请输入一个整数:";
    cin >> n;
    cout << n << "! = " << factorial(n) << endl;
    return 0;
}

2. 求最大公约数(GCD):

使用欧几里得算法(辗转相除法):

int gcd(int a, int b) {
    while(b != 0) {
        int temp = b;
        b = a % b;
        a = temp;
    }
    return a;
}

int main() {
    int a, b;
    cout << "请输入两个整数:";
    cin >> a >> b;
    cout << "最大公约数是:" << gcd(a, b) << endl;
    return 0;
}

3.3.3 控制结构在游戏开发中的应用

案例:猜数字小游戏

#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;

int main() {
    srand(time(0)); // 初始化随机数种子
    int secret = rand() % 100 + 1; // 生成1~100的随机数
    int guess;

    cout << "欢迎来到猜数字游戏!" << endl;
    do {
        cout << "请输入你猜测的数字(1~100):";
        cin >> guess;
        if(guess < secret)
            cout << "太小了!" << endl;
        else if(guess > secret)
            cout << "太大了!" << endl;
        else
            cout << "恭喜你,猜对了!" << endl;
    } while(guess != secret);

    return 0;
}

逻辑流程图:

graph TD
    A[生成随机数] --> B[提示输入猜测]
    B --> C{猜测值小于秘密数?}
    C -->|是| D[提示太小]
    C -->|否| E{猜测值大于秘密数?}
    E -->|是| F[提示太大]
    E -->|否| G[恭喜猜对]
    G --> H[结束游戏]
    F --> B
    D --> B

通过本节案例,读者可以深刻理解流程控制语句在实际开发中的重要性,尤其是在交互式程序和游戏逻辑设计中的应用。

4. 函数定义与调用机制

函数是程序的基本构建单元之一。在C++中,函数不仅可以提高代码的复用性,还能增强程序的模块化结构,使程序结构更清晰、易于维护和调试。本章将深入探讨函数的定义、调用机制、作用域规则以及函数的高级特性,帮助开发者构建高效、可维护的C++程序。

4.1 函数的基本结构与定义

函数是封装特定功能的代码块。在C++中,函数由函数头和函数体组成。函数头包含返回类型、函数名、参数列表,函数体则是具体执行逻辑的代码块。

4.1.1 函数的返回值与参数列表

一个函数可以有返回值,也可以没有(使用 void 表示)。参数列表用于接收外部传入的数据,可以是多个参数,也可以没有参数。

示例代码:
int add(int a, int b) {
    return a + b;
}

代码逻辑分析:

  • int 是返回类型,表示该函数返回一个整型值。
  • add 是函数名。
  • (int a, int b) 是参数列表,两个整型参数 a b
  • return a + b; 表示函数返回两个参数相加的结果。
参数说明:
  • a b 是形式参数,调用函数时传入的实参将赋值给它们。
  • 返回值通过 return 语句返回给调用者。

4.1.2 函数的声明与实现分离

在大型项目中,通常将函数的声明(函数原型)放在头文件( .h )中,而函数的实现(函数定义)放在源文件( .cpp )中。

示例代码:

add.h

#ifndef ADD_H
#define ADD_H

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

#endif

add.cpp

#include "add.h"

int add(int a, int b) {
    return a + b;
}

main.cpp

#include <iostream>
#include "add.h"

int main() {
    std::cout << "Sum: " << add(3, 5) << std::endl;
    return 0;
}

说明:

  • 函数声明告知编译器函数的存在及其接口。
  • 函数定义实现具体逻辑。
  • 分离声明与实现有助于代码模块化和多人协作开发。

4.2 函数的调用与作用域

函数调用是程序执行流程的重要组成部分。理解函数调用的堆栈机制以及变量作用域对于编写安全、高效的代码至关重要。

4.2.1 函数调用堆栈与生命周期

当调用一个函数时,程序会将当前执行状态压入调用堆栈,并跳转到函数入口执行。函数执行完毕后,控制权返回调用点,并从堆栈中弹出该函数的执行上下文。

mermaid 流程图:
graph TD
    A[main函数调用add] --> B[将add函数压入调用栈]
    B --> C[执行add函数体]
    C --> D[计算a + b]
    D --> E[返回结果给main]
    E --> F[弹出add函数栈帧]

分析:

  • 每次函数调用都会在栈上创建一个 栈帧(Stack Frame) ,包含局部变量、参数、返回地址等。
  • 函数执行结束后,栈帧被销毁,局部变量生命周期结束。

4.2.2 全局变量与局部变量的作用域

变量根据其声明位置分为 全局变量 局部变量 ,它们的作用域不同。

示例代码:
#include <iostream>

int globalVar = 10;  // 全局变量

void func() {
    int localVar = 20;  // 局部变量
    std::cout << "Global: " << globalVar << ", Local: " << localVar << std::endl;
}

int main() {
    func();
    // std::cout << localVar;  // 编译错误:localVar不可见
    return 0;
}

输出结果:

Global: 10, Local: 20

作用域说明:

  • 全局变量 在整个程序中都可见。
  • 局部变量 仅在定义它的函数或代码块内部可见,外部无法访问。

4.3 函数的高级特性

C++为函数提供了多种高级特性,如 内联函数 默认参数 函数重载 递归调用 ,它们大大增强了函数的灵活性和表达能力。

4.3.1 内联函数与默认参数

内联函数(inline)

使用 inline 关键字可以建议编译器将函数体在调用处展开,减少函数调用的开销。

inline int square(int x) {
    return x * x;
}

使用场景:

  • 小型、频繁调用的函数适合使用内联。
默认参数(Default Arguments)

允许在函数声明中为参数指定默认值。

#include <iostream>

void printMessage(std::string msg = "Hello World") {
    std::cout << msg << std::endl;
}

int main() {
    printMessage();            // 输出默认值
    printMessage("Custom Msg");  // 输出自定义值
    return 0;
}

输出结果:

Hello World
Custom Msg

说明:

  • 默认参数必须从右向左设置,不能跳过中间参数。
  • 调用时若未提供参数,则使用默认值。

4.3.2 函数重载与递归调用

函数重载(Function Overloading)

C++允许使用相同的函数名,但参数类型或数量不同。

#include <iostream>

int add(int a, int b) {
    return a + b;
}

double add(double a, double b) {
    return a + b;
}

int main() {
    std::cout << add(2, 3) << std::endl;      // 调用int版本
    std::cout << add(2.5, 3.5) << std::endl;  // 调用double版本
    return 0;
}

输出结果:

5
6

说明:

  • 编译器根据实参类型选择最匹配的函数版本。
  • 返回类型不能作为重载依据。
递归调用(Recursion)

函数调用自身称为递归,常用于解决分治问题。

#include <iostream>

int factorial(int n) {
    if (n == 0) return 1;  // 终止条件
    return n * factorial(n - 1);  // 递归调用
}

int main() {
    std::cout << "Factorial of 5: " << factorial(5) << std::endl;
    return 0;
}

输出结果:

Factorial of 5: 120

说明:

  • 递归必须有终止条件,否则会导致栈溢出。
  • 递归调用栈逐层展开直到终止条件满足。

4.4 函数在模块化编程中的应用

模块化编程是将程序划分为多个功能独立的模块,每个模块由若干函数组成。这种设计方式有助于代码复用、维护和协作开发。

4.4.1 使用函数实现模块化设计

示例:计算器模块

calculator.h

#ifndef CALCULATOR_H
#define CALCULATOR_H

int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
int divide(int a, int b);

#endif

calculator.cpp

#include "calculator.h"

int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
int divide(int a, int b) { return a / b; }

main.cpp

#include <iostream>
#include "calculator.h"

int main() {
    std::cout << "Add: " << add(10, 5) << std::endl;
    std::cout << "Subtract: " << subtract(10, 5) << std::endl;
    std::cout << "Multiply: " << multiply(10, 5) << std::endl;
    std::cout << "Divide: " << divide(10, 5) << std::endl;
    return 0;
}

输出结果:

Add: 15
Subtract: 5
Multiply: 50
Divide: 2

优势:

  • 模块化设计使代码结构清晰。
  • 每个模块可独立测试和维护。
  • 便于多人协作开发。

4.4.2 设计可复用的函数库

将常用功能封装成函数库,可以在多个项目中复用。

示例:日志记录函数库

logger.h

#ifndef LOGGER_H
#define LOGGER_H

void logInfo(const char* message);
void logError(const char* message);

#endif

logger.cpp

#include <iostream>
#include "logger.h"

void logInfo(const char* message) {
    std::cout << "[INFO] " << message << std::endl;
}

void logError(const char* message) {
    std::cerr << "[ERROR] " << message << std::endl;
}

main.cpp

#include "logger.h"

int main() {
    logInfo("Application started.");
    logError("Something went wrong!");
    return 0;
}

输出结果:

[INFO] Application started.
[ERROR] Something went wrong!

说明:

  • 日志函数库可以在多个项目中复用。
  • 统一的日志格式便于调试和日志分析。

总结延伸:
本章从函数的定义与调用机制出发,逐步讲解了函数的基本结构、作用域规则、高级特性以及模块化编程中的应用。通过示例代码与流程图的结合,读者不仅掌握了函数的语法和使用技巧,还理解了如何将函数作为构建模块化程序的重要工具。后续章节将进一步深入函数指针、Lambda表达式等高级函数技术,帮助开发者构建更灵活、高效的C++程序。

5. 指针操作与动态内存管理

在C++编程中,指针(Pointer)是最核心、最灵活、也是最容易出错的机制之一。指针的使用贯穿整个系统编程、内存管理、性能优化等多个层面。本章将深入讲解指针的基本操作、高级用法、动态内存分配与释放策略,以及其在实际开发中的典型应用场景。通过本章的学习,读者将掌握如何高效、安全地使用指针,并具备在项目中进行动态内存管理的能力。

5.1 指针的基本概念与使用

指针是C++中用于直接操作内存地址的一种机制。通过指针,我们可以访问和修改变量的值、处理数组、字符串、结构体等复合数据结构,甚至实现高效的函数参数传递和动态内存管理。

5.1.1 指针变量的声明与初始化

在C++中,指针变量的声明格式如下:

数据类型 *指针变量名;

例如:

int *p;  // 声明一个指向int类型的指针变量p

指针变量可以被初始化为一个变量的地址,也可以被赋值为 nullptr (空指针),表示它不指向任何内存地址。

int a = 10;
int *p = &a; // 初始化指针p为变量a的地址
代码分析:
  • int *p :定义一个指向整型数据的指针变量。
  • &a :取变量 a 的内存地址。
  • p = &a :将指针 p 指向变量 a 的内存地址。
参数说明:
参数 含义
* 指针操作符,表示该变量为指针类型
& 地址操作符,用于获取变量的内存地址
nullptr C++11引入的关键字,表示空指针
指针访问内存示意图(Mermaid流程图):
graph TD
    A[变量a] -->|取地址| B(指针p)
    B -->|指向| C[内存地址]
    C -->|存储值| D[10]

5.1.2 指针与数组、字符串的关系

指针与数组在C++中有着密切的联系。数组名本质上是一个指向数组第一个元素的指针。

示例代码:
int arr[] = {1, 2, 3, 4, 5};
int *p = arr; // 指针p指向数组arr的第一个元素

for (int i = 0; i < 5; ++i) {
    std::cout << *(p + i) << " "; // 通过指针访问数组元素
}
代码分析:
  • arr 是数组名,表示数组首地址。
  • p = arr :指针 p 指向数组第一个元素。
  • *(p + i) :通过指针偏移访问数组元素,等价于 arr[i]
指针与字符串的使用:

C++中的字符串常量存储在只读内存中,可以通过字符指针进行访问:

char *str = "Hello World";
std::cout << str; // 输出字符串

注意: str 是一个指向常量字符串的指针,不能通过 str 修改字符串内容。

指针与数组访问流程图:
graph LR
    A[数组arr] -->|起始地址| B(指针p)
    B -->|p+0| C[元素1]
    B -->|p+1| D[元素2]
    B -->|p+2| E[元素3]
    B -->|p+3| F[元素4]
    B -->|p+4| G[元素5]

5.2 指针的高级操作

在掌握基本指针操作之后,我们可以进一步学习指针的高级用法,如指针的算术运算、与函数的结合、以及与结构体的结合使用。

5.2.1 指针的算术运算与比较

指针支持加减整数、比较等运算,常用于遍历数组或动态内存操作。

示例代码:
int arr[] = {10, 20, 30, 40, 50};
int *p = arr;

// 指针算术运算
p += 2; // 移动到第三个元素
std::cout << *p << std::endl; // 输出30

// 指针比较
int *q = &arr[4];
if (p < q) {
    std::cout << "p 指向的地址在 q 之前" << std::endl;
}
代码分析:
  • p += 2 :指针移动两个 int 类型的大小(通常是8字节)。
  • p < q :比较两个指针的地址,判断其逻辑顺序。
注意事项:
  • 指针运算只在指向同一数组或动态内存时有效。
  • 不同数组之间的指针比较无意义。
指针运算逻辑图:
graph LR
    A[p = arr] -->|p+0| B[arr[0]]
    A -->|p+1| C[arr[1]]
    A -->|p+2| D[arr[2]]
    A -->|p+3| E[arr[3]]
    A -->|p+4| F[arr[4]]

5.2.2 指针与函数参数传递

在函数调用中,使用指针可以实现对变量的间接修改,避免数据拷贝,提高性能。

示例代码:
void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int x = 10, y = 20;
    swap(&x, &y); // 通过指针交换变量值
    std::cout << "x = " << x << ", y = " << y << std::endl;
}
代码分析:
  • 函数 swap 接收两个 int* 类型参数,表示传入的是变量的地址。
  • *a *b :解引用操作,访问指针指向的变量值。
  • temp :临时变量用于交换值。
参数说明:
参数 含义
int *a 指向整型变量的指针
&x 取变量 x 的地址作为参数传入
函数调用流程图:
graph LR
    A[main函数] -->|调用swap| B(swap函数)
    B -->|接收指针a和b| C[解引用修改值]
    C -->|返回结果| D[main中x和y交换]

5.2.3 指针与结构体的结合使用

结构体(struct)是C++中用于组织数据的一种方式。通过指针访问结构体成员可以提高程序的灵活性和效率。

示例代码:
struct Student {
    char name[20];
    int age;
};

int main() {
    Student s;
    Student *p = &s;

    strcpy(p->name, "Alice"); // 使用->访问结构体成员
    p->age = 20;

    std::cout << "Name: " << p->name << ", Age: " << p->age << std::endl;
}
代码分析:
  • p->name 等价于 (*p).name ,用于通过指针访问结构体成员。
  • strcpy 是C标准库函数,用于复制字符串。
指针与结构体访问逻辑图:
graph LR
    A[结构体Student] -->|p指向| B(内存地址)
    B -->|p->name| C[姓名字段]
    B -->|p->age| D[年龄字段]
参数说明:
参数 含义
-> 结构体指针访问成员的操作符
strcpy 字符串复制函数,需包含 <cstring> 头文件

5.3 动态内存管理

动态内存管理是C++中非常关键的一环,尤其在处理不确定大小的数据、资源管理、数据结构实现等方面尤为重要。

5.3.1 new与delete操作符的使用

C++使用 new delete 操作符进行动态内存的分配与释放。

示例代码:
int *p = new int; // 动态分配一个int大小的内存
*p = 42;

std::cout << *p << std::endl;

delete p; // 释放内存
代码分析:
  • new int :在堆上分配一个整型内存空间。
  • *p = 42 :将值写入分配的内存。
  • delete p :释放内存,防止内存泄漏。
动态数组的分配与释放:
int *arr = new int[5]; // 动态分配一个长度为5的数组
for (int i = 0; i < 5; ++i) {
    arr[i] = i * 10;
}

// 使用完毕后释放
delete[] arr;
参数说明:
参数 含义
new T 分配一个类型为T的内存空间
new T[N] 分配N个T类型的连续内存空间
delete ptr 释放单个对象的内存
delete[] ptr 释放数组内存

5.3.2 内存泄漏的检测与防范

内存泄漏(Memory Leak)是指程序在堆上分配了内存但未及时释放,导致内存资源浪费。

示例代码(潜在内存泄漏):
void leakMemory() {
    int *p = new int[100];
    // 忘记 delete p;
}
防范策略:
  1. 严格配对 new / delete :确保每次分配都有对应的释放。
  2. 使用智能指针(C++11起)
    • std::unique_ptr :独占所有权的智能指针。
    • std::shared_ptr :共享所有权的智能指针。
使用智能指针示例:
#include <memory>

void safeMemory() {
    std::unique_ptr<int[]> p(new int[100]);
    // 使用p...
} // 离开作用域后自动释放内存
内存泄漏检测工具:
  • Valgrind(Linux) :可检测内存泄漏、越界访问等问题。
  • Visual Leak Detector(Windows) :用于Visual Studio项目中检测内存泄漏。
内存管理流程图:
graph LR
    A[分配内存: new] --> B[使用内存]
    B --> C[释放内存: delete]
    C --> D[内存回收]
    E[忘记释放] -->|内存泄漏| F[程序崩溃或性能下降]

5.4 指针在实际开发中的典型应用

指针在现代C++开发中虽然被智能指针逐步替代,但在某些底层开发场景中仍然不可替代。

5.4.1 实现动态数组与链表结构

指针是实现动态数据结构(如链表、栈、队列、树等)的基础。

动态链表节点结构:
struct Node {
    int data;
    Node *next;
};

int main() {
    Node *head = new Node;
    head->data = 10;
    head->next = nullptr;

    Node *second = new Node;
    second->data = 20;
    second->next = nullptr;
    head->next = second;

    // 遍历链表
    Node *current = head;
    while (current != nullptr) {
        std::cout << current->data << " ";
        current = current->next;
    }

    // 释放内存
    while (head != nullptr) {
        Node *temp = head;
        head = head->next;
        delete temp;
    }
}
代码分析:
  • Node *next :指向下一个节点的指针。
  • new Node :动态分配节点内存。
  • while (current != nullptr) :通过指针逐个访问链表节点。
链表结构图:
graph LR
    A[head] -->|data=10| B[next]
    B -->|指向| C[second]
    C -->|data=20| D[next=null]

5.4.2 指针在资源管理中的实践

在系统级编程、图形处理、文件操作、网络通信等场景中,指针广泛用于资源句柄的管理。

示例:使用指针管理文件句柄
#include <cstdio>

void readFile(const char *filename) {
    FILE *fp = fopen(filename, "r");
    if (fp == nullptr) {
        std::cerr << "无法打开文件" << std::endl;
        return;
    }

    char buffer[1024];
    while (fgets(buffer, sizeof(buffer), fp)) {
        std::cout << buffer;
    }

    fclose(fp); // 必须手动关闭文件
}
代码分析:
  • FILE *fp :文件指针,指向打开的文件流。
  • fopen :打开文件并返回文件指针。
  • fclose :关闭文件,防止资源泄漏。
指针在资源管理中的重要性:
资源类型 指针应用示例
文件 FILE*
网络连接 SOCKET* (Windows)或 int (Linux)
图形资源 HWND* GLuint* (OpenGL)
内存 void* T*
资源管理流程图:
graph LR
    A[申请资源] --> B[使用资源]
    B --> C[释放资源]
    D[忘记释放] -->|资源泄漏| E[系统崩溃或卡顿]

本章通过深入剖析指针的声明、使用、算术运算、函数参数传递、结构体操作,以及动态内存管理机制,帮助读者全面掌握C++中指针的核心用法。同时,结合实际开发案例,展示了指针在数据结构和资源管理中的关键作用。在后续章节中,我们将进一步学习类与对象的机制,构建面向对象的编程思维。

6. 类与对象设计与实现

6.1 面向对象编程的基本概念

C++作为一门面向对象的编程语言,其核心思想是“万物皆对象”,通过类(Class)和对象(Object)来组织程序结构。面向对象编程(OOP)有三个核心特征: 封装性 (Encapsulation)、 继承性 (Inheritance)和 多态性 (Polymorphism)。

类与对象的关系

类是对象的模板或蓝图,用于描述某一类对象的属性和行为。对象是类的实例化结果,具有具体的属性值和行为。例如,类 Car 描述了汽车的通用属性(如品牌、颜色、速度)和行为(如启动、加速、刹车),而 myCar Car 类的一个具体实例。

class Car {
public:
    string brand;
    string color;
    int speed;

    void start() {
        cout << brand << " car is starting." << endl;
    }

    void accelerate(int increment) {
        speed += increment;
        cout << "Speed increased to " << speed << " km/h." << endl;
    }
};

int main() {
    Car myCar;  // 创建对象
    myCar.brand = "Toyota";
    myCar.color = "Red";
    myCar.speed = 0;

    myCar.start();         // 输出: Toyota car is starting.
    myCar.accelerate(30);  // 输出: Speed increased to 30 km/h.

    return 0;
}

代码说明
- class Car 定义了一个类,包含成员变量(brand、color、speed)和成员函数(start、accelerate)。
- Car myCar; 创建了类的一个实例对象 myCar
- 通过对象访问成员变量和函数,实现了对象的行为调用。

6.2 类的定义与成员访问

类的定义由关键字 class struct 开始,后跟类名和一对花括号 {} 包含成员变量和成员函数。在类中,成员的访问权限可以通过访问修饰符来控制:

  • public :成员对外部可见,可被类外直接访问。
  • private :成员仅类内部可访问,外部不可见。
  • protected :成员对类内部和派生类可见,但外部不可访问。

示例:使用访问修饰符定义类

class Person {
private:
    string name;
    int age;

public:
    void setName(string n) {
        name = n;
    }

    void setAge(int a) {
        if (a > 0) age = a;
    }

    void displayInfo() {
        cout << "Name: " << name << ", Age: " << age << endl;
    }
};

int main() {
    Person p;
    // p.name = "Alice";  // 编译错误:name是private成员,不可直接访问
    p.setName("Alice");
    p.setAge(25);
    p.displayInfo();  // 输出: Name: Alice, Age: 25
    return 0;
}

参数说明
- setName() setAge() 是公开方法,用于安全地设置私有成员变量。
- displayInfo() 用于输出成员变量的值。
- 通过封装机制,确保了数据的安全性和完整性。

6.3 类的实例化与对象操作

类定义完成后,可以通过类名来创建对象,称为实例化。对象的生命周期包括创建、使用和销毁三个阶段。

对象的创建与销毁

class MyClass {
public:
    MyClass() {
        cout << "Constructor called." << endl;
    }

    ~MyClass() {
        cout << "Destructor called." << endl;
    }
};

int main() {
    MyClass obj;  // 创建对象,调用构造函数
    // obj 生命周期在此作用域内
}  // 离开作用域时自动调用析构函数

执行流程
1. 程序进入 main() 函数,创建 MyClass 类的对象 obj
2. 调用构造函数 MyClass() ,输出“Constructor called.”。
3. 程序执行完 main() 函数后, obj 生命周期结束,调用析构函数 ~MyClass() ,输出“Destructor called.”。

对象数组与对象指针的使用

class Rectangle {
private:
    int width, height;

public:
    Rectangle(int w, int h) : width(w), height(h) {}

    int area() {
        return width * height;
    }
};

int main() {
    // 对象数组
    Rectangle rects[3] = { Rectangle(2, 3), Rectangle(4, 5), Rectangle(6, 7) };

    for (int i = 0; i < 3; i++) {
        cout << "Area of rect[" << i << "] = " << rects[i].area() << endl;
    }

    // 对象指针
    Rectangle* rectPtr = new Rectangle(8, 9);
    cout << "Area via pointer: " << rectPtr->area() << endl;
    delete rectPtr;  // 释放内存

    return 0;
}

执行说明
- rects[3] 是一个对象数组,每个元素都是 Rectangle 类的实例。
- rectPtr 是指向堆内存中创建的对象的指针,使用 new 动态分配内存。
- 使用 -> 运算符访问指针所指向对象的成员函数。
- 最后使用 delete 释放动态分配的内存,避免内存泄漏。

6.4 类与对象在项目开发中的应用

6.4.1 使用类封装数据与行为

类的核心作用是将数据和操作封装在一起,形成一个完整的逻辑单元。这种封装机制可以提高代码的可维护性和复用性。

例如,我们可以定义一个 BankAccount 类,用于封装账户信息和操作:

class BankAccount {
private:
    string accountNumber;
    double balance;

public:
    BankAccount(string accNum, double initialBalance) 
        : accountNumber(accNum), balance(initialBalance) {}

    void deposit(double amount) {
        if (amount > 0) balance += amount;
    }

    bool withdraw(double amount) {
        if (amount > 0 && balance >= amount) {
            balance -= amount;
            return true;
        }
        return false;
    }

    double getBalance() const {
        return balance;
    }
};

功能说明
- deposit() withdraw() 封装了存款和取款的业务逻辑。
- getBalance() 提供了读取账户余额的方法。
- 所有数据成员为私有,防止外部直接修改账户数据。

6.4.2 实现简单的学生管理系统

我们可以通过类设计一个简单的学生信息管理系统,实现学生信息的添加、查询和显示。

#include <iostream>
#include <vector>
#include <string>
using namespace std;

class Student {
private:
    string name;
    int age;
    string id;

public:
    Student(string n, int a, string i) : name(n), age(a), id(i) {}

    void display() const {
        cout << "ID: " << id << ", Name: " << name << ", Age: " << age << endl;
    }

    string getId() const { return id; }
};

class StudentManager {
private:
    vector<Student> students;

public:
    void addStudent(const Student& s) {
        students.push_back(s);
    }

    void findStudent(string id) const {
        for (const auto& s : students) {
            if (s.getId() == id) {
                s.display();
                return;
            }
        }
        cout << "Student not found." << endl;
    }

    void showAll() const {
        for (const auto& s : students) {
            s.display();
        }
    }
};

使用示例

int main() {
    StudentManager manager;

    manager.addStudent(Student("Alice", 20, "S001"));
    manager.addStudent(Student("Bob", 22, "S002"));

    cout << "All Students:" << endl;
    manager.showAll();

    cout << "\nSearch Student S001:" << endl;
    manager.findStudent("S001");

    return 0;
}

执行结果

All Students:
ID: S001, Name: Alice, Age: 20
ID: S002, Name: Bob, Age: 22

Search Student S001:
ID: S001, Name: Alice, Age: 20

逻辑分析
- Student 类封装了学生的基本信息,并提供显示和查询方法。
- StudentManager 类管理多个学生对象,提供添加、查询和显示功能。
- 通过面向对象的设计,实现了良好的模块划分和功能封装,便于后续扩展和维护。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:《C++编程语言第三版》是Y. Daniel Liang编著的经典教材,系统讲解了C++的基础语法与面向对象编程核心概念,适合初学者与进阶开发者学习。本书配套源代码集合了书中所有示例程序,便于读者快速理解与实践C++编程。通过这些源码的学习与修改,读者可以深入掌握变量、函数、指针、类、继承、模板、STL等关键知识点,提升实际编程能力,为开发高性能C++应用程序打下坚实基础。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

更多推荐