文章

程序设计范式-Week2

程序设计范式-第2周小结

程序设计范式-Week2

C++ 的面向对象 I/O

C 语言使用 printf 函数进行输出,它通过格式字符串指定输出格式.

问题与风险

1. 类型不安全

2. 编译器难以检查:

  • 格式字符串是普通字符串,编译器很难验证参数类型是否匹配
  • 错误通常在运行时才暴露,难以调试

3. 参数数量不匹配

C++ 的 IO 流:类型安全且可扩展

核心机制

1. 通过引用传递 (By References)

C++ I/O 使用引用而不是指针或值传递,这使得语法更简洁且更安全。

2. 函数重载 (Function Overloading)

C++ 为不同的数据类型重载了 <<>> 运算符,使它们能够处理各种内置类型。

3. 运算符重载 (Operator Overloading)

C++ 允许重载运算符,使自定义类型能够像内置类型一样使用 I/O 操作。

类型安全 (Type Safe)

C++ I/O 系统是类型安全的,这意味着编译器会在编译时检查类型匹配,防止运行时错误。

I/O 对数据类型的敏感性

C++ I/O 能够识别和处理不同的数据类型,为每种类型提供适当的格式化。

类型不匹配时的错误处理

当输入与期望类型不匹配时,C++ I/O 流会设置错误标志,程序可以检测并处理这些错误。

C++ 的可扩展性

C++ I/O 系统的最强大之处在于它的可扩展性。您可以为自己创建的任何类型重载 I/O 运算符。

综合示例:完整的自定义类型 I/O

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
#include <string>
using namespace std;

class Student {
private:
    string name;
    int id;
    double gpa;
public:
    Student(string n = "", int i = 0, double g = 0.0) 
        : name(n), id(i), gpa(g) {}
    
    // 重载输出运算符
    friend ostream& operator<<(ostream& os, const Student& s) {
        os << "Student: " << s.name << " (ID: " << s.id 
           << ", GPA: " << s.gpa << ")";
        return os;
    }
    
    // 重载输入运算符
    friend istream& operator>>(istream& is, Student& s) {
        cout << "Enter student name: ";
        getline(is, s.name);
        cout << "Enter student ID: ";
        is >> s.id;
        cout << "Enter student GPA: ";
        is >> s.gpa;
        is.ignore(); // 忽略换行符
        return is;
    }
};

int main() {
    Student s1("Alice", 12345, 3.8);
    cout << s1 << endl;
    
    Student s2;
    cin >> s2;
    cout << s2 << endl;
    
    return 0;
}

数据类型

基本数据类型

C++ 提供了丰富的基本数据类型,它们可以分为几大类:

1. 字符类型 (char)

1
2
3
4
5
6
char letter = 'A';      // 单个字符
char number_char = '7'; // 字符 '7',不是数字 7
char newline = '\n';    // 转义字符表示换行符

// char 也可以表示小整数(通常 -128 到 127)
char small_number = 65; // 与 'A' 相同(ASCII 值)

2. 整数类型

1
2
3
4
5
6
7
8
9
short small_number = 100;          // 通常 2 字节,-32,768 到 32,767
int normal_number = 100000;        // 通常 4 字节,-2^31 到 2^31-1
long large_number = 1000000000L;   // 通常 4 或 8 字节
long long very_large = 1000000000000LL; // 通常 8 字节

// 无符号版本
unsigned short us = 50000;
unsigned int ui = 4000000000U;
unsigned long ul = 4000000000UL;

3. 浮点类型

1
2
3
float f = 3.14159F;           // 单精度,通常 4 字节
double d = 3.14159265358979;  // 双精度,通常 8 字节
long double ld = 3.141592653589793238L; // 扩展精度,通常 10 或 16 字节

4. 布尔类型 (bool)

1
2
3
4
5
bool is_cpp_great = true;
bool is_c_better = false;

// 布尔值可以转换为整数(true=1, false=0)
int truth_value = is_cpp_great; // 值为 1

字面常量

字面常量是程序中直接书写的固定值:

1. 整数字面量

1
2
3
4
5
6
7
8
9
int decimal = 42;        // 十进制
int octal = 052;         // 八进制(前缀0),等于十进制的42
int hexadecimal = 0x2A;  // 十六进制(前缀0x),等于十进制的42

// 带后缀的字面量
unsigned int ui = 42U;   // 无符号整型
long l = 42L;            // 长整型
unsigned long ul = 42UL; // 无符号长整型
long long ll = 42LL;     // 长长整型

