C++基础:1d1C到C++

[TOC]

1 C到C++

1.1 缺省函数参数

在C++中,缺省函数参数(Default Function Arguments)允许你在定义函数时为某些参数指定默认值。如果调用函数时没有为这些参数提供值,编译器会自动使用默认值。这使得函数调用更加灵活,减少了需要编写的重载函数数量。

在函数声明或定义中,可以为参数指定默认值。语法如下:

1
返回类型 函数名(参数类型 参数名 = 默认值);

以下是一个简单的示例,展示了如何使用缺省函数参数:

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

// 函数声明,带有缺省参数
void printMessage(const std::string& message = "Hello, World!");

int main() {
printMessage(); // 使用默认参数
printMessage("Custom Message"); // 提供自定义参数
return 0;
}

// 函数定义
void printMessage(const std::string& message) {
std::cout << message << std::endl;
}
1
2
Hello, World!
Custom Message

1.1.1 注意事项

  1. 顺序:缺省参数必须从右向左依次指定。也就是说,如果一个参数有默认值,那么它右边的所有参数也必须有默认值。

    1
    2
    void func(int a, int b = 10, int c = 20); // 合法
    void func(int a = 10, int b, int c = 20); // 非法
  2. 声明与定义:如果函数在声明时指定了缺省参数,那么在定义时不应再指定缺省参数,否则会导致编译错误。

    1
    2
    3
    4
    5
    6
    7
    // 声明
    void func(int a = 10);

    // 定义
    void func(int a) {
    // 函数体
    }
  3. 重载函数:缺省参数可以与函数重载一起使用,但需要注意避免歧义。

    1
    2
    void func(int a);
    void func(int a, int b = 10); // 可能导致歧义
  4. 指针和引用:缺省参数可以是常量、变量、表达式、函数调用等,但不能是局部变量。

    1
    2
    int globalVar = 10;
    void func(int a = globalVar); // 合法

1.2 函数重载

在C++中,函数重载(Function Overloading) 允许你在同一个作用域内定义多个同名函数,只要它们的参数列表(参数的类型、数量或顺序)不同即可。函数重载是C++支持多态性的一种方式,它使得你可以用同一个函数名处理不同类型或数量的输入。

  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
#include <iostream>
using namespace std;

// 重载函数:处理整数
void print(int value) {
cout << "Integer: " << value << endl;
}

// 重载函数:处理浮点数
void print(double value) {
cout << "Double: " << value << endl;
}

// 重载函数:处理两个整数
void print(int a, int b) {
cout << "Two Integers: " << a << ", " << b << endl;
}

int main() {
print(10); // 调用 void print(int)
print(3.14); // 调用 void print(double)
print(5, 7); // 调用 void print(int, int)
return 0;
}
1
2
3
Integer: 10
Double: 3.14
Two Integers: 5, 7

1.2.1 函数重载的工作原理

当调用一个重载函数时,编译器会根据以下规则确定调用哪个函数:

  1. 精确匹配:如果参数类型完全匹配,则调用对应的函数。
  2. 类型转换:如果没有精确匹配,编译器会尝试进行隐式类型转换(如 intdouble),并调用最合适的函数。
  3. 歧义检测(二异性问题):如果存在多个可能的匹配,或者没有匹配的函数,编译器会报错。

1.2.2 注意事项

  1. 返回类型不影响重载

    1
    2
    int func(int a);
    double func(int a); // 错误:仅返回类型不同,不能构成重载
  2. 默认参数可能导致歧义

    1
    2
    3
    4
    void func(int a);
    void func(int a, int b = 10);

    func(5); // 歧义:编译器无法确定调用哪个函数
  3. 作用域规则

    • 如果在一个作用域内定义了重载函数,而在另一个作用域内定义了同名函数,则后者会隐藏前者。
    • 例如,在类中定义的重载函数会隐藏全局作用域中的同名函数。
  4. 重载与模板

    • 函数模板也可以与重载结合使用,但需要确保模板实例化后不会导致歧义。

1.3 命名空间

在C++中,命名空间(Namespace) 是一种用于组织代码的机制,它可以避免命名冲突,尤其是在大型项目或使用多个第三方库时。命名空间将全局作用域划分为不同的区域,每个区域内的标识符(如变量、函数、类等)具有唯一性,但在不同命名空间中可以存在同名的标识符。

  1. 避免命名冲突:不同命名空间中的同名标识符不会冲突。
  2. 组织代码:将相关的代码组织在一起,提高代码的可读性和可维护性。
  3. 支持模块化编程:命名空间可以将代码划分为逻辑模块。

1.3.1 定义命名空间

