C++

C++ 数据处理基础

在C++中,处理数据的基础是了解变量类型、常量、算数运算符等。以下是这一章节的详细介绍。

1. 变量类型

C++支持多种数据类型,常用的有:

  • 整型 (int):用于表示整数。例如,int a = 5;
  • 浮点型 (float, double):用于表示带小数的数值。float占4字节,double占8字节。
    float b = 3.14f;   // 使用f表示float
    double c = 3.14159; // 默认是double
    
  • 字符型 (char):用于表示单个字符。用单引号括起来,例如,char d = 'A';
  • 布尔型 (bool):用于表示真(true)或假(false)。

1.1. const限定符

const关键字用于声明常量,一旦被初始化后,值不能被改变。例如:

const int MAX_VALUE = 100;
// MAX_VALUE = 200; // 错误,不能修改

使用 const可以增加代码的可读性和安全性。

2. 浮点数详解

浮点数在C++中用于表示小数,有两种主要类型:floatdouble。其表示范围和精度有所不同:

  • float:大约7位有效数字
  • double:大约15位有效数字

2.1. 注意事项

  • 浮点数运算可能会导致精度丢失。例如,0.1 + 0.2 可能不会等于 0.3
  • 对于需要高精度的计算,可以考虑使用 double,但在性能上可能会有所影响。

3. 算数运算符

C++提供了多种算数运算符来进行数值计算:

  • 加法 (+)a + b
  • 减法 (-)a - b
  • 乘法 (*)a * b
  • 除法 (/)a / b
  • 取模 (%):用于整型的取余运算。

3.1. 运算符示例

int a = 10;
int b = 3;
int sum = a + b;      // 结果为13
int difference = a - b; // 结果为7
int product = a * b;  // 结果为30
float quotient = (float)a / b; // 结果为3.33333
int remainder = a % b; // 结果为1

4. 其他重要内容

  • 类型转换:可以通过强制类型转换来转换数据类型。例如:

    double x = 5.7;
    int y = (int)x; // y的值为5
    
  • 自增和自减运算符++--可以用来增加或减少变量的值。

    int z = 5;
    z++; // z的值变为6
    

C++ 复合类型

在C++中,复合类型是用于存储多个数据项的数据结构。复合类型包括数组、字符串、结构体、联合体、枚举、指针等。以下是对这些复合类型的详细介绍。

1. 数组的详解

数组是同一类型数据的集合,允许通过索引访问元素。定义数组的基本语法如下:

type arrayName[arraySize];

1.1. 一维数组

一维数组是最简单的数组类型,声明和使用如下:

int arr[5] = {1, 2, 3, 4, 5}; // 声明并初始化一个整型数组

访问数组元素时,可以使用索引:

int firstElement = arr[0]; // 访问第一个元素

1.2. 多维数组

多维数组是数组的数组。最常用的是二维数组:

int matrix[3][3] = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

访问元素的方式:

int element = matrix[1][2]; // 访问第二行第三列的元素

2. 字符串的介绍和方法

C++中有两种字符串处理方式:C风格字符串和C++标准库中的 string类。

2.1. C风格字符串

C风格字符串是字符数组,以空字符 \0结尾。例如:

char str[] = "Hello";

常用字符串操作函数有:

  • strlen(str):返回字符串长度。
  • strcpy(dest, src):拷贝字符串。
  • strcmp(str1, str2):比较字符串。

2.2. C++ string类简介

C++标准库提供了 string类,方便进行字符串操作。

#include <string>
using namespace std;

string str = "Hello, World!";

常用方法包括:

  • length():返回字符串长度。
  • substr(pos, len):提取子字符串。
  • find(sub):查找子字符串位置。

3. 结构简介

结构是用户自定义的数据类型,用于将不同类型的数据组合在一起。定义结构的基本语法如下:

struct StructName {
    type member1;
    type member2;
    // ...
};

3.1. 结构数组

可以创建结构体的数组来存储多个结构体实例:

struct Student {
    string name;
    int age;
};

Student students[10]; // 声明一个学生结构体数组

3.2. 结构中的位字段

位字段允许为结构体的某个成员分配特定的比特数。例如:

struct BitField {
    unsigned int a : 3; // 3位
    unsigned int b : 5; // 5位
};

3.3. 结构属性

结构体可以包含函数成员,也可以定义构造函数和析构函数。

struct Rectangle {
    int width, height;

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

3.4. 共用体

共用体允许在同一内存位置存储不同类型的数据,定义方法如下:

union UnionName {
    int intValue;
    float floatValue;
};

共用体的大小是其最大成员的大小。

4. 枚举

枚举用于定义一组相关的常量,提供可读性。定义方式如下:

enum Color { RED, GREEN, BLUE };

可以指定枚举值:

enum Color { RED = 1, GREEN, BLUE }; // GREEN自动为2,BLUE为3

5. 指针和自由存储空间

指针是存储地址的变量,用于动态内存分配和数据结构。

5.1. 指针的基本用法

int a = 10;
int* p = &a; // p指向a的地址

5.2. 动态内存分配

使用 newdelete进行动态内存管理:

int* arr = new int[5]; // 动态分配数组
delete[] arr;          // 释放内存

6. 类型组合

C++允许将多种数据类型组合到一起,包括数组、结构体和指针的组合。

struct Person {
    string name;
    int age;
};

Person* people = new Person[10]; // 动态分配Person结构体数组

7. vectorarray

7.1. vector

vector是动态数组,可以在运行时自动调整大小。包含在 <vector>头文件中:

#include <vector>
using namespace std;

vector<int> vec; // 声明一个整型vector
vec.push_back(10); // 添加元素

7.2. array

array是固定大小的数组,包含在 <array>头文件中:

#include <array>
using namespace std;

array<int, 5> arr = {1, 2, 3, 4, 5}; // 声明一个大小为5的array

array提供了更安全的访问方法,例如 at()

C++ 循环和关系表达式

在C++中,循环用于重复执行一段代码,直到满足特定条件。关系表达式用于比较两个值,结果为真或假。以下是这一章节的详细介绍。

1. for循环

for循环是最常用的循环结构之一,适合于已知循环次数的情况。基本语法如下:

for (initialization; condition; increment) {
    // 循环体
}

1.1. 示例

for (int i = 0; i < 5; i++) {
    cout << "Hello, World!" << endl;
}

在这个例子中,i从0开始,每次循环 i加1,直到 i不再小于5。

2. while循环

while循环适合于当循环次数未知时,基于某个条件来决定是否继续执行的情况。基本语法如下:

while (condition) {
    // 循环体
}

2.1. 示例

int count = 0;
while (count < 5) {
    cout << "Count: " << count << endl;
    count++;
}

这个例子会打印 Count: 0Count: 4

3. 基于范围的for循环

C++11引入了基于范围的 for循环,简化了对容器的遍历。基本语法如下:

for (declaration : collection) {
    // 循环体
}

3.1. 示例

vector<int> vec = {1, 2, 3, 4, 5};
for (int num : vec) {
    cout << num << " "; // 输出:1 2 3 4 5
}

基于范围的 for循环使得遍历容器更加简洁明了。

4. 嵌套循环

嵌套循环是指在一个循环内部再定义一个循环。常用于处理多维数组或复杂的逻辑。

4.1. 示例

for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        cout << "(" << i << ", " << j << ") ";
    }
    cout << endl; // 换行
}

输出结果为:

(0, 0) (0, 1) (0, 2) 
(1, 0) (1, 1) (1, 2) 
(2, 0) (2, 1) (2, 2) 

5. 二维数组与嵌套循环

处理二维数组时,通常会使用嵌套循环来访问每个元素。

5.1. 示例

int matrix[2][3] = {
    {1, 2, 3},
    {4, 5, 6}
};

for (int i = 0; i < 2; i++) {
    for (int j = 0; j < 3; j++) {
        cout << matrix[i][j] << " ";
    }
    cout << endl; // 换行
}

输出结果为:

1 2 3 
4 5 6 

6. 关系表达式

关系表达式用于比较两个值,结果为布尔类型(truefalse)。常用的关系运算符有:

  • ==:等于
  • !=:不等于
  • <:小于
  • >:大于
  • <=:小于等于
  • >=:大于等于

6.1. 示例

int a = 5, b = 10;
if (a < b) {
    cout << "a is less than b" << endl; // 输出
}

if (a != b) {
    cout << "a is not equal to b" << endl; // 输出
}

总结

这一章节介绍了C++中的循环结构,包括 for循环、while循环、基于范围的 for循环、嵌套循环以及如何使用这些循环处理二维数组。同时,关系表达式的使用也被介绍,以便于在程序中进行条件判断。

C++ 循环和文本输入