2. 浮点字面量

1
2
3
4
5
6
float f = 3.14F;         // 单精度浮点数
double d = 3.14;         // 双精度浮点数
long double ld = 3.14L;  // 扩展精度浮点数

// 科学计数法
double sci = 6.022e23;   // 6.022 × 10^23

3. 字符和字符串字面量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
char c = 'A';            // 字符字面量
const char* str = "Hello, World!"; // C风格字符串字面量

// 转义序列
char newline = '\n';     // 换行符
char tab = '\t';         // 制表符
char quote = '\'';       // 单引号
char backslash = '\\';   // 反斜杠
char null_char = '\0';   // 空字符

// 原始字符串字面量 (C++11)
const char* path = R"(C:\Program Files\MyApp)"; // 不需要转义反斜杠
const char* multi_line = R"(
Line 1
Line 2
Line 3
)"; // 多行字符串

C++ 字符串类型

C++ 提供了 std::string 类来处理字符串,比 C 风格的字符数组更安全、更方便:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <string>
using namespace std;

int main() {
    // 初始化字符串的不同方式
    string s1("Hello, World!");     // 从C风格字符串初始化
    string s2(s1);                  // 从另一个string初始化
    string s3(10, 'x');             // 包含10个'x'字符的字符串
    string s4;                      // 空字符串
    
    // 获取字符串长度
    cout << "Length of s1: " << s1.size() << endl;   // 13
    cout << "Length of s4: " << s4.size() << endl;   // 0
    
    // 检查字符串是否为空
    if (s4.empty()) {
        cout << "s4 is empty" << endl;
    }
    
    // 字符串比较
    string a = "apple";
    string b = "banana";
    
    if (a == b) {
        cout << "Strings are equal" << endl;
    } else {
        cout << "Strings are different" << endl;
    }
    
    // 字符串赋值
    s4 = s1; // 将s1的内容复制给s4
    
    // 字符串连接
    string greeting = "Hello";
    string name = "Alice";
    string message = greeting + ", " + name + "!"; // "Hello, Alice!"
    
    cout << message << endl;
    
    return 0;
}

结构化数据类型

1. 枚举类型 (enum)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 定义枚举类型
enum HealthType {Poor, Fair, Good, Excellent};

// 使用枚举
HealthType patient_health = Good;

// 枚举值可以转换为整数
int health_value = patient_health; // 值为2(从0开始)

// 有作用域的枚举 (C++11)
enum class Color {Red, Green, Blue};
Color favorite = Color::Blue;

// 需要显式转换才能获取整数值
// int color_value = static_cast<int>(favorite); // 值为2

2. 结构体类型 (struct)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 定义结构体
struct AnimalType {
    long id;
    string name;
    string genus;
    string species;
    string country;
    int age;
    float weight;
    HealthType health;
};

// 使用结构体
AnimalType thisAnimal;
thisAnimal.id = 12345;
thisAnimal.name = "Leo";
thisAnimal.genus = "Panthera";
thisAnimal.species = "leo";
thisAnimal.country = "South Africa";
thisAnimal.age = 5;
thisAnimal.weight = 190.5f;
thisAnimal.health = Good;

// 初始化结构体 (C++11)
AnimalType anotherAnimal = {
    67890,           // id
    "Tiger",         // name
    "Panthera",      // genus
    "tigris",        // species
    "India",         // country
    7,               // age
    220.0f,          // weight
    Excellent        // health
};

// 访问结构体成员
cout << thisAnimal.name << " is a " << thisAnimal.species 
     << " from " << thisAnimal.country << endl;

抽象的概念

抽象是计算机科学中的核心概念,指的是隐藏实现细节,只暴露必要的接口。在C++中,抽象通过多种机制实现:

1. 函数抽象

1
2
3
4
5
6
7
8
// 计算圆面积的函数 - 用户不需要知道计算细节
double calculateCircleArea(double radius) {
    return 3.14159 * radius * radius;
}

// 使用函数
double area = calculateCircleArea(5.0);
// 用户不需要知道面积是如何计算的,只需要提供半径

2. 类抽象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <iostream>
#include <string>
using namespace std;

// 银行账户类 - 隐藏内部实现细节
class BankAccount {
private:
    string accountNumber;
    double balance;
    
    // 私有方法 - 内部实现细节
    bool isValidAmount(double amount) const {
        return amount > 0;
    }
    
public:
    // 构造函数
    BankAccount(const string& accNum, double initialBalance = 0.0)
        : accountNumber(accNum), balance(initialBalance) {}
    