使用 namespace 关键字定义命名空间:

1
2
3
namespace 命名空间名 {
// 变量、函数、类等的定义
}

示例

1
2
3
4
5
6
namespace MyNamespace {
int value = 10;
void print() {
std::cout << "Value: " << value << std::endl;
}
}

1.3.2 访问命名空间中的成员

使用作用域解析运算符 :: 访问命名空间中的成员:

1
命名空间名::成员名

示例

1
2
3
4
5
int main() {
std::cout << MyNamespace::value << std::endl; // 访问变量
MyNamespace::print(); // 调用函数
return 0;
}

1.3.3 命名空间的嵌套

命名空间可以嵌套定义,形成层次化的结构:

1
2
3
4
5
6
7
namespace Outer {
int outerValue = 10;

namespace Inner {
int innerValue = 20;
}
}

访问嵌套命名空间中的成员:

1
2
3
4
5
int main() {
std::cout << Outer::outerValue << std::endl; // 访问外层命名空间
std::cout << Outer::Inner::innerValue << std::endl; // 访问内层命名空间
return 0;
}

1.3.4 使用 using 指令

为了避免每次访问命名空间成员时都写完整的命名空间路径,可以使用 using 指令。

1.3.4.1 . using namespace 指令

引入整个命名空间,使得该命名空间中的所有成员可以直接使用:

1
using namespace 命名空间名;

示例

1
2
3
4
5
6
7
using namespace MyNamespace;

int main() {
std::cout << value << std::endl; // 直接访问
print(); // 直接调用
return 0;
}
1.3.4.2 . using 声明

引入命名空间中的特定成员,使得该成员可以直接使用:

1
using 命名空间名::成员名;

示例

1
2
3
4
5
6
7
using MyNamespace::value;

int main() {
std::cout << value << std::endl; // 直接访问
MyNamespace::print(); // 仍需使用命名空间
return 0;
}

1.3.5 匿名命名空间

匿名命名空间是一个没有名字的命名空间,其成员的作用域仅限于当前文件(类似于 static 关键字的作用)。

示例

1
2
3
4
5
6
7
8
namespace {
int localValue = 30; // 仅在当前文件中可见
}

int main() {
std::cout << localValue << std::endl;
return 0;
}

1.3.6 命名空间与全局作用域

如果命名空间中的成员与全局作用域中的成员同名,可以通过 :: 访问全局作用域的成员:

1
2
3
4
5
6
7
8
9
10
11
int value = 40;

namespace MyNamespace {
int value = 10;
}

int main() {
std::cout << MyNamespace::value << std::endl; // 访问命名空间中的 value
std::cout << ::value << std::endl; // 访问全局作用域中的 value
return 0;
}

1.3.7 命名空间的别名

如果命名空间的名字太长,可以为其定义别名:

1
namespace 别名 = 命名空间名;

示例

1
2
3
4
5
6
7
8
9
10
namespace VeryLongNamespaceName {
int value = 50;
}

namespace ShortName = VeryLongNamespaceName;

int main() {
std::cout << ShortName::value << std::endl; // 使用别名访问
return 0;
}

1.3.8 标准命名空间 std

C++ 标准库中的所有内容都定义在 std 命名空间中。例如:

1
2
3
std::cout
std::endl
std::string

通常使用 using namespace std; 来简化代码,但在大型项目中应避免全局引入 std,以防止命名冲突。


1.3.9 总结

命名空间是C++中用于组织代码、避免命名冲突的重要机制。通过合理使用命名空间,可以提高代码的可读性、可维护性和模块化程度。以下是命名空间的核心要点:

  1. 使用 namespace 定义命名空间。
  2. 使用 :: 访问命名空间成员。
  3. 使用 using 指令简化命名空间成员的访问。
  4. 匿名命名空间用于限制作用域。
  5. 命名空间可以嵌套和定义别名。

1.4 标准数据流

在C++中,标准输入输出流 是通过 流(Stream) 机制实现的,它提供了一种方便的方式来处理输入和输出操作。C++ 标准库中的 <iostream> 头文件定义了用于标准输入输出的流类,主要包括:

  • std::cin:标准输入流(通常与键盘输入关联)。
  • std::cout:标准输出流(通常与控制台输出关联)。
  • std::cerr:标准错误流(无缓冲,通常用于错误信息)。
  • std::clog:标准日志流(有缓冲,通常用于日志信息)。

1.4.1 标准输出流 (std::cout)

std::cout 是用于将数据输出到控制台的流对象。它通常与插入运算符 << 一起使用。

1
2
3
4
5
6
7
8
#include <iostream>