在C++中,处理文本输入是一个常见的需求。我们可以使用标准输入流 cin来获取用户输入,同时也可以使用其他函数和方法来处理文本输入。这一章节将详细介绍 cin输入、cin.get()、文件尾条件(EOF)以及 getchar()putchar()的用法。

1. cin输入

cin是C++中的标准输入流,用于从键盘获取输入。基本用法如下:

int number;
cout << "Enter a number: ";
cin >> number; // 从输入流读取整数

1.1. 输入多个数据

可以使用 cin一次性读取多个数据项:

int a, b;
cout << "Enter two numbers: ";
cin >> a >> b;

如果输入的格式不符合预期,cin会进入错误状态。

2. cin.get(char)补救输入

cin.get()用于从输入流中读取单个字符。其基本用法如下:

char ch;
cin.get(ch); // 读取一个字符到ch

这种方法在需要逐字符处理输入时非常有用。

2.1. 示例

char ch;
cout << "Enter a character: ";
cin.get(ch); // 读取用户输入的字符
cout << "You entered: " << ch << endl;

3. 选择哪一个cin.get()

在处理输入时,有时需要选择使用 cincin.get()。通常,cin用于读取格式化数据(如整数、浮点数、字符串),而 cin.get()适用于读取字符或处理文本行。

3.1. 示例:结合使用

string line;
cout << "Enter a line: ";
getline(cin, line); // 使用getline读取一整行
cout << "You entered: " << line << endl;

在这种情况下,getline读取整行文本,而 cin.get()可以用于处理行内的每个字符。

4. 文件尾条件(EOF)

EOF(End Of File)是指输入流中的数据结束。在处理输入时,程序可以检测到EOF,以决定何时停止读取数据。EOF可以通过输入Ctrl+D(Unix/Linux)或Ctrl+Z(Windows)来触发。

4.1. 检测EOF

使用 cin时,可以通过检查 cin.eof()方法来判断是否到达文件末尾:

int number;
while (cin >> number) { // 直到输入结束
    cout << "You entered: " << number << endl;
}

当用户输入EOF后,循环将结束。

5. 另一个cin.get()版本(getchar(), putchar())

getchar()putchar()是C标准库中的函数,用于从标准输入读取字符和向标准输出写入字符。它们的基本用法如下:

5.1. getchar()

getchar()读取下一个字符并返回其ASCII值:

char ch;
cout << "Enter a character: ";
ch = getchar(); // 读取字符
cout << "You entered: " << ch << endl;

5.2. putchar()

putchar()用于将字符输出到标准输出:

putchar(ch); // 输出字符

6. 结合使用cin和循环进行文本输入

使用循环和 cingetchar()结合可以处理多行输入。

6.1. 示例

char ch;
cout << "Enter characters (Ctrl+D to end): " << endl;
while ((ch = getchar()) != EOF) { // 读取字符直到EOF
    putchar(ch); // 输出每个读取的字符
}

总结

这一章节详细介绍了C++中的文本输入处理,包括使用 cin进行基本输入、使用 cin.get()逐字符读取、检测文件尾条件(EOF)以及 getchar()putchar()的用法。掌握这些方法有助于更灵活地处理用户输入和文件读取。

C++ 分支语句和逻辑运算符

在C++中,分支语句用于根据条件的真假执行不同的代码块,而逻辑运算符用于组合多个布尔表达式。本章将详细介绍 if语句、逻辑表达式、switch语句及简单的文件输入/输出。

1. if语句

if语句用于根据条件的真假来决定执行哪一段代码。基本语法如下:

if (condition) {
    // 当条件为真时执行的代码
}

1.1. 示例

int number;
cout << "Enter a number: ";
cin >> number;

if (number > 0) {
    cout << "Positive number." << endl;
}

2. 逻辑表达式

逻辑运算符用于组合布尔表达式,主要有以下几种:

  • &&(逻辑与):只有当两个条件都为真时,结果为真。
  • ||(逻辑或):只要有一个条件为真,结果就为真。
  • !(逻辑非):对条件取反。

2.1. cctype库

cctype库提供了一些用于字符分类的函数,如 isdigit()isalpha()等。使用这些函数可以简化逻辑判断:

#include <cctype>

char ch;
cout << "Enter a character: ";
cin >> ch;

if (isdigit(ch)) {
    cout << ch << " is a digit." << endl;
}

2.2. 三元运算符(?:)

三元运算符是一种简化 if-else语句的方式。基本语法如下:

condition ? expression1 : expression2;

2.3. 示例

int number;
cout << "Enter a number: ";
cin >> number;

string result = (number > 0) ? "Positive" : "Non-positive";
cout << "The number is " << result << "." << endl;

3. switch语句

switch语句用于根据变量的值执行不同的代码块。其基本语法如下:

switch (expression) {
    case constant1:
        // 代码块1
        break;
    case constant2:
        // 代码块2
        break;
    default:
        // 默认代码块
}

3.1. 示例

int day;
cout << "Enter a day (1-7): ";
cin >> day;

switch (day) {
    case 1:
        cout << "Monday" << endl;
        break;
    case 2:
        cout << "Tuesday" << endl;
        break;
    // ...
    default:
        cout << "Invalid day" << endl;
}

4. break和continue

  • break:用于跳出 switch语句或循环。
  • continue:用于跳过当前循环的迭代,继续下一次循环。

4.1. 示例

for (int i = 0; i < 5; i++) {
    if (i == 2) {
        continue; // 跳过i=2时的循环
    }
    cout << i << " "; // 输出:0 1 3 4
}

5. 读取文字的循环

可以使用 cin和循环结合来读取多行输入,直到输入EOF为止。

5.1. 示例

string line;
cout << "Enter text (Ctrl+D to end): " << endl;
while (getline(cin, line)) {
    cout << "You entered: " << line << endl;
}

6. 简单文件输入/输出

C++提供了文件输入输出的功能,使用 fstream库进行文件操作。

6.1. 文件输出

#include <fstream>

ofstream outfile("output.txt");
outfile << "Hello, File!" << endl;
outfile.close(); // 关闭文件

6.2. 文件输入

#include <fstream>

ifstream infile("output.txt");
string line;
while (getline(infile, line)) {
    cout << line << endl; // 输出文件中的每一行
}
infile.close(); // 关闭文件

总结

这一章节详细介绍了C++中的分支语句和逻辑运算符,包括 if语句、逻辑表达式、switch语句及其相关的 breakcontinue,以及简单的文件输入/输出。掌握这些内容对构建复杂的控制结构和处理文件非常重要。

C++ 函数—编程模块

函数是C++中重要的编程模块,用于封装代码,提高代码的可重用性和可维护性。本章将详细介绍函数的基本知识、参数传递、与数组和字符串的关系、递归以及函数指针。

1. 函数的基本知识

1.1. 定义与原型

函数定义包括返回类型、函数名和参数列表。函数原型是函数的声明,通常在函数实现之前提供。

// 函数原型
int add(int a, int b);

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

1.2. 函数调用

调用函数时,只需使用函数名并传入必要的参数:

int result = add(5, 3); // result为8

2. 函数参数与按值传参

C++中,函数参数可以按值传递,表示将参数的副本传递给函数。多个参数可以在一个函数中使用。

2.1. 示例

void displaySum(int a, int b) {
    cout << "Sum: " << (a + b) << endl;
}

displaySum(10, 20); // 输出:Sum: 30

3. 函数与数组

3.1. 函数使用指针传递数组

数组名作为参数传递时,实际上是传递了数组的地址。可以在函数中通过指针访问数组元素。

void printArray(int* arr, int size) {
    for (int i = 0; i < size; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
}

int main() {
    int nums[] = {1, 2, 3, 4, 5};
    printArray(nums, 5); // 输出:1 2 3 4 5
}

3.2. 使用数组区间的函数

可以使用指针和数组区间的组合来处理数组。

void printArray(int* begin, int* end) {
    while (begin != end) {
        cout << *begin << " ";
        begin++;
    }
    cout << endl;
}

3.3. 指针与const

可以使用 const来防止函数修改传入的数组内容。

void printArray(const int* arr, int size) {
    for (int i = 0; i < size; i++) {
        cout << arr[i] << " ";
    }
}

3.4. 将数组作为参数意味着什么

将数组作为参数意味着传递的是数组的首地址,函数内对数组内容的修改将反映在原数组中。

3.5. 函数与二维数组

对于二维数组,需要在函数参数中指定第二维的大小。

void printMatrix(int matrix[][3], int rows) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < 3; j++) {
            cout << matrix[i][j] << " ";
        }
        cout << endl;
    }
}

4. 函数与C风格字符串

4.1. 作为参数