    // 公共接口 - 用户只能通过这些方法访问账户
    void deposit(double amount) {
        if (isValidAmount(amount)) {
            balance += amount;
            cout << "Deposited: $" << amount << endl;
        } else {
            cout << "Invalid deposit amount" << endl;
        }
    }
    
    bool withdraw(double amount) {
        if (isValidAmount(amount) && amount <= balance) {
            balance -= amount;
            cout << "Withdrew: $" << amount << endl;
            return true;
        } else {
            cout << "Invalid withdrawal amount or insufficient funds" << endl;
            return false;
        }
    }
    
    double getBalance() const {
        return balance;
    }
    
    string getAccountNumber() const {
        return accountNumber;
    }
};

int main() {
    // 使用银行账户类
    BankAccount myAccount("123456789", 1000.0);
    
    myAccount.deposit(500.0);
    myAccount.withdraw(200.0);
    
    cout << "Account " << myAccount.getAccountNumber() 
         << " has balance: $" << myAccount.getBalance() << endl;
    
    // 无法直接访问私有成员
    // myAccount.balance = 1000000; // 编译错误
    
    return 0;
}

类型转换

C++ 提供了多种类型转换机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 隐式转换
int i = 3.14;      // 3 - 小数部分被截断
double d = i;      // 3.0 - 整数转换为浮点数

// 显式转换(C风格)
double pi = 3.14159;
int approx_pi = (int)pi; // 3

// C++风格的类型转换(更安全)
int another_approx = static_cast<int>(pi); // 3

// 常量转换
const int ci = 10;
int* modifiable = const_cast<int*>(&ci); // 移除const限定符

// 重新解释转换(危险,但有时必要)
int number = 0x12345678;
char* bytes = reinterpret_cast<char*>(&number); // 将int解释为字节数组

抽象数据类型 (ADT) 与 C++ 类型定义

抽象数据类型 (ADT) 是计算机科学中的一个核心概念,它允许程序员创建具有特定行为和属性的自定义类型。下面是关于 ADT 和 C++ 中定义新类型的详细解释。

抽象数据类型 (ADT) 的概念

抽象数据类型 (ADT) 是一种程序员定义的类型,它具有:

  1. 一组特定的值(定义域)

  2. 一组允许在这些值上执行的操作

ADT 的关键特点是它将数据表示操作实现分离,只向用户暴露接口,隐藏内部实现细节。

在 C++ 中定义新类型的方法

1. 使用 typedef

typedef 关键字为现有类型创建别名:

1
2
3
4
5
6
7
8
9
typedef char String20[21];  // 创建名为 String20 的类型,它是 21 个字符的数组
String20 message;           // 声明一个 String20 类型的变量

// 使用示例
#include <cstring>
int main() {
    strcpy(message, "Hello, World!");  // 复制字符串到 message
    return 0;
}

2. 使用 struct

struct 允许将多个相关数据项组合成一个单一类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct EmployeeType {
    long idNumber;
    String20 name;  // 使用之前定义的 String20 类型
};

EmployeeType myself;  // 声明 EmployeeType 变量

// 使用示例
int main() {
    myself.idNumber = 12345;
    strcpy(myself.name, "John Doe");
    
    std::cout << "Employee ID: " << myself.idNumber << std::endl;
    std::cout << "Name: " << myself.name << std::endl;
    
    return 0;
}

3. 使用 class(实现 ADT)

class 是 C++ 中实现 ADT 的主要方式,它允许将数据和操作封装在一起:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include <iostream>
#include <iomanip>

class TimeType {
private:
    int hours;
    int minutes;
    int seconds;

public:
    // 构造函数
    TimeType() : hours(0), minutes(0), seconds(0) {}
    TimeType(int h, int m, int s) : hours(h), minutes(m), seconds(s) {
        normalize(); // 确保时间是有效的
    }

    // 设置时间
    void setTime(int h, int m, int s) {
        hours = h;
        minutes = m;
        seconds = s;
        normalize();
    }

    // 打印时间
    void printTime() const {
        std::cout << std::setfill('0') << std::setw(2) << hours << ":"
                  << std::setfill('0') << std::setw(2) << minutes << ":"
                  << std::setfill('0') << std::setw(2) << seconds << std::endl;
    }

    // 增加一秒
    void incrementByOneSecond() {
        seconds++;
        normalize();
    }

