一:命名空间
C++ 命名空间是一种用于避免命名冲突的机制,它将全局作用域划分为不同的区域,每个区域可以包含不同的变量、函数、类和其他标识符。
1.1. 命名空间的定义
命名空间的定义使用关键字 namespace
,后跟命名空间的名称和一对大括号。
namespace myNamespace {
// 区域内的代码
}
1.2. 命名空间的使用
使用命名空间可以通过两种方式:
- 使用完整的命名空间限定符访问其中的标识符
myNamespace::variable;
myNamespace::function();
myNamespace::Class obj;
- 使用
using
关键字引入整个命名空间,这样就可以直接访问其中的标识符
using namespace myNamespace;
variable;
function();
Class obj;
1.3. 命名空间的嵌套
C++ 允许在一个命名空间内定义另一个命名空间,从而创建命名空间的嵌套结构
namespace outerNamespace {
// 外层命名空间的代码
namespace innerNamespace {
// 内层命名空间的代码
}
}
1.4. 标准命名空间
C++ 标准库中的标识符位于名为 std
的命名空间中。在使用标准库中的函数、类和其他标识符时,需要使用 std
命名空间限定符,或者使用 using namespace std;
引入整个 `std` 命名空间。
std::cout << "Hello, World!" << std::endl;
或者:
using namespace std;
cout << "Hello, World!" << endl;
1.5. 解决命名冲突
当多个命名空间中存在相同名称的标识符时,可以使用命名空间限定符来明确指定要使用的标识符所属的命名空间。
如果不同的命名空间中存在相同名称的函数或变量,可以使用 using
关键字指定要使用的特定命名空间中的标识符。
二:C++输入和输出
C++ 中的输入和输出是通过流(stream)来实现的。流是一种用于读取和写入数据的抽象概念,可以与不同的设备(如键盘、屏幕和文件)进行交互。
2.1. 输出数据
C++ 使用输出流(ostream
类对象)来将数据发送到输出设备。常用的输出流对象是 std::cout
,它可以将数据输出到屏幕上。
#include <iostream>
int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}
上述代码使用 < <
运算符将字符串 "Hello, World!"
输出到标准输出流,并在末尾添加换行符。
2.2. 格式化输出
C++ 提供了不同的输出格式选项,可以在输出时控制数据的显示方式。
可以使用 std::setw()
函数来设置字段宽度
使用 std::setprecision()
函数来设置浮点数的精度。
#include <iostream>
#include <iomanip>
int main() {
double pi = 3.14159265359;
std::cout << std::setprecision(4) << pi << std::endl;
return 0;
}
上述代码将输出圆周率 π 的值为 3.142。
2.3. 输入数据
C++ 使用输入流(istream
类对象)来从输入设备读取数据。常用的输入流对象是 std::cin
,它可以从标准输入流(键盘)读取数据。
#include <iostream>
int main() {
int num;
std::cout << "Enter a number: ";
std::cin >> num;
std::cout << "You entered: " << num << std::endl;
return 0;
}
上述代码提示用户输入一个整数,并将输入存储在变量 num
中,然后将其输出。
2.4. 字符串输入和输出
C++ 使用字符串流(stringstream
)来进行字符串的输入和输出操作。可以使用 std::istringstream
将字符串转换为输入流,使用 std::ostringstream
将输出流保存到字符串中。
#include <iostream>
#include <sstream>
int main() {
std::string name = "John";
int age = 25;
std::ostringstream oss;
oss << "Name: " << name << ", Age: " << age;
std::string info = oss.str();
std::cout << info << std::endl;
std::istringstream iss(info);
std::string extractedName;
int extractedAge;
iss.ignore(6); // 忽略前面的 "Name: "
iss >> extractedName;
iss.ignore(8); // 忽略 ", Age: "
iss >> extractedAge;
std::cout << "Extracted Name: " << extractedName << std::endl;
std::cout << "Extracted Age: " << extractedAge << std::endl;
return 0;
}
上述代码将字符串 "Name: John, Age: 25"
输出到屏幕上,并从该字符串中提取出姓名和年龄。
三:缺省参数
C++ 中的默认参数(default arguments)允许在函数声明中为一个或多个参数提供默认值。这意味着在调用函数时,可以选择省略这些参数,并使用它们的默认值。
3.1. 声明带有默认参数的函数
可以在函数声明或函数原型中为参数提供默认值。
void func(int a, int b = 10, int c = 20);
在上述函数声明中,参数 b
和 c
都有默认值,分别为 10
和 20
。
3.2. 实现带有默认参数的函数
在函数的实现部分不需要再次为参数指定默认值。例如:
void func(int a, int b, int c) {
// 函数体
}
在上述函数实现中,参数 b
和 c
已经在声明中有了默认值,所以不需要再次提供。
3.3. 调用带有默认参数的函数
在调用带有默认参数的函数时,可以选择省略某些参数。省略的参数将使用其默认值。例如:
func(5); // a = 5, b = 10, c = 20
func(5, 15); // a = 5, b = 15, c = 20
func(5, 15, 25); // a = 5, b = 15, c = 25
在上述示例中,函数 func()
的第一个参数 a
是必需的,但是第二个参数 b
和第三个参数 c
可以省略。
3.4. 注意事项
以下是与默认参数相关的一些注意事项:
- 默认参数只能在函数声明或函数原型中提供,不能在函数的定义中提供。
- 如果函数有多个参数具有默认值,那么在调用函数时,可以选择从任何一个具有默认值的参数开始省略。
- 如果有多个函数重载,其中某些函数具有默认参数,那么应该确保没有二义性。例如,下面的示例是不允许的:
void func(int a, int b = 10);
void func(int a, int b = 20); // 不允许,因为会产生二义性
四:函数重载
C++ 中的函数重载(function overloading)是指可以在同一个作用域内,定义多个名称相同但参数个数或类型不同的函数。在调用这些函数时,编译器会根据实际传入的参数类型和数量来自动选择最匹配的函数进行调用.
4.1. 声明函数重载
要声明函数重载,需要使用相同的函数名,但是必须至少有一个参数与其他版本的函数不同。例如:
void func(int a);
void func(double a);
在上述代码中,我们声明了两个名为 func
的函数,一个接受整数参数,另一个接受双精度浮点数参数。
4.2. 实现函数重载
在实现函数重载时,必须使用与其声明相同的函数名。函数重载通常使用不同的参数列表和实现细节。例如:
void func(int a) {
// 处理整数参数的函数体
}
void func(double a) {
// 处理双精度浮点数参数的函数体
}
在上述代码中,我们实现了两个名为 func
的函数,分别处理整数和双精度浮点数参数。
4.3. 调用函数重载
在调用函数重载时,编译器会根据实际传入的参数类型和数量来选择最匹配的函数进行调用。
func(5); // 调用 func(int a)
func(3.14); // 调用 func(double a)
在上述示例中,第一次调用 func()
时,传入整数参数,所以会调用处理整数参数的函数。第二次调用 func()
时,传入双精度浮点数参数,所以会调用处理双精度浮点数参数的函数。
4.4. 注意事项
以下是与函数重载相关的一些注意事项:
- 函数重载只与函数的参数列表有关,与函数的返回类型无关。
- 在调用函数时,如果没有找到与参数匹配的函数,编译器将抛出错误。
- 函数重载可以应用于成员函数和非成员函数。
五:引用
在 C++ 中,引用(reference)是一种为对象起别名的机制。通过引用,可以使用原始对象的名称来访问和修改对象的内容,而无需使用指针。
5.1. 定义引用
引用是在声明时使用 &
符号来定义的。例如:
int num = 10;
int& ref = num; // 创建一个整数引用 ref,它引用 num
在上述代码中,ref
是 num
的引用,它可以被用来访问和修改 num
的值。
5.2. 引用的特性
引用有以下几个重要特性:
- 引用必须在创建时初始化,并且不能更改引用的目标对象。一旦引用被初始化,它将一直引用同一个对象。
- 引用可以作为函数的参数或返回值,以便传递和操作对象而不进行对象的复制。
- 引用不占用额外的内存,因为它只是对象的别名。
5.3. 引用 vs.指针
引用和指针有一些相似之处,但也有一些重要的区别:
- 引用必须在创建时初始化,而指针可以在任何时候初始化。
- 引用不能重新绑定到其他对象,而指针可以通过改变指针的值指向其他对象。
- 引用不需要解引用操作符( * ),直接使用引用就可以访问和修改对象。指针需要使用解引用操作符来访问和修改指向的对象。
5.4. 引用作为函数参数
引用常被用作函数的参数,以便在函数内部修改传入的对象。这样可以避免传递大型对象时的复制开销。例如:
void modify(int& ref) {
ref = 20;
}
int num = 10;
modify(num); // 传递 num 的引用给 modify 函数,在函数内部修改 num 的值
在上述代码中,modify()
函数接受一个整数的引用,通过引用修改了传入的对象 num
。
六:内联函数
C++ 内联函数(inline function)是一种在程序中使用的特殊函数,它的主要目的是在调用点直接将函数体嵌入到调用处,而不是通过函数调用的方式进行执行。这样可以减少函数调用带来的开销,并提高程序的执行效率。
6.1. 定义内联函数
要定义一个内联函数,需要在函数声明和定义之前加上 inline
关键字。
inline int add(int a, int b) {
return a + b;
}
在上述代码中,add()
函数被声明为内联函数。
6.2. 使用内联函数
使用内联函数与使用普通函数没有太大区别,只需要像调用普通函数一样调用内联函数即可。
int result = add(5, 10); // 调用内联函数 add()
在上述代码中,我们调用了内联函数 add()
,并将返回值赋给变量 result
。
6.3. 内联函数的优势
- 减少函数调用开销:内联函数在调用点直接嵌入函数体,避免了函数调用带来的额外开销,如栈帧的创建和销毁。
- 提高执行效率:内联函数在编译时展开,可以减少函数调用的开销,并且有利于编译器进行更好的优化。
- 避免函数调用带来的副作用:某些情况下,函数调用可能引入副作用,如修改全局变量的值。使用内联函数可以避免这些副作用。
6.4. 内联函数的注意事项
- 内联函数适合用于短小的函数体,避免将复杂的函数体内联,以免代码膨胀。
- 编译器可以选择是否将函数内联,它可能会忽略
inline
关键字的建议。 - 内联函数一般放在头文件中定义,以便在多个源文件中使用。
七:auto关键字
C++11 引入了 auto
关键字,它用于声明变量时自动推导变量的类型。使用 auto
关键字可以简化代码,减少类型的重复书写,并增加代码的可读性。
7.1. 自动类型推导
使用 auto
关键字声明变量时,编译器会根据变量的初始化表达式来自动推导变量的类型。例如:
auto num = 10; // 自动推导 num 的类型为 int
auto name = "Alice"; // 自动推导 name 的类型为 const char*
在上述代码中,变量 num
的类型被推导为 int
,变量 name
的类型被推导为 const char*
。
7.2. 适用范围
auto
关键字主要用于自动推导局部变量的类型。全局变量、静态变量和函数参数不适用于 auto
关键字。
7.3. 常量性推导
auto
关键字还可以与 const
修饰符一起使用,自动推导出常量类型的变量。例如:
const auto pi = 3.14; // 自动推导 pi 的类型为 const double
在上述代码中,变量 pi
被自动推导为 const double
类型。
7.4. 函数返回值类型推导
auto
关键字还可以用于函数的返回值类型推导。例如:
auto add(int a, int b) {
return a + b; // 自动推导返回值类型为 int
}
在上述代码中,函数 add()
的返回值类型被自动推导为 int
。
7.5. auto
与引用
在 C++11 中,auto
关键字和引用组合使用时,可以推导出引用类型的变量。例如:
int num = 10;
auto& ref = num; // 自动推导 ref 的类型为 int&
在上述代码中,变量 ref
被自动推导为 int&
类型。
基于范围的for循环(C++17)
C++17引入了基于范围的for循环,它提供了一种简洁的方式来遍历容器、数组和其他支持迭代器的数据结构。使用基于范围的for循环可以更直观地编写循环代码。
基本语法如下所示:
for (element_declaration : range_expression) {
// 循环体
}
其中,element_declaration
是一个变量声明,用于接收range_expression
中的每个元素。range_expression
是一个可迭代的数据结构,如容器、数组或支持迭代器的类型。
以下是一个示例,展示了如何使用基于范围的for循环来遍历一个数组:
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = { 1, 2, 3, 4, 5 };
for (int num : numbers) {
std::cout << num << " ";
}
return 0;
}
运行上述代码将输出:1 2 3 4 5
。
基于范围的for循环还支持使用auto
关键字自动推导元素类型:
for (auto element : range_expression) {
// 循环体
}
这样可以更方便地处理不同类型的数据结构。
需要注意的是,基于范围的for循环在遍历过程中不允许修改容器中的元素。如果需要修改元素,可以使用引用类型来声明循环变量。
指针空置———nullptr(C++11)
在C++11标准中,引入了一个新的关键字nullptr,用于表示空指针。nullptr是一个字面量,可以用来显式地将指针初始化为空值。
在C++11之前,通常使用整数0来表示空指针,但这会导致一些模糊的语义和类型转换问题。nullptr的引入解决了这些问题,使得代码更加明确和安全。
以下是使用nullptr的示例:
int* ptr = nullptr; // 将指针初始化为空值
if (ptr == nullptr) {
// 检查指针是否为空
// ...
}
// 使用空指针调用函数或进行其他操作是安全的
在上面的示例中,我们使用nullptr将指针ptr初始化为空值。然后,我们使用if语句检查指针是否为空。nullptr与指针进行比较时会自动转换为相应的类型,所以无需担心类型转换问题。
在C++中,nullptr可以与任何指针类型进行比较,包括原始指针、智能指针和类成员指针等。与其他类型进行比较时,nullptr会被转换为布尔值false。