C风格字符串是以 '\0'结束的字符数组,可以作为函数参数传递。

void printString(const char* str) {
    cout << str << endl;
}

4.2. 返回值

函数可以返回C风格字符串的指针。

const char* getGreeting() {
    return "Hello, World!";
}

5. 函数与结构

5.1. string对象

可以将 std::string作为参数传递给函数。

void printString(const string& str) {
    cout << str << endl;
}

5.2. array对象

C++标准库的 std::array可以作为函数参数。

#include <array>

void printArray(const std::array<int, 5>& arr) {
    for (const int& num : arr) {
        cout << num << " ";
    }
}

6. 递归

递归是指函数直接或间接地调用自身。递归需要一个基本情况以防止无限循环。

6.1. 包含一个递归调用的递归

int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1); // 递归调用
}

6.2. 包含多个递归调用的递归

int fibonacci(int n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2); // 多次递归调用
}

7. 函数指针

函数指针是指向函数的指针,可以用于回调或动态选择函数。

7.1. 基础知识

定义函数指针的基本语法如下:

int (*funcPtr)(int, int); // 指向返回类型为int,参数为两个int的函数指针

7.2. 深入探讨

函数指针可以作为参数传递。

void executeFunction(int (*func)(int, int), int a, int b) {
    cout << func(a, b) << endl; // 调用指向的函数
}

7.3. 示例

int add(int x, int y) {
    return x + y;
}

int main() {
    int (*funcPtr)(int, int) = add;
    executeFunction(funcPtr, 3, 4); // 输出:7
}

7.4. typedef简化

使用 typedef可以简化函数指针的定义:

typedef int (*FuncPtr)(int, int);

void executeFunction(FuncPtr func, int a, int b) {
    cout << func(a, b) << endl;
}

总结

本章详细介绍了C++中的函数,包括函数的定义与调用、参数传递、与数组和字符串的关系、递归的实现,以及函数指针的使用。掌握这些概念对于编写模块化和可重用的代码至关重要。

C++ 函数探幽

在C++中,函数的使用可以进一步增强程序的灵活性和可读性。本章将探讨内联函数、引用变量、函数重载和函数模板,每个概念将涵盖其意义、使用方法、示例、使用局限以及优缺点。

1. C++内联函数

1.1. 意义

内联函数是一种请求编译器在每次调用时插入函数体的函数,而不是进行常规的函数调用。内联函数的主要目的是提高效率,尤其是在频繁调用的小函数中。

1.2. 使用

使用 inline关键字定义内联函数:

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

1.3. 举例

#include <iostream>
using namespace std;

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

int main() {
    cout << add(5, 3) << endl; // 输出:8
}

1.4. 使用局限

  • 内联函数不能包含循环或递归。
  • 编译器可能会忽略 inline请求。

1.5. 优缺点

  • 优点

    • 减少函数调用的开销,提高性能。
    • 简化代码,增加可读性。
  • 缺点

    • 如果内联函数体较大,可能导致代码膨胀。
    • 并非所有的函数都适合内联,选择不当可能导致性能下降。

2. 引用变量

2.1. 意义

引用变量是一个别名,它允许程序使用一个变量的另一个名字。引用通常用于简化参数传递,避免复制开销。

2.2. 使用

定义引用时,使用 &符号:

int& ref = original; // original是一个int类型的变量

2.3. 举例

void increment(int& value) {
    value++; // 直接修改原变量
}

int main() {
    int num = 5;
    increment(num);
    cout << num << endl; // 输出:6
}

2.4. 使用局限

  • 引用必须在初始化时赋值,且不能更改指向。
  • 不可为空引用。

2.5. 优缺点

  • 优点

    • 省去了传值带来的开销。
    • 更加直观,易于理解代码。
  • 缺点

    • 可能导致不可预知的副作用,尤其在修改引用参数时。
    • 引用的生命周期依赖于所引用的对象。

3. 函数重载

3.1. 意义

函数重载允许在同一作用域内定义多个同名函数,只要它们的参数列表不同(参数类型、数量或顺序)。

3.2. 使用

可以通过不同的参数列表来实现重载:

void print(int value);
void print(double value);
void print(const string& value);

3.3. 举例

#include <iostream>
using namespace std;

void display(int a) {
    cout << "Integer: " << a << endl;
}

void display(double b) {
    cout << "Double: " << b << endl;
}

int main() {
    display(5);     // 调用int版本
    display(5.5);   // 调用double版本
}

3.4. 使用局限

  • 仅依赖参数类型、数量或顺序进行重载,返回类型不算。
  • 重载可能导致代码不易阅读,尤其在参数类型相似时。

3.5. 优缺点

  • 优点

    • 增强了程序的灵活性和可读性。
    • 允许同一功能的不同实现,简化调用。
  • 缺点

    • 可能导致混淆,尤其是在重载复杂的情况下。
    • 编译器错误信息可能不够清晰。

4. 函数模板

4.1. 意义

函数模板允许创建通用的函数,能够接受任意类型的参数。这样可以减少代码重复,提高代码的灵活性。

4.2. 使用

使用 template关键字定义模板:

template<typename T>
T add(T a, T b) {
    return a + b;
}

4.3. 举例

#include <iostream>
using namespace std;

template<typename T>
T multiply(T a, T b) {
    return a * b;
}

int main() {
    cout << multiply(5, 3) << endl;        // 输出:15
    cout << multiply(2.5, 4.0) << endl;    // 输出:10
}

4.4. 使用局限

  • 模板在编译时生成代码,可能导致较大的可执行文件。
  • 调试时,模板错误信息可能较复杂。

4.5. 优缺点

  • 优点

    • 提高代码的重用性和灵活性,支持不同类型的参数。
    • 减少重复代码的需求。
  • 缺点

    • 编译时间较长,因为模板在编译时实例化。
    • 可能导致可执行文件增大,影响性能。

总结

本章探讨了C++中的函数内联、引用变量、函数重载和函数模板的概念。每个概念都有其独特的意义、使用方式、优缺点及局限性。掌握这些知识可以帮助程序员更高效地使用C++语言编写灵活的代码。

C++ 内存模型和名称空间

C++的内存模型对于理解变量的存储、作用域以及程序的组织结构至关重要。本章将介绍单独编译、存储持续性、作用域和链接性,以及名称空间。

1. 单独编译

单独编译是指将每个源文件独立编译成目标文件,然后再将这些目标文件链接成一个可执行文件。这种方法提高了编译效率,并支持模块化编程。

1.1. 示例

在C++项目中,通常会有多个源文件(如 file1.cppfile2.cpp),每个文件可以独立编译:

g++ -c file1.cpp    # 生成 file1.o
g++ -c file2.cpp    # 生成 file2.o
g++ file1.o file2.o -o my_program  # 链接生成可执行文件

2. 存储持续性、作用域和链接性

2.1. 作用域和链接

  • 作用域:变量的可见性和生存时间。在C++中,作用域主要分为块作用域、函数作用域和文件作用域。
  • 链接:指在不同的文件或不同的作用域中,如何访问和引用变量或函数。

2.2. 静态持续变量

静态持续变量是在程序的整个生命周期内保持其值,通常在函数内定义时使用 static关键字。

void counter() {
    static int count = 0; // 静态持续变量
    count++;
    cout << count << endl;
}

2.3. 静态持续性、外部链接性

静态持续变量具有外部链接性时,可以在多个文件中使用 extern关键字访问。

// file1.cpp
static int shared = 10; // 内部链接

// file2.cpp
extern int shared; // 外部链接

2.4. 静态持续性、内部链接性

内部链接性意味着变量只能在定义它的文件中访问。静态变量默认是内部链接的。

2.5. 静态存储持续性、无链接性

这类变量在程序整个运行期间存在,但无法在程序其他文件中访问。

static int internal = 5; // 无链接性

2.6. 说明符和限定符

  • 说明符:如 staticexternconst等,用于控制变量的存储持续性和链接性。
  • 限定符:如 constvolatile等,影响变量的使用和修改。

2.7. 函数和链接性

函数也可以具有链接性,分为外部链接和内部链接。默认情况下,函数具有外部链接性,可以在其他文件中访问。

2.8. 语言链接性

C++支持与C语言的链接性,可以使用 extern "C"来指示编译器以C语言方式链接函数。

extern "C" {
    void c_function();
}

2.9. 存储方案和动态分配

  • 存储方案:包括栈、堆和全局区域。栈用于自动存储,堆用于动态分配。
  • 动态分配:使用 newdelete关键字进行内存管理。
int* ptr = new int; // 动态分配
delete ptr;        // 释放内存

2.10. 自动存储持续性