    // 比较两个时间是否相等
    bool equals(const TimeType& other) const {
        return (hours == other.hours) && 
               (minutes == other.minutes) && 
               (seconds == other.seconds);
    }

private:
    // 规范化时间(确保秒和分钟在0-59之间,小时在0-23之间)
    void normalize() {
        minutes += seconds / 60;
        seconds %= 60;
        if (seconds < 0) {
            seconds += 60;
            minutes--;
        }

        hours += minutes / 60;
        minutes %= 60;
        if (minutes < 0) {
            minutes += 60;
            hours--;
        }

        hours %= 24;
        if (hours < 0) {
            hours += 24;
        }
    }
};

// 使用示例
int main() {
    TimeType t1(23, 59, 59);
    TimeType t2;
    
    std::cout << "Initial time: ";
    t1.printTime();
    
    t1.incrementByOneSecond();
    std::cout << "After increment: ";
    t1.printTime(); // 应该显示 00:00:00
    
    t2.setTime(0, 0, 0);
    std::cout << "t2: ";
    t2.printTime();
    
    if (t1.equals(t2)) {
        std::cout << "t1 and t2 are equal" << std::endl;
    } else {
        std::cout << "t1 and t2 are not equal" << std::endl;
    }
    
    return 0;
}

TimeType ADT 的完整规范

更详细地定义 TimeType ADT:

TYPE

TimeType - 表示时间的数据类型,包含小时、分钟和秒。

DOMAIN

每个 TimeType 值是一个时间,由以下部分组成:

  • 小时 (hours): 0 到 23 之间的整数

  • 分钟 (minutes): 0 到 59 之间的整数

  • 秒 (seconds): 0 到 59 之间的整数

OPERATIONS

  1. 设置时间 (setTime)

    • 输入: 小时、分钟、秒

    • 前置条件: 输入值在有效范围内

    • 后置条件: 时间被设置为指定值

    • 异常: 如果输入值无效,抛出异常或进行调整

  2. 打印时间 (printTime)

    • 输入: 无

    • 输出: 以 HH:MM:SS 格式打印时间

    • 前置条件: 时间已正确初始化

    • 后置条件: 无

  3. 增加一秒 (incrementByOneSecond)

    • 输入: 无

    • 前置条件: 时间已正确初始化

    • 后置条件: 时间增加一秒,必要时处理进位(59秒→0秒,分钟+1)

  4. 比较相等 (equals)

    • 输入: 另一个 TimeType 对象

    • 输出: 布尔值,表示两个时间是否相等

    • 前置条件: 两个时间都已正确初始化

    • 后置条件: 无

C++ 引用传递

使用引用传递的三个主要原因

1. 修改传入函数的对象

引用允许函数直接修改调用者传递的变量,而不是操作副本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using namespace std;

// 使用引用修改传入的值
void increment(int &num) {
    num++; // 直接修改原变量
}

int main() {
    int x = 5;
    cout << "Before increment: " << x << endl; // 输出 5
    increment(x);
    cout << "After increment: " << x << endl;  // 输出 6
    return 0;
}

2. 避免复制大对象的开销

对于大型对象(如结构体、类实例),使用引用可以避免复制整个对象的开销。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <string>
using namespace std;

struct LargeData {
    int array[1000];
    string text[100];
};

// 使用值传递 - 会产生复制开销
void processByValue(LargeData data) {
    // 处理数据
}

// 使用引用传递 - 避免复制开销
void processByReference(const LargeData &data) {
    // 处理数据,const确保不会意外修改
}

int main() {
    LargeData bigData;
    
    // 值传递 - 复制整个LargeData对象
    processByValue(bigData);
    
    // 引用传递 - 只传递引用,没有复制开销
    processByReference(bigData);
    
    return 0;
}

3. 实现多个返回值

通过引用参数,函数可以返回多个值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <cmath>
using namespace std;

// 通过引用参数返回多个值
void calculateCircle(double radius, double &area, double &circumference) {
    area = M_PI * radius * radius;
    circumference = 2 * M_PI * radius;
}

int main() {
    double r = 5.0;
    double a, c;
    
    calculateCircle(r, a, c);
    
    cout << "Radius: " << r << endl;
    cout << "Area: " << a << endl;
    cout << "Circumference: " << c << endl;
    
    return 0;
}

引用与指针的区别

虽然引用和指针在底层机制上可能相似,但它们在用法上有重要区别:

引用特性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main() {
    int x = 10;
    
    // 引用必须在声明时初始化
    int &ref = x; // 正确:引用已初始化
    // int &ref2; // 错误:引用必须初始化
    
    // 引用不能重新绑定到其他变量
    int y = 20;
    // &ref = y; // 错误:不能重新绑定引用
    
    // 引用是原变量的别名
    ref = 15; // 等价于 x = 15
    cout << "x = " << x << endl; // 输出 15
    
    return 0;
}

指针特性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int main() {
    int x = 10;
    int y = 20;
    
    // 指针可以不初始化(但不推荐)
    int *ptr;
    
    // 指针可以重新指向其他变量
    ptr = &x; // 指向x
    *ptr = 15; // x = 15
    
    ptr = &y; // 现在指向y
    *ptr = 25; // y = 25
    
    // 指针可以为nullptr
    ptr = nullptr;
    
    return 0;
}

选择使用引用还是指针

  • 使用引用

    • 你确定它总是代表一个非空对象

    • 它不会改变代表其他对象

  • 使用指针

    • 可能需要指向空值(nullptr)

    • 需要能够重新指向不同的对象

引用参数在函数中的使用

基本语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using namespace std;

// 函数原型:data是一个引用到int
void modifyValue(int &data);

int main() {
    int value = 10;
    cout << "Before: " << value << endl;
    
    modifyValue(value); // 调用格式与值传递相同
    
    cout << "After: " << value << endl;
    return 0;
}

// 函数定义
void modifyValue(int &data) {
    data *= 2; // 修改原变量的值
}

常量引用参数

当不需要修改参数但想避免复制开销时,使用常量引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <string>
using namespace std;

// 使用常量引用避免复制,同时防止修改
void printString(const string &str) {
    cout << str << endl;
    // str[0] = 'X'; // 错误:不能修改常量引用
}

int main() {
    string longText = "This is a very long string that we want to avoid copying.";
    printString(longText);
    return 0;
}

返回引用

函数可以返回引用,但必须确保引用的对象在函数返回后仍然存在。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
using namespace std;

// 返回数组元素的引用
int &getElement(int arr[], int index) {
    return arr[index]; // 返回引用,可以修改原数组
}

int main() {
    int numbers[] = {1, 2, 3, 4, 5};
    
    // 通过返回的引用修改数组
    getElement(numbers, 2) = 10;
    
    cout << "Modified array: ";
    for (int i = 0; i < 5; i++) {
        cout << numbers[i] << " ";
    }
    cout << endl; // 输出: 1 2 10 4 5
    
    return 0;
}

引用与指针的对比示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
using namespace std;

// 使用引用参数
void swapByReference(int &a, int &b) {
    int temp = a;
    a = b;
    b = temp;
}

// 使用指针参数
void swapByPointer(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int x = 5, y = 10;
    
    cout << "Before swap: x = " << x << ", y = " << y << endl;
    
    // 使用引用
    swapByReference(x, y);
    cout << "After reference swap: x = " << x << ", y = " << y << endl;
    
    // 使用指针
    swapByPointer(&x, &y);
    cout << "After pointer swap: x = " << x << ", y = " << y << endl;
    
    return 0;
}

C++ Vector(向量):数组的现代替代方案

传统array(数组)缺点

C++ 中的传统数组确实有很多缺点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 数组的问题示例
void demonstrateArrayProblems() {
    // 1. 固定大小
    const int SIZE = 5;
    int arr[SIZE] = {1, 2, 3, 4, 5};
    
    // 无法动态调整大小
    // arr[5] = 6; // 错误:数组越界
    
    // 2. 传递数组给函数很麻烦
    printArray(arr, SIZE); // 必须传递大小参数
    
    // 3. 插入和删除元素困难
    // 要在中间插入元素,需要手动移动后面的所有元素
    // 要删除元素,同样需要移动大量元素
    
    // 4. 不能从函数返回数组
    // int[] createArray() { return {1,2,3}; } // 错误
}

// 必须传递大小参数
void printArray(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
}

Vector 的基本用法

Vector 是 C++ 标准模板库(STL)中的动态数组,解决了传统数组的所有问题。

  • 包含头文件和声明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include <vector>  // 必须包含这个头文件
using namespace std;

int main() {
    // 声明向量的不同方式
    
    // 1. 声明包含 20 个 int 的向量
    const int SIZE = 20;
    vector<int> numbers1(SIZE);
    
    // 2. 声明包含 20 个 int 的向量,所有元素初始化为 18
    vector<int> numbers2(SIZE, 18);
    
    // 3. 声明空向量
    vector<int> numbers3;
    
    // 4. 使用初始化列表(C++11)
    vector<int> numbers4 = {1, 2, 3, 4, 5};
    
    // 5. 从另一个向量复制
    vector<int> numbers5(numbers4);
    
    return 0;
}

