初识C++:指针与引用的异同,inline关键字
大家好,我是小卡皮巴拉
文章目录
目录
一.指针和引用的关系
1.1 概念
1.2 相似点
1.3 不同点
二.inline关键字
2.1 概念
2.2 工作原理
2.3 使用场景
2.4 注意事项
三.nullptr
3.1 引入背景
3.2 语义和类型
3.3 使用场景
兄弟们共勉 !!!
每篇前言
博客主页:小卡皮巴拉
咱的口号:🌹小比特,大梦想🌹
作者请求:由于博主水平有限,难免会有错误和不准之处,我也非常渴望知道这些错误,恳请大佬们批评斧正。
在上一次的博客初识C++:C++入门基础中,我们学习了引用,这是与C语言中的指针极其类似的语法结构,可以在一定程度上替代指针,下面我们来看看指针和引用有什么区别。
一.指针和引用的关系
1.1 概念
-
指针:指针是一个变量,其值为另一个变量的地址。在 C++ 中,通过使用
*
操作符来声明一个指针变量。例如,int *p;
声明了一个名为p
的指针,它可以指向一个int
类型的变量。指针可以被重新赋值,让它指向不同的变量。 -
引用:引用是一个别名,它是一个变量的另一个名字。在 C++ 中,通过使用
&
操作符来声明一个引用。例如,int a = 10; int &r=a;
,这里r
就是a
的引用,r
和a
代表同一个变量,对r
的操作就是对a
的操作。
1.2 相似点
-
访问变量内容:
-
指针和引用都可以用来访问所关联变量的内容。对于指针,需要使用解引用操作符
*
来获取指针所指向变量的值。例如,如果p
是一个指向int
变量的指针,*p
就可以获取指针所指向的int
值。对于引用,直接使用引用名就可以访问它所关联变量的值,如上述例子中的r
,它和a
的值相同,使用r
就相当于使用a
。
-
-
函数参数传递效率提升方面(部分情况):
-
在函数参数传递中,指针和引用都可以用来避免不必要的变量拷贝,提高效率。例如,当传递一个大型结构体作为函数参数时,使用指针或者引用可以避免整个结构体的拷贝。如果有一个函数
void func(int *p)
和void func(int &r)
,当调用这些函数时,传递的是变量的地址或者别名,而不是变量的副本。
-
1.3 不同点
-
初始化要求:
-
指针可以在声明后不立即初始化,它可以先定义为一个空指针(在 C++ 中
nullptr
是更好的表示空指针的方式),例如int *p = nullptr;
,然后在后续的代码中再让它指向一个有效的变量。引用必须在声明时就进行初始化,并且一旦初始化后,就不能再绑定到其他变量。例如,int a = 10; int &r=a;
是正确的,但是不能在之后再将r
绑定到其他变量,如r = b
(假设b
是另一个int
变量)这种操作是不允许的。
-
-
内存占用和操作灵活性:
-
指针本身占用内存空间,其大小通常取决于系统的寻址位数。例如,在 32 位系统中,指针大小一般是 4 字节,在 64 位系统中,指针大小一般是 8 字节。指针可以进行算术运算,如
p++
(假设p
是一个指针)可以让指针指向下一个内存位置(如果p
指向一个数组元素,那么它会指向下一个数组元素)。引用在语法上只是一个别名,它本身不占用额外的内存空间(在底层实现上可能会有一些细微差异,但从用户角度可以这样理解),并且不能进行像指针那样的算术运算。
-
-
重新赋值特性:
-
指针可以被重新赋值,使其指向不同的变量或者内存位置。例如,
int a = 10, b = 20; int *p=&a; p = &b;
是合法的,这样p
就从指向a
变成了指向b
。引用一旦初始化绑定到一个变量后,就不能被重新赋值去引用其他变量。例如,int a = 10, b = 20; int &r=a; r = b;
这里并不是让r
引用b
,而是把b
的值赋给r
所引用的变量a
,r
始终引用a
。
-
二.inline关键字
2.1 概念
在 C++ 中,inline
是一个关键字,用于建议编译器将函数体直接插入到函数调用的地方,而不是像普通函数那样通过函数调用的机制(如栈帧的创建、参数传递、返回地址保存等)来执行函数。这样做的目的主要是为了提高程序的执行效率,减少函数调用的开销。例如:
inline int add(int a, int b) {
return a + b;
}
这里的add
函数被声明为inline
函数,编译器可能会将函数体直接插入到调用add
函数的地方。
2.2 工作原理
当编译器遇到inline
函数调用时,它会尝试在编译阶段将函数的代码直接复制到调用点。这样,在程序执行时,就好像是直接在调用点执行了函数体的代码,而不是进行传统的函数调用过程。例如,如果有以下代码:
int main() {
int x = 3, y = 5;
int result = add(x, y);
return 0;
}
编译器可能会将add
函数的代码return a + b;
直接替换到result = add(x, y);
这个调用位置,就像写成了int result = x + y;
一样。不过,这只是一种可能的优化方式,编译器是否真正执行这种优化取决于编译器的实现和优化策略。
2.3 使用场景
频繁调用的小型函数:inline
函数非常适合那些短小且被频繁调用的函数。例如,简单的数学运算函数(如加法、减法函数)或者获取和设置类成员变量的访问函数。这些函数的代码通常比较简短,将它们内联可以减少函数调用的开销,从而提高程序的性能。
class Rectangle {
private:
int width;
int height;
public:
// 内联的获取函数
inline int getWidth() const {
return width;
}
// 内联的设置函数
inline void setWidth(int w) {
width = w;
}
};
2.4 注意事项
代码膨胀:过度使用inline
可能会导致代码膨胀。因为每次函数被调用时,函数体的代码都会被插入到调用点,如果inline
函数的代码很长或者被频繁调用,那么最终的可执行文件可能会变得很大。例如,一个有大量代码的inline
函数在多个地方被调用,会导致程序中存在很多重复的代码。
编译器的决定权:inline
只是对编译器的一个建议,编译器并不一定会按照要求将函数内联。编译器会根据自己的优化策略、函数的复杂程度、调用频率等因素来决定是否真正内联一个函数。例如,一个包含复杂循环或者递归的函数,即使被声明为inline
,编译器可能也不会将其内联,因为这样可能会导致代码变得更加难以优化或者不符合内联的实际效益。
三.nullptr
3.1 引入背景
在 C++ 早期版本中,使用NULL
来表示空指针。NULL
通常被定义为((void*)0)
,这在 C 语言中工作得很好。然而,在 C++ 中存在函数重载的情况,NULL
的这种定义可能会导致一些问题。例如,假设有两个函数重载:void func(int)
和void func(void*)
,当调用func(NULL)
时,编译器可能会产生歧义,因为NULL
既可以被解释为整数0
(对于int
参数的函数),也可以被解释为void*
类型的空指针(对于void*
参数的函数)。为了解决这个问题,C++ 11 引入了nullptr
。
3.2 语义和类型
nullptr
是一个表示空指针的常量。它的类型是std::nullptr_t
,这是一种特殊的类型,能够隐式地转换为任何指针类型,但不能转换为非指针类型(除了bool
,nullptr
转换为bool
时为false
)。例如:
int* p = nullptr; // 正确,nullptr可以转换为int*
int i = nullptr; // 错误,nullptr不能转换为int
if (nullptr) { // 条件为假,因为nullptr转换为bool为false
// 不会执行
}
3.3 使用场景
初始化指针变量:在声明指针变量时,可以使用nullptr
来初始化它,表示这个指针当前不指向任何有效的内存地址。这比使用NULL
更加安全和明确,避免了上述提到的函数重载的歧义问题。
class MyClass {
public:
void* ptr = nullptr;
};
作为函数参数传递空指针:当一个函数的参数是指针类型,并且需要传递一个表示 “没有指向任何东西” 的参数时,可以使用nullptr
。例如:
void printString(const char* str) {
if (str == nullptr) {
std::cout << "字符串为空" << std::endl;
} else {
std::cout << str << std::endl;
}
}
在指针比较中的应用:可以使用nullptr
来检查指针是否为空,就像以前使用NULL
一样,但是更加符合 C++ 的类型系统。
兄弟们共勉 !!!
码字不易,求个三连
抱拳了兄弟们!