自动存储持续性表示变量在其所在的块或函数执行时有效,离开作用域后自动释放。局部变量通常具有这种持续性。

void myFunction() {
    int localVar = 10; // 自动存储
} // localVar在此处失效

3. 名称空间

名称空间用于避免命名冲突,允许在同一程序中使用相同名称的变量或函数。使用 namespace关键字定义。

3.1. 示例

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

namespace Physics {
    int add(int a, int b) {
        return a + b + 1; // 假设有额外的物理常数
    }
}

int main() {
    cout << Math::add(5, 3) << endl;    // 调用Math命名空间的add
    cout << Physics::add(5, 3) << endl; // 调用Physics命名空间的add
}

3.2. 使用局限

  • 命名空间可以嵌套,可能导致复杂性增加。
  • 不建议使用 using namespace在头文件中,可能导致全局命名冲突。

总结

本章介绍了C++的内存模型,包括单独编译、存储持续性、作用域和链接性,以及名称空间的使用。理解这些概念对于高效地管理C++程序的内存和组织结构至关重要。

C++ 对象和类

在C++中,面向对象编程(OOP)是一种重要的编程范式,与传统的过程性编程有所不同。本章将探讨过程性编程与面向对象编程的区别,详细介绍类与抽象,构造函数与析构函数,this指针,对象数组,类作用域,以及抽象数据类型。

1. 过程性编程与面向对象编程

1.1. 过程性编程

过程性编程是一种基于函数的编程方法,强调通过调用函数来实现程序逻辑。程序由一系列的函数组成,这些函数接收输入、处理数据并输出结果。主要特征包括:

  • 代码的逻辑结构基于函数调用。
  • 数据和功能分开。
  • 程序的可扩展性和可维护性较低。

示例:

#include <iostream>
using namespace std;

void process(int a, int b) {
    cout << "Sum: " << (a + b) << endl;
}

int main() {
    process(5, 10);
}

1.2. 面向对象编程

面向对象编程是一种基于对象的编程方法,强调通过封装数据和功能来提高程序的可维护性和可扩展性。主要特征包括:

  • 将数据和功能结合在一起,形成类和对象。
  • 支持继承和多态,促进代码重用。
  • 强调抽象,通过接口隐藏实现细节。

示例:

#include <iostream>
using namespace std;

class Calculator {
public:
    int add(int a, int b) {
        return a + b;
    }
};

int main() {
    Calculator calc;
    cout << "Sum: " << calc.add(5, 10) << endl;
}

2. 抽象和类

2.1. 抽象

抽象是指通过隐藏复杂性,突出必要特征的过程。在OOP中,类作为抽象的工具,允许开发者定义对象的属性和行为。

2.2. 类的定义

类是面向对象编程的基本构建块,用于创建对象。类包含成员变量(属性)和成员函数(方法)。

class Dog {
public:
    string name;
    void bark() {
        cout << "Woof! My name is " << name << endl;
    }
};

3. 类的构造函数和析构函数

3.1. 构造函数

构造函数是在创建对象时自动调用的特殊成员函数,用于初始化对象。构造函数可以重载。

class Person {
public:
    string name;

    Person(string n) { // 构造函数
        name = n;
    }
};

3.2. 析构函数

析构函数是在对象生命周期结束时自动调用的特殊成员函数,通常用于释放资源。

class Sample {
public:
    Sample() {
        cout << "Constructor called" << endl;
    }
    ~Sample() { // 析构函数
        cout << "Destructor called" << endl;
    }
};

4. this指针

this指针是一个隐式指针,用于指向当前对象。可以在类的成员函数中使用,以区分同名的参数和成员变量。

class Example {
public:
    int value;

    Example(int value) {
        this->value = value; // 区分参数和成员变量
    }
};

5. 对象数组

对象数组是一个可以存储多个对象的数组,允许对一组相同类型的对象进行管理。

class Book {
public:
    string title;
};

int main() {
    Book books[3]; // 创建Book类的对象数组
    books[0].title = "C++ Primer";
    books[1].title = "Effective C++";
    books[2].title = "The C++ Programming Language";
}

6. 类作用域

类的作用域限制了类成员的可见性。公有(public)、私有(private)和保护(protected)是访问修饰符,用于控制访问权限。

class Example {
private:
    int secret; // 私有成员,只能在类内部访问
public:
    void setSecret(int s) {
        secret = s;
    }
    int getSecret() {
        return secret;
    }
};

7. 抽象数据类型(ADT)

抽象数据类型是对数据结构和操作的数学描述,而不考虑具体实现。使用类可以实现ADT,从而提供数据的封装和操作。

class Stack {
private:
    int arr[100];
    int top;

public:
    Stack() : top(-1) {}
    void push(int x) {
        arr[++top] = x;
    }
    int pop() {
        return arr[top--];
    }
};

总结

本章介绍了C++中的对象和类,包括过程性编程与面向对象编程的区别,抽象和类的定义,构造函数和析构函数,this指针,对象数组,类的作用域,以及抽象数据类型。掌握这些概念将帮助程序员更好地组织代码,提高程序的可维护性和可扩展性。

C++ 使用类

在C++中,类不仅可以封装数据和方法,还可以通过运算符重载和友元机制增强其功能。本章将探讨运算符重载、友元、重载运算符的实现方式(作为成员函数或非成员函数)、矢量类的重载,以及类的自动转换和强制类型转换。

1. 运算符重载

运算符重载允许用户定义自定义类型的运算符行为,使得对象之间的运算符可以像内置类型一样使用。这种特性增强了代码的可读性和直观性。

1.1. 示例

下面是一个简单的运算符重载示例,演示如何重载加法运算符:

#include <iostream>
using namespace std;

class Point {
public:
    int x, y;

    Point(int x, int y) : x(x), y(y) {}

    // 重载加法运算符
    Point operator+(const Point& p) {
        return Point(x + p.x, y + p.y);
    }
};

int main() {
    Point p1(1, 2);
    Point p2(3, 4);
    Point p3 = p1 + p2; // 使用重载的加法运算符
    cout << "Result: (" << p3.x << ", " << p3.y << ")" << endl; // 输出:(4, 6)
}

2. 友元

友元是指可以访问类的私有成员的函数或类。通过将某个函数或类声明为友元,可以提高代码的灵活性和封装性。

2.1. 示例

class Box {
private:
    int length;

public:
    Box(int len) : length(len) {}

    // 友元函数
    friend void printLength(Box b);
};

void printLength(Box b) {
    cout << "Length: " << b.length << endl;
}

int main() {
    Box box(10);
    printLength(box); // 友元函数可以访问私有成员
}

3. 重载运算符作为成员函数还是非成员函数

运算符重载可以作为成员函数或非成员函数实现,选择哪种方式取决于具体需求。

3.1. 作为成员函数

如果运算符的第一个操作数是类的对象,通常使用成员函数:

Point operator+(const Point& p) {
    return Point(x + p.x, y + p.y);
}

3.2. 作为非成员函数

如果运算符的第一个操作数不是类的对象,或需要对多个类之间的运算符进行重载,使用非成员函数更合适:

friend Point operator+(const Point& p1, const Point& p2) {
    return Point(p1.x + p2.x, p1.y + p2.y);
}

4. 重载:一个矢量类

下面是一个简单的矢量类示例,演示如何实现运算符重载:

class Vector {
public:
    double x, y;

    Vector(double x, double y) : x(x), y(y) {}

    // 重载加法运算符
    Vector operator+(const Vector& v) {
        return Vector(x + v.x, y + v.y);
    }

    // 重载输出运算符
    friend ostream& operator<<(ostream& os, const Vector& v) {
        os << "(" << v.x << ", " << v.y << ")";
        return os;
    }
};

int main() {
    Vector v1(1.0, 2.0);
    Vector v2(3.0, 4.0);
    Vector v3 = v1 + v2; // 使用重载的加法运算符
    cout << "Result: " << v3 << endl; // 输出:(4, 6)
}

5. 类的自动转换和强制类型转换

5.1. 自动转换

C++支持类的自动类型转换,通过定义构造函数来实现。例如,可以将一个基本类型自动转换为自定义类型:

class Distance {
public:
    int meters;

    Distance(int m) : meters(m) {} // 构造函数
};

void showDistance(Distance d) {
    cout << "Distance: " << d.meters << " meters" << endl;
}

int main() {
    showDistance(100); // 自动转换为Distance
}

5.2. 强制类型转换

如果需要更明确的转换,可以定义一个类型转换运算符:

class Temperature {
public:
    double celsius;

    Temperature(double c) : celsius(c) {}

    // 强制转换为double类型
    operator double() const {
        return celsius;
    }
};