Vector 的初始化细节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <vector>
using namespace std;

int main() {
    // 默认初始化:元素为默认值(int 为 0)
    vector<int> v1(5);        // 5 个 0: {0, 0, 0, 0, 0}
    
    // 指定初始值:所有元素初始化为指定值
    vector<int> v2(5, 18);    // 5 个 18: {18, 18, 18, 18, 18}
    
    // 注意:如果类型不匹配会发生隐式转换
    vector<int> v3(5, 3.5);   // 5 个 3: {3, 3, 3, 3, 3}(小数部分被截断)
    
    // 初始化列表(C++11)
    vector<int> v4 = {1, 2, 3, 4, 5};  // {1, 2, 3, 4, 5}
    
    // 空向量
    vector<int> v5;           // 空向量
    
    return 0;
}

Vector 的常用方法

  • 基本操作方法 ```cpp #include #include using namespace std;

int main() { vector numbers = {1, 2, 3};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 1. size() - 获取元素数量
cout << "Size: " << numbers.size() << endl; // 输出 3

// 2. empty() - 检查是否为空
cout << "Is empty: " << (numbers.empty() ? "Yes" : "No") << endl; // 输出 No

// 3. resize() - 调整大小
numbers.resize(5); // 调整为 5 个元素,新元素初始化为 0
cout << "After resize to 5: ";
for (int num : numbers) cout << num << " "; // 输出 1 2 3 0 0
cout << endl;

// 可以指定新元素的默认值
numbers.resize(7, 99); // 调整为 7 个元素,新元素初始化为 99
cout << "After resize to 7 with default 99: ";
for (int num : numbers) cout << num << " "; // 输出 1 2 3 0 0 99 99
cout << endl;

// 4. clear() - 清空向量
numbers.clear();
cout << "After clear - Size: " << numbers.size() << endl; // 输出 0
cout << "Is empty: " << (numbers.empty() ? "Yes" : "No") << endl; // 输出 Yes

return 0; } ```
  • 添加和删除元素 ```cpp #include #include using namespace std;

int main() { vector numbers;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// push_back() - 在末尾添加元素
numbers.push_back(10);
numbers.push_back(20);
numbers.push_back(30);

cout << "After push_back: ";
for (int num : numbers) cout << num << " "; // 输出 10 20 30
cout << endl;

// pop_back() - 删除末尾元素
numbers.pop_back();
cout << "After pop_back: ";
for (int num : numbers) cout << num << " "; // 输出 10 20
cout << endl;

// insert() - 在指定位置插入元素
numbers.insert(numbers.begin() + 1, 15); // 在索引 1 的位置插入 15
cout << "After insert at index 1: ";
for (int num : numbers) cout << num << " "; // 输出 10 15 20
cout << endl;

// erase() - 删除指定位置的元素
numbers.erase(numbers.begin()); // 删除第一个元素
cout << "After erase first element: ";
for (int num : numbers) cout << num << " "; // 输出 15 20
cout << endl;

return 0; } ``` ### Vector 的高级特性 - 访问元素的不同方式 ```cpp #include <iostream> #include <vector> using namespace std;

int main() { vector numbers = {10, 20, 30, 40, 50};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 1. 使用下标运算符 [](不检查边界)
cout << "Element at index 2: " << numbers[2] << endl; // 30

// 2. 使用 at() 方法(检查边界,更安全)
cout << "Element at index 2: " << numbers.at(2) << endl; // 30

// at() 会在越界时抛出异常
try {
    cout << "Element at index 10: " << numbers.at(10) << endl;
} catch (const out_of_range& e) {
    cout << "Error: " << e.what() << endl;
}

// 3. 访问第一个和最后一个元素
cout << "First element: " << numbers.front() << endl; // 10
cout << "Last element: " << numbers.back() << endl;   // 50

return 0; } ```
  • Vector 作为函数参数和返回值 ```cpp #include #include using namespace std;

// Vector 可以作为函数参数(推荐使用常量引用避免复制) void printVector(const vector& vec) { cout << "Vector elements: "; for (int num : vec) { cout << num << " "; } cout << endl; }

// Vector 可以作为函数返回值 vector createSequence(int start, int end) { vector result; for (int i = start; i <= end; i++) { result.push_back(i); } return result; // 可以返回 vector }

