
C++总介绍
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++中用于表示小数,有两种主要类型:float
和 double
。其表示范围和精度有所不同:
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. 动态内存分配
使用 new
和 delete
进行动态内存管理:
int* arr = new int[5]; // 动态分配数组
delete[] arr; // 释放内存
6. 类型组合
C++允许将多种数据类型组合到一起,包括数组、结构体和指针的组合。
struct Person {
string name;
int age;
};
Person* people = new Person[10]; // 动态分配Person结构体数组
7. vector
和 array
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: 0
到 Count: 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. 关系表达式
关系表达式用于比较两个值,结果为布尔类型(true
或 false
)。常用的关系运算符有:
==
:等于!=
:不等于<
:小于>
:大于<=
:小于等于>=
:大于等于
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()
在处理输入时,有时需要选择使用 cin
或 cin.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和循环进行文本输入
使用循环和 cin
或 getchar()
结合可以处理多行输入。
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
语句及其相关的 break
和 continue
,以及简单的文件输入/输出。掌握这些内容对构建复杂的控制结构和处理文件非常重要。
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.cpp
、file2.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. 说明符和限定符
- 说明符:如
static
、extern
、const
等,用于控制变量的存储持续性和链接性。 - 限定符:如
const
、volatile
等,影响变量的使用和修改。
2.7. 函数和链接性
函数也可以具有链接性,分为外部链接和内部链接。默认情况下,函数具有外部链接性,可以在其他文件中访问。
2.8. 语言链接性
C++支持与C语言的链接性,可以使用 extern "C"
来指示编译器以C语言方式链接函数。
extern "C" {
void c_function();
}
2.9. 存储方案和动态分配
- 存储方案:包括栈、堆和全局区域。栈用于自动存储,堆用于动态分配。
- 动态分配:使用
new
和delete
关键字进行内存管理。
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. 动态内存分配
动态内存分配使用 new
和 delete
运算符,允许程序在运行时分配和释放内存。使用动态内存分配可以创建可变大小的数据结构。
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_ptr
、unique_ptr
和 shared_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 包含多种容器类,例如 vector
、list
、set
和 map
,用于存储数据。
#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::fixed
和 std::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++ 提供了简单的文件输入输出接口,可以使用 ifstream
和 ofstream
进行文件操作。
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 | += , -= , *= , /= , %= |
复合赋值运算符 |