int main() {
    Temperature t(25.0);
    double temp = (double)t; // 强制类型转换
    cout << "Temperature: " << temp << "°C" << endl;
}

总结

本章探讨了C++中使用类的高级特性,包括运算符重载、友元、重载运算符的实现方式,以及矢量类的重载和类的自动与强制类型转换。这些特性不仅增强了C++的表达能力,也提高了代码的可读性和可维护性。

C++ 类和动态内存分配

在C++中,动态内存分配为类提供了灵活性,使得在运行时可以控制内存的使用。本章将探讨如何对类使用动态内存分配,隐式和显式的复制构造函数与重载赋值运算符,在构造函数中使用 new,使用静态类成员,将定位 new 运算符用于对象,以及如何实现队列抽象数据类型(ADT)。

1. 动态内存分配

动态内存分配使用 newdelete 运算符,允许程序在运行时分配和释放内存。使用动态内存分配可以创建可变大小的数据结构。

1.1. 示例

class MyArray {
private:
    int* arr;
    int size;

public:
    MyArray(int s) : size(s) {
        arr = new int[size]; // 动态分配内存
    }

    ~MyArray() {
        delete[] arr; // 释放内存
    }
};

2. 隐式和显式复制构造函数

2.1. 隐式复制构造函数

当类的对象被传递给函数或作为返回值时,编译器会自动生成隐式复制构造函数。

class Sample {
public:
    int* data;

    Sample(int value) {
        data = new int(value);
    }

    // 隐式复制构造函数
};

void func(Sample s) {
    // 使用隐式复制构造函数
}

2.2. 显式复制构造函数

显式定义复制构造函数可以控制如何复制对象,包括深拷贝和浅拷贝。

class Sample {
public:
    int* data;

    Sample(int value) {
        data = new int(value);
    }

    // 显式复制构造函数
    Sample(const Sample& other) {
        data = new int(*other.data); // 深拷贝
    }

    ~Sample() {
        delete data; // 释放内存
    }
};

3. 隐式和显式重载赋值运算符

3.1. 隐式重载赋值运算符

编译器会提供一个默认的赋值运算符,适合简单的成员变量赋值。

class Sample {
public:
    int* data;
    // 默认赋值运算符
};

3.2. 显式重载赋值运算符

显式重载赋值运算符可以实现自定义赋值行为,尤其在处理动态内存时。

class Sample {
public:
    int* data;

    Sample(int value) {
        data = new int(value);
    }

    // 显式重载赋值运算符
    Sample& operator=(const Sample& other) {
        if (this == &other) return *this; // 自我赋值检测
        delete data; // 释放旧内存
        data = new int(*other.data); // 深拷贝
        return *this;
    }

    ~Sample() {
        delete data; // 释放内存
    }
};

4. 在构造函数中使用 new

在构造函数中可以使用 new 动态分配内存,以便根据需要初始化对象的成员变量。

class Node {
public:
    int value;
    Node* next;

    Node(int val) : value(val), next(nullptr) {
        // 使用 new 创建新的节点
    }
};

5. 使用静态类成员

静态成员变量是属于类而非特定对象的成员,可以用于共享数据。

class Counter {
public:
    static int count; // 静态成员变量

    Counter() {
        count++; // 每创建一个对象,计数加一
    }
};

int Counter::count = 0; // 静态成员的定义

6. 将定位 new 运算符用于对象

定位 new 运算符允许在指定的内存地址分配对象,这在自定义内存管理时非常有用。

class Sample {
public:
    int value;

    Sample(int val) : value(val) {}
};

int main() {
    char* buffer = new char[sizeof(Sample)]; // 分配足够的内存
    Sample* obj = new (buffer) Sample(10); // 定位 new
    obj->~Sample(); // 显式调用析构函数
    delete[] buffer; // 释放内存
}

7. 使用指向对象的指针

指向对象的指针允许管理动态分配的对象,提高灵活性。

class Student {
public:
    string name;

    Student(string n) : name(n) {}
};

int main() {
    Student* student = new Student("John Doe");
    cout << student->name << endl; // 访问对象成员
    delete student; // 释放内存
}

8. 实现队列抽象数据类型(ADT)

队列是一种常见的抽象数据类型,遵循先进先出(FIFO)原则。可以使用链表或数组实现。

8.1. 使用链表实现队列

class Node {
public:
    int data;
    Node* next;

    Node(int val) : data(val), next(nullptr) {}
};

class Queue {
private:
    Node* front;
    Node* rear;

public:
    Queue() : front(nullptr), rear(nullptr) {}

    void enqueue(int value) {
        Node* newNode = new Node(value);
        if (rear) {
            rear->next = newNode;
        } else {
            front = newNode; // 如果队列为空,front也指向新节点
        }
        rear = newNode; // 更新后端
    }

    int dequeue() {
        if (!front) throw std::out_of_range("Queue is empty");
        int value = front->data;
        Node* temp = front;
        front = front->next;
        delete temp; // 释放内存
        if (!front) rear = nullptr; // 如果队列变为空,更新rear
        return value;
    }

    ~Queue() {
        while (front) {
            dequeue(); // 释放所有节点
        }
    }
};

总结

本章介绍了C++中类和动态内存分配的高级特性,包括动态内存的使用、隐式和显式的复制构造函数与赋值运算符、构造函数中使用 new、静态类成员、定位 new 运算符,以及如何实现队列抽象数据类型。这些特性为有效的内存管理和数据结构的实现提供了基础。

C++ 类继承

在C++中,继承是实现代码重用和构建层次结构的一种强大机制。继承允许一个类(派生类)基于另一个类(基类)创建,并且可以扩展或修改基类的功能。本章将深入探讨类继承的各种概念,包括is-a关系的继承、公有继承、保护访问、构造函数成员初始化列表、类型转换、虚成员函数、联编、抽象基类和纯虚函数。

1. is-a关系的继承

继承体现了"is-a"关系,即派生类是一种基类的特殊类型。例如,如果有一个 Animal 类,Dog 类可以继承自 Animal,表示“狗是一种动物”。

class Animal {
public:
    void speak() {
        cout << "Animal speaks!" << endl;
    }
};

class Dog : public Animal { // 公有继承
public:
    void bark() {
        cout << "Dog barks!" << endl;
    }
};

int main() {
    Dog dog;
    dog.speak(); // 继承自Animal
    dog.bark();  // Dog自己的方法
}

2. 以公有方式从一个类派生出另一个类

公有继承是最常见的继承方式,派生类可以访问基类的公有和保护成员,但无法访问基类的私有成员。

class Base {
protected:
    int protectedVar;

public:
    Base() : protectedVar(10) {}
};

class Derived : public Base {
public:
    void show() {
        cout << "Protected variable: " << protectedVar << endl; // 访问基类的保护成员
    }
};

3. 保护访问

保护成员在派生类中可访问,但在类外部不可访问。这在基类中使用保护成员时特别有用。

class Base {
protected:
    int protectedVar;

public:
    Base() : protectedVar(10) {}
};

class Derived : public Base {
public:
    void modify() {
        protectedVar = 20; // 可访问
    }
};

4. 构造函数成员初始化列表

在派生类的构造函数中,基类的构造函数会首先被调用,可以使用成员初始化列表来初始化基类的成员。

class Base {
public:
    Base(int val) {
        cout << "Base initialized with " << val << endl;
    }
};

class Derived : public Base {
public:
    Derived(int val) : Base(val) { // 使用成员初始化列表
        cout << "Derived initialized" << endl;
    }
};

5. 向上和向下强制转化

5.1. 向上转化(Upcasting)

向上转化是将派生类指针转换为基类指针,通常是安全的。

Derived* d = new Derived(5);
Base* b = d; // 向上转化

5.2. 向下转化(Downcasting)

向下转化是将基类指针转换为派生类指针,可能会导致不安全的操作。应使用 dynamic_cast 来确保安全性。

Base* b = new Derived(5);
Derived* d = dynamic_cast<Derived*>(b); // 向下转化,安全检查
if (d) {
    cout << "Downcast successful" << endl;
}

6. 虚成员函数

虚成员函数允许通过基类指针或引用调用派生类的重写方法,支持多态性。

class Base {
public:
    virtual void show() { // 虚函数
        cout << "Base show" << endl;
    }
};

class Derived : public Base {
public:
    void show() override { // 重写
        cout << "Derived show" << endl;
    }
};

void display(Base* b) {
    b->show(); // 动态绑定
}

int main() {
    Base b;
    Derived d;
    display(&b); // 输出: Base show
    display(&d); // 输出: Derived show
}

7. 早期联编与晚期联编

7.1. 早期联编(静态联编)

静态联编在编译时确定调用哪个函数,使用非虚函数。

7.2. 晚期联编(动态联编)