// 修改 vector 内容的函数 void doubleValues(vector& vec) { for (int& num : vec) { num *= 2; } }

int main() { // 测试作为返回值的函数 vector sequence = createSequence(1, 5); printVector(sequence); // 输出 1 2 3 4 5

1
2
3
4
5
// 测试修改 vector 的函数
doubleValues(sequence);
printVector(sequence); // 输出 2 4 6 8 10

return 0; } ```

函数的基本结构

  • 函数声明、定义和调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1. 函数声明(可以出现多次)
int functionA(int iX, int iY);

// 2. 函数定义(只能出现一次)
int functionA(int iX, int iY) {
    return iX + iY;
}

int main() {
    // 3. 函数调用
    int result = functionA(3, 5); // 3和5是实参(arguments)
    cout << "Result: " << result << endl; // 输出 8
    
    return 0;
}

例:斐波那契数列函数

  • 基础版本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int fibon_elem(int iPos) {
    if (iPos <= 0) return 0;
    if (iPos == 1 || iPos == 2) return 1;
    
    int n2 = 1, n1 = 1;
    int iElem = 1;
    
    for (int iX = 3; iX <= iPos; iX++) {
        iElem = n2 + n1;
        n2 = n1;
        n1 = iElem;
    }
    return iElem;
}

错误处理与输入验证

  • 改进的斐波那契函数(带错误检查)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// 改进版本:返回布尔值表示成功/失败,通过引用参数返回结果
bool fibon_elem(int iPos, int &iElem) {
    // 输入验证:防止无效位置
    if (iPos <= 0 || iPos >= 1024) {
        iElem = 0; 
        return false; // 表示计算失败
    }
    
    if (iPos == 1 || iPos == 2) {
        iElem = 1;
        return true;
    }
    
    int n2 = 1, n1 = 1;
    iElem = 1;
    
    for (int iX = 3; iX <= iPos; iX++) {
        iElem = n2 + n1;
        
        // 检查整数溢出
        if (iElem < 0) { // 如果出现负数,说明溢出
            iElem = 0;
            return false;
        }
        
        n2 = n1;
        n1 = iElem;
    }
    return true;
}

int main() {
    int result;
    
    // 有效输入
    if (fibon_elem(10, result)) {
        cout << "Fibonacci(10) = " << result << endl;
    } else {
        cout << "Invalid position or overflow!" << endl;
    }
    
    // 无效输入
    if (fibon_elem(2000, result)) {
        cout << "Fibonacci(2000) = " << result << endl;
    } else {
        cout << "Invalid position or overflow!" << endl;
    }
    
    // 边界情况
    if (fibon_elem(0, result)) {
        cout << "Fibonacci(0) = " << result << endl;
    } else {
        cout << "Invalid position!" << endl;
    }
    
    return 0;
}

性能优化与内存管理

  • 问题版本:返回局部变量的指针(危险!)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <vector>
using namespace std;

// 错误版本:返回局部变量的指针
vector<int>* fibon_seq_bad(int iLength) {
    if (iLength <= 0 || iLength >= 1024) {
        cerr << "Length " << iLength 
             << " not supported, reset to 8" << endl;
        iLength = 8;
    }
    
    vector<int> Elems(iLength); // 局部变量,函数结束后会被销毁
    
    for (int iX = 0; iX < iLength; iX++) {
        if (iX == 0 || iX == 1)
            Elems[iX] = 1;
        else
            Elems[iX] = Elems[iX-1] + Elems[iX-2];
    }
    
    return &Elems; // 危险!返回指向即将被销毁对象的指针
}

// 使用这个函数会导致未定义行为
void demonstrate_bad_example() {
    vector<int>* bad_ptr = fibon_seq_bad(10);
    // 此时 bad_ptr 指向的内存已经无效
    // 访问它是未定义行为
    // cout << (*bad_ptr)[0] << endl; // 可能崩溃或输出错误数据
}

作用域(Extent)的概念

  • C++ 中有几种不同的作用域:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <vector>
using namespace std;

// 文件作用域(全局变量)- 不推荐大量使用
vector<int> globalElems; // 在程序启动时分配,结束时销毁

void demonstrate_extents() {
    // 局部作用域(栈上分配)
    int localVar = 42; // 函数结束时自动销毁
    
    // 动态作用域(堆上分配)
    int* dynamicVar = new int(100); // 需要手动管理
    
    delete dynamicVar; // 必须手动释放
}

正确的内存管理:使用 new 和 delete

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void demonstrate_new_delete() {
    // 单个对象
    string* strPtr = new string("Hello");
    cout << *strPtr << endl;
    delete strPtr; // 删除单个对象
    
    // 对象数组
    string* strArray = new string[5];
    for (int i = 0; i < 5; i++) {
        strArray[i] = "String " + to_string(i);
    }
    delete[] strArray; // 删除数组(必须使用 delete[])
    
    // 错误的混合使用示例
    // int* badArray = new int[10];
    // delete badArray; // 错误!应该使用 delete[]
    // 这会导致未定义行为,可能内存泄漏
}
  • Fibonacci例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include <vector>
#include <string>
using namespace std;

// 正确的动态内存分配
vector<int>* create_fibon_seq(int iLength) {
    if (iLength <= 0 || iLength >= 1024) {
        return nullptr; // 返回空指针表示失败
    }
    
    // 使用 new 在堆上分配内存
    vector<int>* Elems = new vector<int>(iLength);
    
    for (int iX = 0; iX < iLength; iX++) {
        if (iX == 0 || iX == 1)
            (*Elems)[iX] = 1;
        else
            (*Elems)[iX] = (*Elems)[iX-1] + (*Elems)[iX-2];
    }
    
    return Elems; // 返回堆上对象的指针
}

// 使用函数
void use_dynamic_memory() {
    vector<int>* seq = create_fibon_seq(10);
    
    if (seq != nullptr) {
        cout << "Fibonacci sequence: ";
        for (int num : *seq) {
            cout << num << " ";
        }
        cout << endl;
        
        // 必须手动释放内存
        delete seq;
    }
}

优化方案:使用静态局部变量

  • 使用静态局部变量缓存结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <iostream>
#include <vector>
using namespace std;

// 优化版本:使用静态局部变量缓存计算结果
const vector<int>* fibon_seq(int iLength) {
    // 静态局部变量:在第一次调用时初始化,之后保持状态
    static vector<int> Elems;
    
    if (iLength <= 0 || iLength > 1024) {
        cerr << "Length " << iLength << " not supported" << endl;
        return nullptr;
    }
    
    // 如果请求的长度大于当前缓存的大小,扩展缓存
    if (iLength > Elems.size()) {
        for (int iX = Elems.size(); iX < iLength; iX++) {
            if (iX == 0 || iX == 1)
                Elems.push_back(1);
            else
                Elems.push_back(Elems[iX-1] + Elems[iX-2]);
        }
    }
    
    return &Elems; // 返回指向静态变量的指针(安全)
}

void use_static_version() {
    // 第一次调用会计算所有元素
    const vector<int>* seq1 = fibon_seq(10);
    if (seq1) {
        cout << "First 10: ";
        for (int i = 0; i < 10; i++) {
            cout << (*seq1)[i] << " ";
        }
        cout << endl;
    }
    
    // 第二次调用会复用之前计算的结果
    const vector<int>* seq2 = fibon_seq(15);
    if (seq2) {
        cout << "First 15: ";
        for (int i = 0; i < 15; i++) {
            cout << (*seq2)[i] << " ";
        }
        cout << endl;
    }
}

代码重构与内联函数

  • 将功能拆分为小函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>
#include <vector>
using namespace std;

// 内联函数:长度验证(建议编译器内联展开)
inline bool is_size_ok(int iLength) {
    if (iLength <= 0 || iLength > 1024) {
        cerr << "Length " << iLength << " not supported" << endl;
        return false;
    }
    return true;
}

// 计算并扩展斐波那契序列
void fibon_expand(vector<int>& Elems, int iLength) {
    for (int iX = Elems.size(); iX < iLength; iX++) {
        if (iX == 0 || iX == 1)
            Elems.push_back(1);
        else
            Elems.push_back(Elems[iX-1] + Elems[iX-2]);
    }
}

// 重构后的主函数
const vector<int>* fibon_seq_refactored(int iLength) {
    static vector<int> Elems;
    
    if (!is_size_ok(iLength)) {
        return nullptr;
    }
    
    fibon_expand(Elems, iLength);
    return &Elems;
}

// 值传递版本(简单但可能效率较低)
vector<int> fibon_seq_by_value(int iLength) {
    vector<int> Elems;
    
    if (!is_size_ok(iLength)) {
        return Elems; // 返回空向量
    }
    
    fibon_expand(Elems, iLength);
    return Elems; // 返回副本
}
本文由作者按照 CC BY 4.0 进行授权