1. 引用
引用的定义
引用是另一个变量的别名,它在声明时必须被初始化,并且一旦初始化后,它就始终引用那个变量。
引用的语法
引用的声明方式是在变量名前加上&
符号。
引用的特点
- 引用必须在声明时初始化。
- 引用一旦初始化后,就不能再引用其他变量。
- 引用不是独立的变量,它和它引用的变量实质上是同一个变量。(底层上是指向同一个变量的指针)
引用的使用场景
- 作为函数参数,实现参数的按引用传递,允许函数直接修改传入的参数。
- 用于简化复杂对象的操作。
基本引用
int main() {
int a = 10;
int &ref = a; // ref是a的引用
ref = 20; // 通过引用修改a的值
std::cout << a << std::endl; // 输出20,因为a的值被ref修改了
return 0;
}
引用作为函数参数
void swap(int &x, int &y) {
int temp = x;
x = y;
y = temp;
}
int main() {
int a = 10, b = 20;
swap(a, b); // 交换a和b的值
std::cout << "a: " << a << ", b: " << b << std::endl; // 输出 a: 20, b: 10
return 0;
}
引用返回局部变量
需要注意的是,返回局部变量的引用是不安全的,因为局部变量在函数返回后其生命周期就结束了。
int &getValue() {
static int value = 10; // 使用静态变量来保证生命周期
return value;
}
int main() {
int &ref = getValue(); // 获取引用
ref = 20; // 修改引用
std::cout << getValue() << std::endl; // 输出20,因为value被修改了
return 0;
}
由于value
是静态变量,它的生命周期不会在getValue
函数调用结束后结束,因此返回它的引用是安全的。如果value
不是静态变量,那么返回它的引用将导致未定义行为。
2. 内联函数
内联函数的定义
内联函数是一种特殊的函数,它在编译时会在每个调用点处展开函数体,从而避免了函数调用的开销。但是,是否真正内联由编译器决定。
内联函数的语法
在C++中,内联函数通过在函数定义前加上inline
关键字来声明。
内联函数的特点
- 内联函数通常用于频繁调用且函数体较小的场合。
- 内联函数可以节省函数调用的开销,但可能会增加编译后的代码大小。
- 内联函数对于编译器来说只是一个建议,编译器可能会忽略这个建议。
使用内联函数的注意事项
- 不要将过于复杂的函数声明为内联,因为这将导致代码膨胀。
- 构造函数和析构函数通常不应该声明为内联,除非它们非常简单。
inline int add(int a, int b) {
return a + b;
}
int main() {
int result = add(3, 4); // 在这里,编译器可能会将add函数体展开
std::cout << "Result: " << result << std::endl; // 输出 Result: 7
return 0;
}
add
函数被声明为内联。当编译器编译main
函数时,它可能会将add(3, 4)
替换为3 + 4
,这样就避免了函数调用的开销。
需要注意的是,内联函数的定义应该放在头文件中,因为内联函数需要在每个调用点展开,所以编译器需要在每个包含该头文件的源文件中看到函数的定义。如果内联函数的定义放在源文件中,可能会导致链接错误。
3. auto关键字(C++11)
auto的自动推导类型
auto
关键字告诉编译器自动推导变量的类型。- 这使得代码更加简洁,尤其是在处理复杂类型或长类型名称时。
auto
可以用于声明变量、函数返回类型、lambda表达式的参数等。
auto var = value; // 编译器会根据value的类型自动推导var的类型
使用auto
时,应确保类型推导的结果是清晰和预期的,避免不必要的混淆。不能用于函数参数的类型推导,因为函数参数需要在函数声明时就确定类型。
4. 基于范围的for循环(C++11)
基本语法
for (declaration : expression) {
// 循环体
}
expression
是你要遍历的序列(如数组、容器等),而declaration
用于在每次迭代中声明一个变量,该变量将被初始化为序列中的下一个元素。
遍历数组
int arr[] = {1, 2, 3, 4, 5};
for (int i : arr) {
std::cout << i << std::endl; // 输出数组中的每个元素
}
遍历容器
std::vector<double> vec = {1.1, 2.2, 3.3, 4.4, 5.5};
for (double val : vec) {
std::cout << val << std::endl; // 输出vector中的每个元素
}
遍历字符串
std::string str = "Hello, World!";
for (char ch : str) {
std::cout << ch; // 输出字符串中的每个字符
}
std::cout << std::endl;
在基于范围的for循环中,如果你需要修改元素的值,你应该使用引用(如int&
或auto&
)来避免不必要的复制。如果你不希望修改元素,可以使用常量引用(如const int&
或const auto&
)。如果元素类型是一个类类型,并且没有定义移动构造函数或移动赋值运算符,使用引用可以避免调用复制构造函数或复制赋值运算符,从而提高性能。
5. 指针空值---nullptr(C++11)
在C++11标准中,nullptr
关键字被引入作为新的空指针常量。在此之前,C++程序通常使用NULL
或0
来表示空指针。然而,这两种方法都有其缺点:
NULL
在C++中被定义为整数0,但在某些情况下可能会导致类型不匹配的问题,尤其是当重载函数同时接受整数和指针参数时。- 直接使用数字
0
同样存在类型不匹配的问题,并且代码的可读性较差。
nullptr
的引入解决了这些问题,它是一个特殊的字面量,类型为std::nullptr_t
,它可以被隐式转换为任何指针类型或成员指针类型,但不能被转换为任何整数类型。
int* ptr = nullptr; // 正确:nullptr可以赋值给整型指针
double* dptr = nullptr; // 正确:nullptr可以赋值给双精度浮点型指针
void func(int*) {}
void func(int) {}
func(nullptr); // 调用func(int*),而不是func(int)
class MyClass {
public:
void memberFunc(int*) {}
};
MyClass obj;
obj.memberFunc(nullptr); // 正确:nullptr可以传递给成员函数的指针参数