动态联编在运行时确定调用哪个函数,使用虚函数。

class Base {
public:
    void show() { // 早期联编
        cout << "Base show" << endl;
    }
    virtual void display() { // 晚期联编
        cout << "Base display" << endl;
    }
};

8. 抽象基类

抽象基类是至少包含一个纯虚函数的类,不能被实例化。它提供了接口供派生类实现。

class AbstractBase {
public:
    virtual void pureVirtualFunction() = 0; // 纯虚函数
};

class ConcreteDerived : public AbstractBase {
public:
    void pureVirtualFunction() override {
        cout << "Implemented pure virtual function" << endl;
    }
};

9. 纯虚函数

纯虚函数是没有实现的虚函数,派生类必须提供实现才能实例化。

class Shape {
public:
    virtual void draw() = 0; // 纯虚函数
};

class Circle : public Shape {
public:
    void draw() override {
        cout << "Drawing Circle" << endl;
    }
};

10. 何时及如何使用公有继承

公有继承适用于派生类是基类的一种特化,且需要访问基类的公有或保护成员。使用公有继承时要确保:

  • 派生类确实是基类的一个子类型(is-a关系)。
  • 需要共享基类的接口和实现。

总结

本章详细讨论了类继承的关键概念,包括is-a关系、公有继承、保护访问、构造函数成员初始化列表、类型转换、虚成员函数、联编方式、抽象基类和纯虚函数。这些特性为C++提供了强大的面向对象编程能力,使得代码更具可重用性和可扩展性。

C++ 中的代码重用

代码重用是软件开发中的重要原则,C++提供了多种机制来实现这一目标,包括has-a关系、包含对象成员的类、模板类、继承和类模板等。本章将详细探讨这些概念,以帮助你更好地理解如何在C++中实现代码重用。

1. has-a关系

has-a关系(组合)表示一个类包含另一个类的对象。这种关系通常用于构建复杂对象,以使得代码更加模块化和可重用。

class Engine {
public:
    void start() {
        cout << "Engine starting..." << endl;
    }
};

class Car {
private:
    Engine engine; // has-a关系

public:
    void start() {
        engine.start(); // 调用Engine的方法
        cout << "Car is ready to go!" << endl;
    }
};

2. 包含对象成员的类

在类中包含其他类的对象,可以使用这些对象的方法和属性。这种方式实现了代码的重用和分离。

class Wheel {
public:
    void rotate() {
        cout << "Wheel is rotating." << endl;
    }
};

class Bicycle {
private:
    Wheel frontWheel; // 包含对象成员
    Wheel rearWheel;

public:
    void ride() {
        frontWheel.rotate(); // 使用包含的对象
        rearWheel.rotate();
        cout << "Bicycle is moving!" << endl;
    }
};

3. 模板类 valarray

valarray 是一个模板类,专为处理数组而设计,支持高效的元素级操作。它允许在编译时指定元素类型,并为数学运算提供了内置支持。

#include <valarray>

template <typename T>
class MyValArray {
private:
    std::valarray<T> arr;

public:
    MyValArray(T* data, size_t size) : arr(data, size) {}

    void print() {
        for (size_t i = 0; i < arr.size(); i++) {
            std::cout << arr[i] << " ";
        }
        std::cout << std::endl;
    }
};

int main() {
    int data[] = {1, 2, 3, 4, 5};
    MyValArray<int> myArray(data, 5);
    myArray.print();
}

4. 私有和保护继承

4.1. 私有继承

私有继承表示派生类无法访问基类的公有和保护成员,基类的成员在派生类中变为私有。

class Base {
public:
    void display() {
        cout << "Base display" << endl;
    }
};

class Derived : private Base { // 私有继承
public:
    void show() {
        // display(); // 不能访问Base的公有成员
    }
};

4.2. 保护继承

保护继承允许派生类访问基类的公有和保护成员,但在类外不可访问。

class Base {
protected:
    void display() {
        cout << "Base display" << endl;
    }
};

class Derived : protected Base { // 保护继承
public:
    void show() {
        display(); // 可以访问Base的保护成员
    }
};

5. 多重继承

多重继承允许一个类从多个基类继承特性。这提供了灵活性,但也可能导致复杂性,如菱形继承问题。

class A {
public:
    void show() {
        cout << "Class A" << endl;
    }
};

class B {
public:
    void display() {
        cout << "Class B" << endl;
    }
};

class C : public A, public B { // 多重继承
public:
    void info() {
        show();
        display();
    }
};

6. 虚基类

虚基类用于解决多重继承中的菱形继承问题。通过使用虚基类,可以确保共享的基类只存在一份。

class A {
public:
    void show() {
        cout << "Class A" << endl;
    }
};

class B : virtual public A { // 虚基类
};

class C : virtual public A { // 虚基类
};

class D : public B, public C {
public:
    void info() {
        show(); // 共享A的实例
    }
};

7. 创建类模板

类模板允许我们定义一个类,该类可以处理不同数据类型。可以在类声明中使用类型参数。

template <typename T>
class Pair {
private:
    T first;
    T second;

public:
    Pair(T a, T b) : first(a), second(b) {}

    T getFirst() {
        return first;
    }

    T getSecond() {
        return second;
    }
};

8. 使用类模板

创建类模板后,可以使用不同的类型来实例化模板类。

int main() {
    Pair<int> intPair(1, 2);
    cout << "First: " << intPair.getFirst() << ", Second: " << intPair.getSecond() << endl;

    Pair<double> doublePair(1.1, 2.2);
    cout << "First: " << doublePair.getFirst() << ", Second: " << doublePair.getSecond() << endl;
}

9. 模板的具体化

模板的具体化允许为特定类型提供定制的实现。可以显式定义特定类型的模板版本。

template <>
class Pair<string> { // 针对string的具体化
private:
    string first;
    string second;

public:
    Pair(string a, string b) : first(a), second(b) {}

    string concat() {
        return first + " " + second; // 特定实现
    }
};

int main() {
    Pair<string> stringPair("Hello", "World");
    cout << stringPair.concat() << endl; // 输出: Hello World
}

总结

本章探讨了C++中的代码重用机制,包括has-a关系、包含对象成员的类、模板类 valarray、私有和保护继承、多重继承、虚基类、创建类模板、使用类模板以及模板的具体化。这些特性为编写可重用和可扩展的代码提供了基础,帮助开发更复杂的应用程序。

友元、异常和其他

C++ 中的友元、异常处理以及类型转换机制为程序员提供了强大的工具,帮助实现灵活的代码设计和错误管理。本章将详细探讨这些概念,包括友元类、友元类方法、嵌套类、异常处理机制以及类型转换操作符。

1. 友元类

友元类可以访问另一个类的私有和保护成员。通过将一个类声明为另一个类的友元类,后者可以访问前者的所有成员。

class ClassB; // 前向声明

class ClassA {
private:
    int data;

public:
    ClassA(int val) : data(val) {}
    friend class ClassB; // ClassB 是 ClassA 的友元类
};

class ClassB {
public:
    void display(ClassA& a) {
        cout << "ClassA's data: " << a.data << endl; // 访问 ClassA 的私有成员
    }
};

2. 友元类方法

友元方法是一个特定的成员函数,可以访问其友元类的私有和保护成员。友元方法可以是任何函数,无论是在类内部还是外部。

class ClassC {
private:
    int value;

public:
    ClassC(int val) : value(val) {}
    friend void showValue(ClassC& c); // 友元函数
};

void showValue(ClassC& c) {
    cout << "Value: " << c.value << endl; // 访问 ClassC 的私有成员
}

3. 嵌套类

嵌套类是一个类定义在另一个类内部。嵌套类可以访问外部类的私有和保护成员。

class Outer {
private:
    int outerData;

public:
    Outer(int val) : outerData(val) {}

    class Inner { // 嵌套类
    public:
        void display(Outer& o) {
            cout << "Outer data: " << o.outerData << endl; // 访问 Outer 的私有成员
        }
    };
};

4. 引发异常、try 块和 catch 块

C++ 提供了一种强大的异常处理机制。可以使用 try 块来捕捉可能引发异常的代码,使用 catch 块来处理这些异常。

void riskyFunction() {
    throw runtime_error("Something went wrong!"); // 引发异常
}

int main() {
    try {
        riskyFunction(); // 可能引发异常
    } catch (const runtime_error& e) {
        cout << "Caught an exception: " << e.what() << endl; // 处理异常
    }
}

5. 异常类

异常类是用于处理特定错误的类,通常从 std::exception 继承。可以创建自定义异常类,以提供更具体的错误信息。

class MyException : public std::exception {
public:
    const char* what() const noexcept override {
        return "My custom exception occurred!";
    }
};

