前言:
c++基础语法(下)
引用是C++语言中的一种机制,用于创建变量的别名。它使用一个已存在的变量来创建另一个名称,从而通过不同的名字访问相同的内存位置。
类型& 引用变量名(对象名) = 引用实体;
int a = 10;
//创建引用
int& ra = a;
规则如下:
引用必须在声明时进行初始化: 引用在定义的同时必须进行初始化。
int a = 42;
int& a1 = a; // 正确,引用被初始化
创建包含引用的数组: 创建一个包含引用的数组。
int array[5] = {1, 2, 3, 4, 5};
int& referencesArray[5] = {array[0], array[1], array[2], array[3], array[4]};
//referencesArray 中的每个元素都是一个对应于 array 中相应索引位置的引用。上述代码创建了一个引用数组,并将每个引用与数组 array 中的元素相绑定。
引用不能重新赋值: 一旦引用被初始化,就不能改变其引用的对象。
int a = 10;
int b = 20;
int& ref = a;
ref = b; // 此时 a 的值变成 20,而不是修改引用的目标
一个变量可以有多个引用: 多个引用可以同时指向相同的变量,这样它们就共享相同的内存地址,对该变量的修改将会被所有引用所影响。
int a = 10;
int& b = a;//b,c都是a的别名
int& c = a;
int d = 5;
b = d;//变量a被修改为5,同样的b = 5,c = 5
常引用(const reference)是指在声明引用时使用 const 关键字,以表明引用的目标对象在引用生命周期内不可通过别名修改。
#include
int main() {
int a = 10;
const int& b = a; // 常引用
std::cout << "a: " << a << std::endl;
std::cout << "b: " << b << std::endl;
//a:10
//b:10
// 尝试修改 b 的值(这会导致编译错误)
// b = 20;
// 修改原始变量 a 的值
a = 30;
std::cout << "a: " << a << std::endl;
std::cout << "b: " << b << std::endl;
//a:30
//b:30
return 0;
}
另外,对常量的常引用时合法的,但对常量的引用不合法
//可以访问常量的值,但不能通过 a 来修改这个值
const int& b = 10;
//int& b = 10; 不合法
传递函数参数: 通过引用传递参数,可以避免传递大型对象时的复制开销,同时允许在函数内部修改传入的变量。常引用用于确保在函数内部不能修改传入的值。
void modifyValue(int& value) {
value *= 2;
}
int main() {
int num = 5;
modifyValue(num);
// num 在此处被修改为 10
return 0;
}
返回引用: 函数可以返回引用,允许对函数返回值进行修改。这在实现链式调用等情况下很有用。
int array[5] = {1, 2, 3, 4, 5};
int& getElement(int index) {
return array[index];
}
int main() {
getElement(2) = 10;
// array[2] 在此处被修改为 10
return 0;
}
这里数组是全局的,因此在getElement返回值时,数组元素并没有被销毁,因此 array[2] 能被成功修改
如果是以下的这种情况,变量c 随函数栈帧的销毁也被收回空间,那么这个时候返回c的引用就是无效的,其返回的值是随机值
int& Add(int a, int b){
int c = a + b;
return c;
}
int main(){
int& ret = Add(1, 2);
Add(3, 4);
cout << "Add(1, 2) is :"<< ret <<endl;
return 0;
}
引用概念上定义一个变量的别名,指针存储一个变量地址。
*
才能访问目标对象。引用在定义时必须初始化,指针没有要求。
引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体。
没有NULL引用,但有NULL指针。
nullptr
或 NULL
。在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)。
sizeof
运算符对引用返回引用类型的大小。sizeof
运算符对指针返回指针所占的字节大小,通常与地址空间的大小相关。引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小。
有多级指针,但是没有多级引用。
int**
、int***
等形式。访问实体方式不同,指针需要显式解引用,引用编译器自己处理。
*
显式访问目标对象。引用比指针使用起来相对更安全。
内联函数是C++中的一种编译器优化手段,它通过将函数的定义嵌入到调用该函数的地方,而不是通过传统的函数调用机制,以提高程序的执行效率。
使用关键字 inline 来告诉编译器希望将函数内联展开。在函数定义或声明前加上 inline 关键字即可。
inline int add(int a, int b) {
return a + b;
}
int main() {
int a = 0;
int b = 1;
cout << "a+b=" << add(a, b) << endl;
return 0;
}
适用于短小的函数: 内联函数适用于函数体较短小的情况,因为内联的目的是减少函数调用的开销,而将大型函数内联可能会导致代码膨胀(函数被内联展开,那么在每个调用点都会生成一份该函数的代码,增加代码的体积,可能导致更大的可执行文件)。
编译器决定内联: 使用 inline
关键字只是向编译器发出一个建议,最终是否内联取决于编译器的决策。通常,编译器会根据函数的复杂性和调用频率等因素来判断是否进行内联。
一般来说,将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰
头文件中定义内联函数: 通常,内联函数的定义会放在头文件中,以便在多个源文件中进行内联展开。
// header.h
inline int add(int a, int b) {
return a + b;
}
// main.cpp
#include "header.h"
int main() {
int result = add(3, 4); // 内联展开
return 0;
}
auto
是C++11引入的关键字,用于在声明变量时由编译器自动推导变量的类型。使用 auto
可以简化代码,特别是在涉及复杂的类型或使用模板时。以下是关于 auto
的一些关键点:
自动类型推导: auto
关键字可以用于声明变量,让编译器自动推导变量的类型。编译器在编译时会根据变量的初始化表达式推断其类型。
auto x = 10; // x 的类型将被推断为 int
auto y = 3.14; // y 的类型将被推断为 double
auto z = "Hello"; // z 的类型将被推断为 const char[6]
适用于各种类型: auto
可以用于推导各种类型,包括基本数据类型、复合类型、指针、引用等。
auto i = 42; // int
auto f = 3.14f; // float
auto ptr = new int(5); // int*
auto& ref = i; // int&
auto与指针和引用结合起来使用: 用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&。
int main(){
int x = 10;
auto a = &x;
auto* b = &x;
auto& c = x;
//用于获取表达式的类型信息
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
*a = 20;
*b = 30;
c = 40;
return 0;
}
在同一行定义多个变量: 当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
auto a = 1, b = 2;
auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
不能作为函数的参数类型: 函数参数的类型必须在编译时确定,而 auto
是用来进行类型推导的,无法在函数参数中使用。在函数参数中,必须显式指定参数的类型。
void myFunction(auto x) { // 错误,auto 不能用作函数参数类型
// 函数体
}
不能直接用来声明数组: 在数组声明中,编译器需要知道数组的元素类型和大小,而 auto
无法提供这些信息。因此,直接使用 auto
来声明数组是不允许的。
auto myArray[] = {1, 2, 3}; // 错误,auto 不能直接用于数组声明
当 auto
用于范围-based for 循环时,它会自动推导迭代变量的类型,并且可以遍历容器中的元素,使代码更加简洁。以下是使用 auto
的范围-based for 循环的示例:
auto
自动推导出 num
的类型,而 for
循环会遍历 numbers
容器中的每个元素,并将元素的值赋给 num
。
#include
#include
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
// 使用 auto 和范围-based for 循环遍历容器中的元素
for (auto num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
nullptr 是 C++11 引入的空指针常量,用于代替传统的空指针值 NULL。nullptr 具有更强的类型安全性。
在传统的C头文件(stddef.h)中,可以看到如下代码:
NULL 实际上是一个宏,通常被定义为 0 或 (void*)0。
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
示例:
void f(int a){
cout << "f(int)" << endl;
}
void f(int* a){
cout << "f(int*)" << endl;
}
int main(){
f(0);
f(NULL);
f((int*)NULL);
f(nullptr);
return 0;
}
程序本意是想通过f(NULL)调用指针版本的f(int* a)函数,但是由于NULL被定义成0,因此与程序的
初衷相悖。
在C++98中,编译器默认情况下将其NULL
看成是一个整形常量0。当我们使用NULL
调用f(int* a)时必须强转类型 (int*)NULL。而nullptr
明确表示空指针