int main() {
int num = 42;
std::cout << "Hello, World!" << std::endl; // 输出字符串并换行
std::cout << "The number is: " << num << std::endl; // 输出变量
return 0;
}
1
2
Hello, World!
The number is: 42

说明

  • << 是插入运算符,用于将数据插入到输出流中。
  • std::endl 用于插入换行符并刷新输出缓冲区。

1.4.2 标准输入流 (std::cin)

std::cin 是用于从控制台读取输入的流对象。它通常与提取运算符 >> 一起使用。

示例

1
2
3
4
5
6
7
8
9
#include <iostream>

int main() {
int num;
std::cout << "Enter a number: ";
std::cin >> num; // 从控制台读取输入
std::cout << "You entered: " << num << std::endl;
return 0;
}

输出

1
2
Enter a number: 42
You entered: 42

说明

  • >> 是提取运算符,用于从输入流中提取数据。
  • std::cin 会自动跳过空白字符(如空格、制表符和换行符)。

1.4.3 标准错误流 (std::cerr)

std::cerr 是用于输出错误信息的流对象。它与 std::cout 类似,但通常是无缓冲的,这意味着错误信息会立即输出。

示例

1
2
3
4
5
6
#include <iostream>

int main() {
std::cerr << "An error occurred!" << std::endl;
return 0;
}

输出

1
An error occurred!

1.4.4 标准日志流 (std::clog)

std::clog 是用于输出日志信息的流对象。它与 std::cerr 类似,但通常是有缓冲的。

示例

1
2
3
4
5
6
#include <iostream>

int main() {
std::clog << "This is a log message." << std::endl;
return 0;
}

输出

1
This is a log message.

1.4.5 格式化输出

C++ 提供了多种方式格式化输出,例如设置宽度、精度、填充字符等。

1.4.6 设置宽度和填充字符

使用 std::setwstd::setfill 设置输出宽度和填充字符。

示例

1
2
3
4
5
6
7
8
#include <iostream>
#include <iomanip> // 包含格式化工具

int main() {
int num = 42;
std::cout << std::setw(10) << std::setfill('*') << num << std::endl;
return 0;
}

输出

1
********42

1.4.7 设置浮点数精度

使用 std::setprecision 设置浮点数的输出精度。

示例

1
2
3
4
5
6
7
8
#include <iostream>
#include <iomanip>

int main() {
double pi = 3.141592653589793;
std::cout << std::setprecision(4) << pi << std::endl;
return 0;
}

输出

1
3.142

1.4.8 处理输入错误

在使用 std::cin 时,可能会遇到输入错误(例如输入类型不匹配)。可以通过检查流的状态来处理错误。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>

int main() {
int num;
std::cout << "Enter a number: ";
if (std::cin >> num) {
std::cout << "You entered: " << num << std::endl;
} else {
std::cerr << "Invalid input!" << std::endl;
std::cin.clear(); // 清除错误状态
std::cin.ignore(1000, '\n'); // 忽略错误的输入
}
return 0;
}

1.5 std::string

在C++中,std::string 是一个用于表示和操作字符串的类,定义在 <string> 头文件中。它是C++标准库的一部分,提供了丰富的功能来处理字符串,比C语言中的字符数组(char[])更加安全和方便。


1.5.1 std::string 的基本特性

  1. 动态大小std::string 可以动态调整其大小,无需手动管理内存。
  2. 丰富的成员函数:提供了大量的成员函数来操作字符串,如查找、替换、插入、删除等。
  3. 支持运算符重载:可以使用 +==!= 等运算符来操作字符串。
  4. 安全性:自动处理内存分配和释放,避免了缓冲区溢出等问题。

1.5.2 创建和初始化 std::string

std::string 可以通过多种方式创建和初始化:

  1. 默认构造函数
1
std::string str;
  1. 使用C风格字符串初始化
1
std::string str = "Hello, World!";
  1. 使用部分字符初始化
1
std::string str("Hello, World!", 5); // 只取前5个字符:"Hello"
  1. 使用重复字符初始化
1
std::string str(5, 'A'); // 创建包含5个'A'的字符串:"AAAAA"
  1. 使用另一个 std::string 初始化
1
2
std::string str1 = "Hello";
std::string str2(str1); // 使用 str1 初始化 str2

1.5.3 std::string 的常用操作

  1. 获取字符串长度

使用 size()length() 成员函数:

1
2
3
std::string str = "Hello";
std::cout << str.size() << std::endl; // 输出:5
std::cout << str.length() << std::endl; // 输出:5
  1. 访问字符

使用 [] 运算符或 at() 成员函数:

1
2
3
std::string str = "Hello";
char ch1 = str[0]; // 获取第一个字符 'H'
char ch2 = str.at(1); // 获取第二个字符 'e'
  • [] 不检查索引是否越界。
  • at() 会检查索引,如果越界则抛出 std::out_of_range 异常。
  1. 字符串连接

使用 + 运算符或 append() 成员函数:

1
2
3
4
std::string str1 = "Hello";
std::string str2 = "World";
std::string result = str1 + " " + str2; // "Hello World"
str1.append(" ").append(str2); // str1 变为 "Hello World"
  1. 比较字符串

使用 ==!=<> 等运算符:

1
2
3
4
5
6
7
std::string str1 = "Hello";
std::string str2 = "World";
if (str1 == str2) {
std::cout << "Strings are equal." << std::endl;
} else {
std::cout << "Strings are not equal." << std::endl;
}
  1. 查找子字符串

使用 find() 成员函数:

1
2
3
4
5
6
7
std::string str = "Hello, World!";
size_t pos = str.find("World"); // 返回子字符串的起始位置
if (pos != std::string::npos) {
std::cout << "Found at position: " << pos << std::endl;
} else {
std::cout << "Not found." << std::endl;
}
  1. 提取子字符串

使用 substr() 成员函数:

1
2
std::string str = "Hello, World!";
std::string sub = str.substr(7, 5); // 从位置7开始提取5个字符:"World"
  1. 插入和删除
  • 插入:使用 insert() 成员函数。
  • 删除:使用 erase() 成员函数。
1
2
3
std::string str = "Hello, !";
str.insert(7, "World"); // 插入后:"Hello, World!"
str.erase(5, 2); // 删除后:"HelloWorld!"
  1. 替换子字符串

使用 replace() 成员函数:

1
2
std::string str = "Hello, World!";
str.replace(7, 5, "C++"); // 替换后:"Hello, C++!"
  1. 转换为C风格字符串

使用 c_str()data() 成员函数:

1
2
std::string str = "Hello";
const char* cstr = str.c_str(); // 返回C风格字符串

1.5.4 std::string 的迭代器

std::string 支持迭代器,可以用于遍历字符串中的字符。

示例

1
2
3
4
std::string str = "Hello";
for (auto it = str.begin(); it != str.end(); ++it) {
std::cout << *it << " ";
}

std::string 的迭代器类似于指针,它可以指向字符串中的某个字符,并支持移动(如递增或递减)以访问其他字符。

1.5.5 std::string 的性能

  • 动态内存管理std::string 会自动管理内存,但频繁的字符串操作(如拼接、插入、删除)可能会导致内存重新分配,影响性能。
  • 小字符串优化:许多实现会对小字符串进行优化,避免频繁的内存分配。

std::string 是C++中处理字符串的强大工具,具有以下优点:

  1. 动态大小:无需手动管理内存。
  2. 丰富的功能:提供了大量的成员函数来操作字符串。
  3. 安全性:避免了C风格字符串的常见问题(如缓冲区溢出)。
  4. 易用性:支持运算符重载和迭代器,使用方便。

在C++中,可以通过重定向标准输出流(std::cout)将输出内容写入文件,而不是显示在控制台上。以下是实现这一功能的简单方法:

1.5.6 标准数据流重定向

方法:使用 std::ofstreamstd::cout.rdbuf()

1打开文件:使用 std::ofstream 创建一个文件输出流。

2保存 std::cout 的缓冲区:使用 std::cout.rdbuf() 获取 std::cout 的当前缓冲区。

3重定向 std::cout:将 std::cout 的缓冲区设置为文件输出流的缓冲区。

4恢复 std::cout:在完成文件写入后,将 std::cout 的缓冲区恢复为原来的缓冲区。

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


#include <iostream>
#include <fstream>

int main() {
// 打开文件
std::ofstream file("output.txt");
if (!file.is_open()) {
std::cerr << "Failed to open file!" << std::endl;
return 1;
}

// 保存 std::cout 的缓冲区
std::streambuf* coutBuffer = std::cout.rdbuf();

// 将 std::cout 重定向到文件
std::cout.rdbuf(file.rdbuf());

// 输出到文件
std::cout << "This will be written to the file." << std::endl;
std::cout << "Hello, File!" << std::endl;

// 恢复 std::cout 的缓冲区
std::cout.rdbuf(coutBuffer);

// 输出到控制台
std::cout << "This will be written to the console." << std::endl;

// 关闭文件
file.close();

return 0;
}