void anotherRiskyFunction() {
    throw MyException(); // 引发自定义异常
}

int main() {
    try {
        anotherRiskyFunction();
    } catch (const MyException& e) {
        cout << "Caught: " << e.what() << endl; // 处理自定义异常
    }
}

6. 运行阶段类型识别

运行阶段类型识别(RTTI)允许程序在运行时识别对象的类型。C++ 提供了 dynamic_cast 和其他转换操作符来实现这一功能。

6.1. dynamic_cast

dynamic_cast 用于安全地将基类指针或引用转换为派生类指针或引用。如果转换失败,返回 nullptr

class Base {
public:
    virtual ~Base() {}
};

class Derived : public Base {};

void func(Base* b) {
    if (Derived* d = dynamic_cast<Derived*>(b)) { // 安全转换
        cout << "Successfully cast to Derived." << endl;
    } else {
        cout << "Cast failed." << endl;
    }
}

6.2. const_cast

const_cast 用于添加或去除 const 属性。它可以安全地转换指针或引用的常量性。

void printValue(const int* ptr) {
    int* modifiablePtr = const_cast<int*>(ptr); // 去除 const 属性
    *modifiablePtr = 100; // 修改原值
}

int main() {
    int x = 42;
    printValue(&x);
    cout << "Modified value: " << x << endl; // 输出 100
}

6.3. reinterpret_cast

reinterpret_cast 是一种低级别的类型转换,通常用于将一个指针类型转换为另一个完全不同的指针类型。使用时要谨慎,因为这可能会导致未定义行为。

void* ptr = malloc(sizeof(int));
int* intPtr = reinterpret_cast<int*>(ptr); // 强制类型转换
*intPtr = 42;
cout << "Value: " << *intPtr << endl; // 输出 42
free(ptr);

总结

本章讨论了友元、异常处理机制以及运行阶段类型识别的关键概念。通过使用友元类和方法,可以实现灵活的类设计;通过异常处理,可以有效管理程序中的错误;通过 RTTI,可以在运行时安全地处理对象类型。这些特性使 C++ 在面向对象编程和错误处理方面具有强大的能力。

string 类和标准模板库

C++ 提供了强大的字符串处理功能以及标准模板库(STL),它包含各种容器、算法和工具,极大地提高了编程的效率和灵活性。本章将深入探讨标准 C++ string 类、智能指针、STL 及其各种组成部分。

1. 标准 C++ string 类

C++ 中的 string 类是处理字符串的标准方式。它提供了丰富的成员函数,用于字符串的创建、操作和管理。

1.1. 创建和初始化

#include <iostream>
#include <string>

int main() {
    std::string str1; // 默认构造
    std::string str2("Hello, World!"); // 使用 C 风格字符串初始化
    std::string str3(str2); // 拷贝构造

    std::cout << str2 << std::endl; // 输出: Hello, World!
}

1.2. 字符串操作

string 类提供了多种操作,例如连接、查找和替换。

std::string str = "Hello";
str += ", World!"; // 字符串连接
std::cout << str << std::endl; // 输出: Hello, World!

size_t pos = str.find("World");
if (pos != std::string::npos) {
    str.replace(pos, 5, "C++"); // 替换字符串
}
std::cout << str << std::endl; // 输出: Hello, C++!

2. 模板 auto_ptrunique_ptrshared_ptr

C++ 提供了多种智能指针以帮助管理动态内存,避免内存泄漏和指针悬空的问题。

2.1. auto_ptr (已废弃)

auto_ptr 是一种智能指针,用于自动管理对象的生命周期,但由于其所有权转移的特性,C++11 已经弃用它。

2.2. unique_ptr

unique_ptr 是一个独占所有权的智能指针,只能有一个指针指向某个对象,确保了内存的自动释放。

#include <memory>

void func() {
    std::unique_ptr<int> ptr(new int(42)); // 创建 unique_ptr
    std::cout << *ptr << std::endl; // 输出: 42
} // 自动释放内存

2.3. shared_ptr

shared_ptr 允许多个指针共享同一个对象的所有权,使用引用计数来管理对象的生命周期。

#include <memory>

void sharedFunc() {
    std::shared_ptr<int> ptr1(new int(42)); // 创建 shared_ptr
    {
        std::shared_ptr<int> ptr2 = ptr1; // 共享所有权
        std::cout << *ptr2 << std::endl; // 输出: 42
    } // ptr2 超出范围,ptr1 仍然有效
}

3. 标准模板库(STL)

STL 是 C++ 的一个重要组成部分,提供了一套丰富的模板类和算法,用于数据结构和算法的高效实现。

3.1. 容器类

STL 包含多种容器类,例如 vectorlistsetmap,用于存储数据。

#include <vector>
#include <iostream>

std::vector<int> vec = {1, 2, 3, 4, 5};
vec.push_back(6); // 添加元素
for (int v : vec) {
    std::cout << v << " "; // 输出: 1 2 3 4 5 6
}

3.2. 迭代器

迭代器是访问容器元素的对象,支持遍历容器的功能。

#include <vector>
#include <iostream>

std::vector<int> vec = {1, 2, 3, 4, 5};
for (auto it = vec.begin(); it != vec.end(); ++it) {
    std::cout << *it << " "; // 输出: 1 2 3 4 5
}

3.3. 函数对象(functor)

函数对象是一个重载了 operator() 的对象,可以像函数一样被调用。它们可以用于 STL 算法中。

#include <iostream>
#include <algorithm>
#include <vector>

class Adder {
public:
    int operator()(int a, int b) {
        return a + b; // 定义加法操作
    }
};

int main() {
    Adder add;
    std::cout << add(3, 4) << std::endl; // 输出: 7
}

4. STL 算法

STL 提供了许多通用算法,如排序、查找和修改等。

#include <algorithm>
#include <vector>
#include <iostream>

std::vector<int> vec = {5, 3, 2, 1, 4};
std::sort(vec.begin(), vec.end()); // 排序
for (int v : vec) {
    std::cout << v << " "; // 输出: 1 2 3 4 5
}

5. 模板 initializer_list

initializer_list 允许使用花括号 {} 初始化容器或其他数据结构,简化了代码。

#include <vector>
#include <iostream>

void printVector(std::initializer_list<int> list) {
    for (int v : list) {
        std::cout << v << " "; // 输出: 1 2 3 4
    }
}

int main() {
    printVector({1, 2, 3, 4});
}

总结

本章介绍了 C++ 的 string 类、智能指针、标准模板库及其主要组成部分。理解这些概念有助于编写更安全、更高效的代码,并利用 STL 提供的丰富功能,简化日常编程任务。

输入、输出和文件

C++ 提供了强大的输入和输出机制,使得与用户和文件的交互变得简单而灵活。本章将探讨 C++ 中的输入和输出,包括 iostream 类系列、文件操作及格式化等。

1. C++ 角度的输入和输出

C++ 的输入和输出主要通过流(streams)来实现。流是数据传输的抽象,可以是输入流(如键盘输入)或输出流(如屏幕输出)。

2. iostream 类系列

C++ 提供了几个主要的流类,包括:

  • istream:用于输入操作。
  • ostream:用于输出操作。
  • ifstream:用于从文件输入。
  • ofstream:用于输出到文件。
  • fstream:用于文件的输入和输出。

2.1. 基本使用示例

#include <iostream>
#include <fstream>

int main() {
    std::cout << "Hello, World!" << std::endl; // 使用 ostream 输出
    return 0;
}

3. 重定向

C++ 允许将输入输出流重定向。例如,可以将输出重定向到文件而不是控制台。

std::ofstream out("output.txt");
std::cout.rdbuf(out.rdbuf()); // 重定向 cout 到文件
std::cout << "This will be written to the file." << std::endl;

4. ostream 类方法

ostream 类提供了多种方法来控制输出,包括:

  • std::endl:输出换行并刷新缓冲区。
  • setprecision():设置浮点数输出精度。
  • setw():设置输出宽度。
#include <iomanip>

std::cout << std::setprecision(2) << 3.14159 << std::endl; // 输出: 3.14

5. 格式化输出

C++ 允许使用多种格式化方法来控制输出的格式。例如,可以使用 std::fixedstd::scientific 来控制浮点数的输出格式。

std::cout << std::fixed << std::setprecision(2) << 3.14159 << std::endl; // 输出: 3.14
std::cout << std::scientific << 3.14159 << std::endl; // 输出: 3.141590e+00

6. istream 类方法

istream 类用于输入操作,提供的方法包括:

  • std::cin:标准输入流。
  • getline():从输入流读取一整行。
  • >> 运算符:从输入流提取数据。
std::string name;
std::cout << "Enter your name: ";
std::getline(std::cin, name); // 读取整行
std::cout << "Hello, " << name << "!" << std::endl;

7. 流状态

流状态可以用于检查输入输出操作是否成功。主要状态标志包括:

  • eofbit:到达文件尾。
  • failbit:输入操作失败。
  • badbit:发生严重错误。
if (std::cin.fail()) {
    std::cout << "Input failed!" << std::endl;
}

8. 文件 I/O

C++ 提供了简单的文件输入输出接口,可以使用 ifstreamofstream 进行文件操作。

8.1. 使用 ifstream 类从文件输入

#include <fstream>
#include <iostream>

std::ifstream inputFile("input.txt");
std::string line;
while (std::getline(inputFile, line)) {
    std::cout << line << std::endl; // 输出文件中的每一行
}
inputFile.close();

8.2. 使用 ofstream 类输出到文件

#include <fstream>

std::ofstream outputFile("output.txt");
outputFile << "This is a line in a file." << std::endl;
outputFile.close();

9. 使用 fstream 类进行文件输入和输出

fstream 类可用于同时进行文件的输入和输出。

#include <fstream>

std::fstream file("data.txt", std::ios::in | std::ios::out | std::ios::app);
file << "Appending this line." << std::endl; // 添加内容
file.seekg(0); // 回到文件开头
std::string line;
while (std::getline(file, line)) {
    std::cout << line << std::endl; // 读取并输出文件内容
}
file.close();

10. 命令行处理

C++ 支持通过命令行参数传递数据。在 main 函数中,可以接收命令行参数。

int main(int argc, char* argv[]) {
    for (int i = 0; i < argc; ++i) {
        std::cout << "Argument " << i << ": " << argv[i] << std::endl;
    }
}

11. 二进制文件

C++ 允许以二进制模式打开文件,通过设置 std::ios::binary 可以进行二进制文件的读写。

std::ofstream binFile("data.bin", std::ios::binary);
int num = 42;
binFile.write(reinterpret_cast<char*>(&num), sizeof(num)); // 写入整数
binFile.close();

12. 随机文件访问

可以使用 seekg()seekp() 方法实现对文件的随机访问。

std::fstream randomFile("data.bin", std::ios::in | std::ios::out | std::ios::binary);
randomFile.seekp(0); // 定位到文件开头
randomFile.write(reinterpret_cast<char*>(&num), sizeof(num)); // 写入整数
randomFile.seekg(0); // 定位到文件开头
randomFile.read(reinterpret_cast<char*>(&num), sizeof(num)); // 读取整数
std::cout << "Number: " << num << std::endl; // 输出: 42
randomFile.close();

13. 内核格式化

C++ 中并没有直接涉及内核格式化的内容,但通常可以使用标准的输入输出流进行格式化,或使用系统调用处理更低级别的文件操作。

总结

本章介绍了 C++ 中的输入、输出和文件操作,包括 iostream 类系列、文件 I/O、格式化输出等。掌握这些概念能够帮助开发者有效地与用户和文件进行交互,处理复杂的数据输入输出需求。

附录

在本附录中,我们将通过表格展示 C++ 的关键字、替代标记、ASCII 字符集以及运算符优先级,方便读者查阅和理解。

1. C++ 关键字

关键字 含义
auto 自动类型推导
break 跳出循环或 switch 语句
case switch 语句中的一个分支
catch 捕获异常块
class 定义一个类
const 定义常量或不允许修改的变量
continue 跳过当前循环迭代,继续下一次循环
default switch 语句的默认分支
delete 释放动态分配的内存
do do-while 循环的开始
else if 语句的否定分支
enum 定义枚举类型
extern 声明外部变量
float 定义浮点数类型
for for 循环的开始
if 条件语句的开始
include 包含头文件
int 定义整数类型
return 从函数返回
struct 定义结构
void 表示无返回值或空类型
while while 循环的开始

2. 替代标记(运算符的字母替代)及含义

替代标记 运算符 含义
+ plus 加法
- minus 减法
* multiply 乘法
/ divide 除法
% modulus 取模
== equal 等于
!= not_equal 不等于
< less_than 小于
> greater_than 大于
<= less_equal 小于或等于
>= greater_equal 大于或等于
&& and 逻辑与
` `
! not 逻辑非

3. ASCII 字符集

ASCII 字符集

十进制 十六进制 字符 描述
0 00 NUL 空字符
1 01 SOH 标题开始
2 02 STX 正文开始
3 03 ETX 正文结束
4 04 EOT 传输结束
5 05 ENQ 查询
6 06 ACK 确认
7 07 BEL 响铃
8 08 BS 退格
9 09 TAB 水平制表符
10 0A LF 换行
11 0B VT 垂直制表符
12 0C FF 换页
13 0D CR 回车
14 0E SO 换出
15 0F SI 换入
16 10 DLE 数据链路转义
17 11 DC1 设备控制 1
18 12 DC2 设备控制 2
19 13 DC3 设备控制 3
20 14 DC4 设备控制 4
21 15 NAK 拒绝
22 16 SYN 同步
23 17 ETB 传输块结束
24 18 CAN 取消
25 19 EM 媒体结束
26 1A SUB 替代
27 1B ESC 转义字符
28 1C FS 文件分隔符
29 1D GS 组分隔符
30 1E RS 记录分隔符
31 1F US 单元分隔符
32 20 (space) 空格
33 21 ! 感叹号
34 22 " 双引号
35 23 # 井号
36 24 $ 美元符号
37 25 % 百分号
38 26 & 和号
39 27 ' 单引号
40 28 ( 左括号
41 29 ) 右括号
42 2A * 星号
43 2B + 加号
44 2C , 逗号
45 2D - 减号
46 2E . 句号
47 2F / 斜杠
48 30 0 数字 0
49 31 1 数字 1
50 32 2 数字 2
51 33 3 数字 3
52 34 4 数字 4
53 35 5 数字 5
54 36 6 数字 6
55 37 7 数字 7
56 38 8 数字 8
57 39 9 数字 9
58 3A : 冒号
59 3B ; 分号
60 3C < 小于
61 3D = 等于
62 3E > 大于
63 3F ? 问号
64 40 @ At 符号
65 41 A 大写字母 A
66 42 B 大写字母 B
67 43 C 大写字母 C
68 44 D 大写字母 D
69 45 E 大写字母 E
70 46 F 大写字母 F
71 47 G 大写字母 G
72 48 H 大写字母 H
73 49 I 大写字母 I
74 4A J 大写字母 J
75 4B K 大写字母 K
76 4C L 大写字母 L
77 4D M 大写字母 M
78 4E N 大写字母 N
79 4F O 大写字母 O
80 50 P 大写字母 P
81 51 Q 大写字母 Q
82 52 R 大写字母 R
83 53 S 大写字母 S
84 54 T 大写字母 T
85 55 U 大写字母 U
86 56 V 大写字母 V
87 57 W 大写字母 W
88 58 X 大写字母 X
89 59 Y 大写字母 Y
90 5A Z 大写字母 Z
91 5B [ 左方括号
92 5C \ 反斜杠
93 5D ] 右方括号
94 5E ^ 脱字符
95 5F _ 下划线
96 60 ` 反引号
97 61 a 小写字母 a
98 62 b 小写字母 b
99 63 c 小写字母 c
100 64 d 小写字母 d
101 65 e 小写字母 e
102 66 f 小写字母 f
103 67 g 小写字母 g
104 68 h 小写字母 h
105 69 i 小写字母 i
106 6A j 小写字母 j
107 6B k 小写字母 k
108 6C l 小写字母 l
109 6D m 小写字母 m
110 6E n 小写字母 n
111 6F o 小写字母 o
112 70 p 小写字母 p
113 71 q 小写字母 q
114 72 r 小写字母 r
115 73 s 小写字母 s
116 74 t 小写字母 t
117 75 u 小写字母 u
118 76 v 小写字母 v
119 77 w 小写字母 w
120 78 x 小写字母 x
121 79 y 小写字母 y
122 7A z 小写字母 z
123 7B { 左花括号
124 7C | 竖线
125 7D } 右花括号
126 7E ~ 波浪号
127 7F DEL 删除字符

4. 运算符优先级

优先级 运算符 描述
1 () 括号
2 ++, --, +, - 一元运算符
3 *, /, % 乘法、除法、取模
4 +, - 加法、减法
5 <<, >> 位移运算符
6 <, <=, >, >= 比较运算符
7 ==, != 相等与不等
8 & 按位与
9 ^ 按位异或
10 ` `
11 && 逻辑与
12 `
13 ?: 条件运算符
14 = 赋值运算符
15 +=, -=, *=, /=, %= 复合赋值运算符