代码说明

  1. 打开文件

    • 使用 std::ofstream 创建文件输出流,并打开文件 output.txt
    • 如果文件打开失败,输出错误信息并退出程序。
  2. 保存 std::cout 的缓冲区

    `std::cout.rdbuf()` 获取 `std::cout` 的当前缓冲区,并保存到 `coutBuffer` 中。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    3. **重定向 `std::cout`**:

    - 使用 `std::cout.rdbuf(file.rdbuf())` 将 `std::cout` 的缓冲区设置为文件输出流的缓冲区。
    4. **输出到文件**:

    {0}. 使用 `std::cout` 输出的内容将被写入文件。

    5. **恢复 `std::cout`**:
    - 使用 `std::cout.rdbuf(coutBuffer)` 将 `std::cout` 的缓冲区恢复为原来的缓冲区。

    6. **输出到控制台**:

    - 恢复缓冲区后,`std::cout` 的输出将再次显示在控制台上。
    7. **关闭文件**:
    - 使用 `file.close()` 关闭文件。

    - 文件 `output.txt` 的内容:

    This will be written to the file.
    Hello, File!

    1
    2
    3

    - 控制台的输出:

    This will be written to the console.

    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



    ### `this` 指针

    在C++中,**`this` 指针** 是一个隐含的指针,它指向当前对象的地址。`this` 指针在类的成员函数中自动可用,用于访问当前对象的成员变量和成员函数。它是C++实现面向对象编程的重要机制之一。

    ------

    #### `this` 指针的特性

    1. **隐含指针**:`this` 指针是编译器自动提供的,无需显式声明。
    2. **指向当前对象**:`this` 指向调用成员函数的对象。
    3. **常量指针**:`this` 是一个常量指针,其值不能被修改(即不能指向其他对象)。
    4. **类型**:`this` 的类型是 `ClassName*`(指向当前类类型的指针)。在 `const` 成员函数中,`this` 的类型是 `const ClassName*`。

    ------

    #### `this` 指针的用途

    ##### 1. 区分成员变量和局部变量

    当成员变量与局部变量同名时,可以使用 `this` 指针明确指定访问的是成员变量。

    ```cpp
    #include <iostream>

    class MyClass {
    private:
    int value;

    public:
    void setValue(int value) {
    this->value = value; // 使用 this 指针区分成员变量和参数
    }

    void printValue() {
    std::cout << "Value: " << value << std::endl;
    }
    };

    int main() {
    MyClass obj;
    obj.setValue(42);
    obj.printValue(); // 输出:Value: 42
    return 0;
    }

—-在 setValue 函数中,this->value 明确表示访问的是成员变量 value,而不是参数 value

this 指针的底层实现

this 指针实际上是成员函数的一个隐含参数。当调用成员函数时,编译器会自动将对象的地址作为第一个参数传递给成员函数。

例如,以下成员函数:

1
2
3
void MyClass::setValue(int value) {
this->value = value;
}

在底层会被编译器转换为:

1
2
3
void MyClass::setValue(MyClass* this, int value) {
this->value = value;
}

调用时:

1
obj.setValue(42);

实际上会被转换为:

1
setValue(&obj, 42);

注意:

ClassName* 定义一个变量,这个变量的功能和 this 并不完全一样,尽管它们都指向类的对象。以下是它们的区别和联系:

特性 this 指针 ClassName* 变量
生成方式 编译器自动生成 需要显式定义
作用域 仅在类的非静态成员函数中有效 可以在任何地方使用
指向对象 始终指向当前对象 可以指向任何同类型的对象
灵活性 不能修改指向的对象 可以修改指向的对象
用途 主要用于访问当前对象的成员 用于动态管理对象或传递对象指针

文件操作

在C++中,文件操作主要通过标准库中的<fstream>头文件提供的类来实现。这些类包括:

  • ifstream:用于从文件中读取数据(输入文件流)。
  • ofstream:用于向文件中写入数据(输出文件流)。
  • fstream:既可以读取也可以写入文件(输入输出文件流)。

以下是C++文件操作的详细介绍和示例:


1. 打开文件

文件操作的第一步是打开文件。可以使用open()函数,或者直接在构造函数中指定文件名。

打开文件的模式

文件模式通过以下标志指定:

  • ios::in:打开文件用于读取。
  • ios::out:打开文件用于写入。
  • ios::app:追加模式,写入数据时不会覆盖原有内容。
  • ios::ate:打开文件后定位到文件末尾。
  • ios::trunc:如果文件已存在,清空文件内容。
  • ios::binary:以二进制模式打开文件。

这些模式可以通过|组合使用。

示例:打开文件

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 <fstream>
using namespace std;

int main() {
// 使用构造函数打开文件
ofstream outFile("example.txt", ios::out); // 打开文件用于写入
ifstream inFile("example.txt", ios::in); // 打开文件用于读取

// 使用open()函数打开文件
fstream file;
file.open("example.txt", ios::in | ios::out); // 打开文件用于读写

// 检查文件是否成功打开
if (!outFile.is_open()) {
cout << "Failed to open file!" << endl;
return 1;
}

// 关闭文件
outFile.close();
inFile.close();
file.close();

return 0;
}

2. 写入文件

使用ofstreamfstream向文件中写入数据。

示例:写入文件

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

int main() {
ofstream outFile("example.txt", ios::out);
if (!outFile.is_open()) {
cout << "Failed to open file!" << endl;
return 1;
}

outFile << "Hello, World!" << endl; // 写入字符串
outFile << 123 << endl; // 写入整数
outFile << 3.14 << endl; // 写入浮点数

outFile.close();
return 0;
}

3. 读取文件

使用ifstreamfstream从文件中读取数据。

示例:读取文件

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
#include <fstream>
#include <iostream>
using namespace std;

int main() {
ifstream inFile("example.txt", ios::in);
if (!inFile.is_open()) {
cout << "Failed to open file!" << endl;
return 1;
}

string line;
int number;
double pi;

getline(inFile, line); // 读取一行字符串
inFile >> number; // 读取整数
inFile >> pi; // 读取浮点数

cout << "Line: " << line << endl;
cout << "Number: " << number << endl;
cout << "Pi: " << pi << endl;

inFile.close();
return 0;
}

4. 文件位置操作

可以使用seekg()tellg()(用于输入流)或seekp()tellp()(用于输出流)来操作文件指针的位置。

示例:文件指针操作

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
#include <fstream>
#include <iostream>
using namespace std;

int main() {
fstream file("example.txt", ios::in | ios::out);
if (!file.is_open()) {
cout << "Failed to open file!" << endl;
return 1;
}

file << "Hello, World!" << endl;

// 获取当前文件指针位置
streampos pos = file.tellp();
cout << "Current position: " << pos << endl;

// 移动文件指针到文件开头
file.seekp(0, ios::beg);

// 读取文件内容
string line;
getline(file, line);
cout << "Read from file: " << line << endl;

file.close();
return 0;
}

5. 二进制文件操作

二进制文件操作使用ios::binary模式,并通过read()write()函数读写数据。

示例:二进制文件操作

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
#include <fstream>
#include <iostream>
using namespace std;

struct Data {
int id;
double value;
};

int main() {
// 写入二进制文件
ofstream outFile("data.bin", ios::out | ios::binary);
if (!outFile.is_open()) {
cout << "Failed to open file!" << endl;
return 1;
}

Data data = {1, 3.14};
outFile.write(reinterpret_cast<char*>(&data), sizeof(data));
outFile.close();

// 读取二进制文件
ifstream inFile("data.bin", ios::in | ios::binary);
if (!inFile.is_open()) {
cout << "Failed to open file!" << endl;
return 1;
}

Data readData;
inFile.read(reinterpret_cast<char*>(&readData), sizeof(readData));
cout << "ID: " << readData.id << ", Value: " << readData.value << endl;

inFile.close();
return 0;
}

6. 错误处理

文件操作中可能会发生错误,可以通过以下方式检查:

  • is_open():检查文件是否成功打开。
  • fail():检查流是否处于错误状态。
  • eof():检查是否到达文件末尾。

示例:错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
ifstream inFile("example.txt");
if (!inFile.is_open()) {
cout << "Failed to open file!" << endl;
return 1;
}

if (inFile.fail()) {
cout << "An error occurred while reading the file!" << endl;
}

if (inFile.eof()) {
cout << "Reached end of file!" << endl;
}

总结

C++中的文件操作主要通过<fstream>实现,支持文本和二进制文件的读写。关键点包括:

  1. 使用ifstreamofstreamfstream类。
  2. 通过open()或构造函数打开文件。
  3. 使用<<>>进行文本读写,使用read()write()进行二进制读写。
  4. 通过seekg()tellg()等操作文件指针。
  5. 注意错误处理和文件关闭。

c++中的类型转换

在C++中,类型转换是将一种数据类型转换为另一种数据类型的过程。C++提供了四种类型转换运算符,分别是:static_castdynamic_castconst_castreinterpret_cast。这些运算符提供了更安全和明确的类型转换方式,相比于C语言中的强制类型转换,C++的类型转换运算符更加精细和可控。

1. static_cast

static_cast 是最常用的类型转换运算符,用于执行非多态类型的转换。它通常用于基本数据类型之间的转换,以及具有继承关系的类指针或引用之间的转换。

使用场景:

  • 基本数据类型之间的转换,如 intdouble
  • 将指针或引用从派生类转换为基类(上行转换)。
  • 将指针或引用从基类转换为派生类(下行转换,但不进行运行时类型检查)。
  • void* 指针转换为其他类型的指针。

示例:

1
2
3
4
5
6
7
8
int i = 10;
double d = static_cast<double>(i); // 将 int 转换为 double

class Base {};
class Derived : public Base {};

Base* b = new Derived;
Derived* d = static_cast<Derived*>(b); // 将 Base* 转换为 Derived*

2. dynamic_cast

dynamic_cast 主要用于处理多态类型的转换,即在继承体系中进行向下转换(从基类指针或引用转换为派生类指针或引用)。它在运行时进行类型检查,如果转换不合法,则返回 nullptr(对于指针)或抛出 std::bad_cast 异常(对于引用)。

使用场景:

  • 在继承体系中进行向下转换。
  • 用于多态类型(即基类必须有虚函数)。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Base {
public:
virtual void foo() {}
};

class Derived : public Base {};

Base* b = new Derived;
Derived* d = dynamic_cast<Derived*>(b); // 将 Base* 转换为 Derived*

if (d) {
// 转换成功
} else {
// 转换失败
}

3. const_cast

const_cast 用于修改类型的 constvolatile 属性。它可以将 const 对象转换为非 const 对象,或者将 volatile 对象转换为非 volatile 对象。

使用场景:

  • 去除 constvolatile 属性。
  • 不能用于修改对象的实际类型。

示例:

1
2
3
const int i = 10;
int* p = const_cast<int*>(&i); // 去除 const 属性
*p = 20; // 修改值,但这是未定义行为,因为 i 本身是 const

4. reinterpret_cast

reinterpret_cast 是最危险的类型转换运算符,它提供了低级别的重新解释比特模式的转换。它通常用于不相关的类型之间的转换,如将指针转换为整数,或将一种类型的指针转换为另一种类型的指针。

使用场景:

  • 指针和整数之间的转换。
  • 不相关类型指针之间的转换。
  • 函数指针之间的转换。

示例:

1
2
3
4
5
int i = 10;
int* p = &i;
long addr = reinterpret_cast<long>(p); // 将指针转换为整数

char* c = reinterpret_cast<char*>(p); // 将 int* 转换为 char*

对比表格

| 转换类型 | 检查时机 | 安全性 | 典型用途 |

|—————————|—————|————|———————————————|

| static_cast | 编译时 | 高 | 显式类型转换、继承体系转换 |

| dynamic_cast | 运行时 | 中 | 多态类型安全向下转型 |

| const_cast | 编译时 | 低 | 修改const/volatile属性 |

| reinterpret_cast| 编译时 | 危险 | 低层二进制数据重新解释 |

转换选择建议

  1. 优先使用static_cast
  2. 多态类型转换用dynamic_cast

  3. 除非必要,避免使用const_cast和reinterpret_cast

  4. 完全禁用C风格强制转换:

1
2
3
4
5
// 避免使用*

int* p = (int*)&d; // C风格*

int* p = reinterpret_cast<int*>(&d); // C++风格更明确*

练习

以下是基于图中主题的40道题目,涵盖选择题、填空题、问答题和改错题。答案统一放在文末。

选择题

  1. C++中函数的缺省参数的作用是什么?
    A. 提高代码执行效率
    B. 简化函数调用
    C. 增加函数的功能
    D. 减少代码行数
  2. 以下哪个是C++中函数重载的正确示例?
    A. void func(int a); int func(int a, int b);
    B. void func(int a); int func(int a);
    C. void func(int a); void func(double a);
    D. void func(int a); void func(int a, int b = 10);
  3. C++中名字空间的作用是什么?
    A. 提高代码执行效率
    B. 避免命名冲突
    C. 增加代码的可读性
    D. 减少内存使用

  4. C++中用于标准输出的流对象是?
    A. std::cin
    B. std::cout
    C. std::cerr
    D. std::clog

  5. std::string 类的哪个成员函数用于获取字符串的长度?
    A. length()
    B. size()
    C. capacity()
    D. max_size()
  6. this 指针在C++中的作用是什么?
    A. 指向当前对象的地址
    B. 指向基类的地址
    C. 指向静态成员的地址
    D. 指向全局变量的地址
  7. C++中用于动态内存分配的关键字是?
    A. malloc
    B. new
    C. alloc
    D. create

  8. 以下哪个是C++中最合适的缺省参数声明?
    A. void func(int a = 10, int b);
    B. void func(int a, int b = 10);
    C. void func(int a = 10, int b = 20, int c);
    D. void func(int a, int b, int c = 30);

  9. C++中用于标准错误输出的流对象是?
    A. std::cin
    B. std::cout
    C. std::cerr
    D. std::clog
  10. std::string 类的哪个成员函数用于查找子字符串?
    A. find()
    B. search()
    C. locate()
    D. index()

填空题

  1. 在C++中,函数的缺省参数必须从__开始依次指定。
  2. C++中函数重载的条件是函数的__必须不同。
  3. 使用__关键字可以定义一个名字空间。
  4. C++中用于标准输入的流对象是__
  5. std::string 类的__成员函数用于提取子字符串。
  6. this 指针的类型是__
  7. 在C++中,使用__关键字可以动态分配内存。
  8. 名字空间的作用是避免__
  9. std::string 类的__成员函数用于返回C风格字符串。
  10. 在C++中,std::cin__成员函数用于清除错误状态。

问答题

  1. 解释C++中函数的缺省参数的作用,并给出一个示例。
  2. 什么是函数重载?在C++中如何实现函数重载?
  3. 名字空间在C++中的作用是什么?如何定义和使用名字空间?
  4. 解释C++中标准输入输出流的作用,并列举常用的流对象。
  5. std::string 类的主要功能是什么?列举常用的成员函数。
  6. 解释 this 指针的作用,并给出一个使用 this 指针的示例。
  7. 在C++中,如何使用 newdelete 进行动态内存管理?
  8. 什么是迭代器?如何在 std::string 中使用迭代器?
  9. 解释C++中 const 成员函数的作用,并给出一个示例。
  10. 如何在C++中重定向标准输出流到文件?

改错题

  1. 以下代码有错误,请指出并修正:

    1
    void func(int a = 10, int b);
  2. 以下代码有错误,请指出并修正:

    1
    2
    void func(int a);
    void func(int a,int b=10);
  3. 以下代码有错误,请指出并修正:

    1
    2
    3
    4
    5
    6
    7
    namespace MyNamespace {
    int value = 10;
    }
    int main() {
    std::cout << value << std::endl;
    return 0;
    }
  4. 以下代码有错误,请指出并修正:

    1
    2
    3
    4
    5
    6
    7
    8
    class MyClass {
    public:
    void print() const {
    this->value = 10;
    }
    private:
    int value;
    };
  5. 以下代码有错误,请指出并修正:

    1
    2
    3
    int* ptr = new int;
    delete ptr;
    delete ptr;
  6. 以下代码有错误,请指出并修正:

    1
    2
    3
    4
    5
    6
    7
    8
    class MyClass {
    public:
    MyClass(int a) {}
    };
    int main() {
    MyClass obj;
    return 0;
    }
  7. 以下代码有错误,请指出并修正:

    1
    2
    3
    4
    5
    6
    class MyClass {
    public:
    void func() {
    return this;
    }
    };

答案

  1. B
  2. C

  3. B

  4. B

  5. A

  6. A

  7. B
  8. B //要求选择最合适的,D可能存在二意性问题。

  9. C

  10. A

  11. 参数列表

  12. namespace
  13. std::cin
  14. substr()
  15. ClassName*
  16. new
  17. 命名冲突
  18. c_str()
  19. clear()

  20. 缺省参数允许在调用函数时省略某些参数,编译器会自动使用默认值。示例:void func(int a = 10);

  21. 函数重载允许在同一作用域内定义多个同名函数,只要它们的参数列表不同。
  22. 名字空间用于组织代码,避免命名冲突。定义:namespace MyNamespace {},使用:MyNamespace::value
  23. 标准输入输出流用于处理输入输出操作。常用流对象:std::cinstd::coutstd::cerrstd::clog
  24. std::string 类用于表示和操作字符串。常用成员函数:length()substr()find()append()
  25. this 指针指向当前对象的地址。示例:void setValue(int value) { this->value = value; }
  26. 使用 new 分配内存,delete 释放内存。示例:int* ptr = new int; delete ptr;
  27. 迭代器用于遍历容器中的元素。示例:for (auto it = str.begin(); it != str.end(); ++it)
  28. const 成员函数保证MyClass* func() { return this; } 不修改对象的状态。示例:void print() const { std::cout << value; }
  29. 使用 std::ofstreamstd::cout.rdbuf() 重定向输出流到文件。
  30. 修正:void func(int a, int b = 10);
  31. 修正:二义性问题。
  32. 修正:std::cout << MyNamespace::value << std::endl;
  33. 修正:void print() const { std::cout << value; }
  34. 修正:delete ptr;(只删除一次)
  35. 修正:MyClass obj(10);
  36. 修正:MyClass* func() { return this; }