目录
[3.1 别名](#3.1 别名)
[3.2 不占用额外内存](#3.2 不占用额外内存)
[3.3 必须初始化](#3.3 必须初始化)
[3.4 不能为 NULL](#3.4 不能为 NULL)
[4.1 函数参数传递](#4.1 函数参数传递)
[4.2 返回值](#4.2 返回值)
[4.3 常量引用](#4.3 常量引用)
一、引用
1.引用的概念
引用(Reference)是 C++ 中的一种类型,它提供了一个变量的别名。引用并不是一种独立的数据类型,而是对已有变量的另一种视图。引用的声明使用 & 符号。
引⽤不是新定义⼀个变量,⽽是给已存在变量取了⼀个别名,编译器不会为引⽤变量开辟内存空间,它和它引⽤的变量共⽤同⼀块内存空间。⽐如:⽔壶传中李逵,宋江叫"铁⽜",江湖上⼈称"⿊旋⻛";林冲,外号豹⼦头;
2.引用的基本语法
cpp
int a = 10; // 定义一个整数变量
int &b = a; // b 是 a 的引用
在上面的例子中,b
作为 a
的引用,b
和 a
是同一个对象,修改 b
的值实际上会改变 a
的值。
3.引用的特点
引用的特点:
1.别名:引用是一个变量的别名,对引用的所有操作实际上都是对原变量的操作。
2.不占用额外内存:引用不占用额外的内存空间,只是另一个指向相同内存地址的标识符。
3.必须初始化:引用在创建时必须初始化,并且一旦初始化后不可改变绑定的对象。
4.不能为 NULL :引用不能被赋值为
nullptr
,必须引用一个有效的对象。
3.1 别名
引用是一个变量的别名。这意味着对引用的所有操作都是直接对其所引用的变量的操作。引用没有独立的内存空间,它只是在原变量的基础上提供了一个新的名字。
cpp
#include <iostream>
int main() {
int a = 42; // 定义一个整数变量 a
int &b = a; // b 是 a 的引用
std::cout << "a: " << a << ", b: " << b << std::endl; // 输出 a: 42, b: 42
b = 100; // 通过引用 b 修改 a 的值
std::cout << "After changing b..." << std::endl;
std::cout << "a: " << a << ", b: " << b << std::endl; // 输出 a: 100, b: 100
return 0;
}
在这个示例中,b
是 a
的引用,对 b
的修改直接影响 a
,反之亦然。
3.2 不占用额外内存
引用本质上是一个别名,不会占用新的内存空间。它只是指向已有变量的地址。因此,引用操作不会增加内存的使用。
cpp
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
int main()
{
int a = 0;
// 引⽤:b和c是a的别名
int& b = a;
int& c = a;
// 也可以给别名b取别名,d相当于还是a的别名
int& d = b;
++d;
// 这⾥取地址我们看到是⼀样的
cout <<"a:" << &a << endl;
cout <<"b:" << &b << endl;
cout <<"c:" << &c << endl;
cout <<"d:" << &d << endl;
return 0;
}
在这个例子中,a、b、c、d的地址相同,证明引用并不占用额外的内存空间。
3.3 必须初始化
引用在创建时必须被初始化。它不能在声明后再被赋值或指向其他变量。这一特性使得引用在使用时更加安全,避免了指向无效对象的风险。
cpp
#include <iostream>
int main() {
int a = 5;
// int &b; // 错误:引用必须在声明时初始化
int &b = a; // 正确:b 在声明时被初始化为 a
std::cout << "a: " << a << ", b: " << b << std::endl; // 输出 a: 5, b: 5
return 0;
}
试图声明一个未初始化的引用 b
会导致编译错误,而在初始化时,引用可以安全地与一个变量绑定。
3.4 不能为 NULL
引用不能被赋值为 nullptr,它必须引用一个有效的对象。这意味着引用在创建后始终是有效的,避免了指向空地址的风险。
cpp
#include <iostream>
int main() {
int a = 10;
int &b = a; // 正确,b 引用 a
// int &c = nullptr; // 错误:引用不能为 NULL
std::cout << "b: " << b << std::endl; // 输出 b: 10
return 0;
}
试图将引用 c
赋值为 nullptr
会导致编译错误。这确保了引用始终指向有效的对象。
4.引用的使用
4.1 函数参数传递
使用引用作为函数参数可以有效避免大对象的复制,从而节省内存和时间。通过引用传递参数,函数可以直接修改原始数据,而无需创建副本。
cpp
#include <iostream>
void increment(int &num) {
num += 1; // 直接修改原始数据
}
int main() {
int value = 5;
increment(value); // 传递 value 的引用
std::cout << "Incremented value: " << value << std::endl; // 输出 6
return 0;
}
在这个示例中,increment 函数接受 num
的引用,对 num 的修改直接影响 value,避免了复制的开销。
4.2 返回值
C++ 中的函数可以返回引用,这样可以在函数外部直接修改原始数据。这种方式在某些情况下可以提高效率,但需要谨慎使用,尤其是返回局部变量的引用是危险的。
cpp
#include <iostream>
int& getReference(int &x) {
return x; // 返回 x 的引用
}
int main() {
int a = 10;
getReference(a) = 20; // 直接修改 a
std::cout << "Updated value: " << a << std::endl; // 输出 20
return 0;
}
4.3 常量引用
常量引用(const 引用)允许我们通过引用访问变量,但不允许修改它。这在需要保护数据不被意外修改时非常有用,尤其是在传递大型对象时,可以避免复制并保护原始数据。
cpp
#include <iostream>
void printValue(const int &num) {
std::cout << "Value: " << num << std::endl; // 只读操作
}
int main() {
int a = 10;
printValue(a); // 输出 10
printValue(20); // 可以传递字面量,输出 20
return 0;
}
在这个例子中,printValue 函数接受 const int &num 作为参数,意味着它只能读取 num 的值,而不能修改。这样不仅保证了数据的安全性,还避免了复制的开销。
5.引用和指针的关系
引用和指针是 C++ 中两个重要的概念,它们都可以用于间接访问变量,但在语法、功能和使用方式上存在显著差异。下面将从几个方面比较它们。
(1).基本定义
引用 :引用是一个变量的别名,它指向一个已有变量,并且在创建时必须初始化。引用不占用额外的内存空间,只是原变量的另一个名称。
指针 :指针是一个变量,它存储一个地址,指向另一个变量的内存位置。指针在定义时不一定要初始化,可以在之后赋值。
(2).初始化
引用:在定义引用时,必须立即初始化并引用一个有效的对象。一旦绑定到某个变量后,就无法改变引用的对象。
cpp
int a = 10;
int &b = a; // 必须初始化
指针:指针在定义时不需要初始化,可以稍后赋值。指针可以随时指向不同的对象。q
cpp
int *p; // 不初始化,指向未知
int a = 10;
p = &a; // 指向 a
(3).改变指向
引用:引用一旦初始化后,就不可以再改变引用的对象。
cpp
int a = 10;
int &b = a;
// b = 20; // 这将改变 a 的值为 20,但 b 仍然引用 a
指针:指针可以在程序运行时动态改变指向的对象。
cpp
int a = 10;
int b = 20;
int *p = &a; // p 指向 a
p = &b; // p 现在指向 b
(4).访问对象
引用:可以直接使用引用访问所引用的对象,语法上更简洁。
cpp
int a = 10;
int &b = a;
std::cout << b; // 直接访问
指针 :需要使用解引用操作符 * 访问指针指向的对象。
cpp
int a = 10;
int *p = &a;
std::cout << *p; // 解引用访问
(5).内存大小
引用 :在 sizeof 运算中,引用的结果是引用对象的大小,不占用额外的内存。
cpp
int a = 10;
int &b = a;
std::cout << sizeof(b); // 输出 sizeof(int)
指针 :在 sizeof 运算中,指针的大小是固定的(在 32 位平台上通常为 4 字节,64 位平台上为 8 字节)。
cpp
int *p;
std::cout << sizeof(p); // 输出 4 或 8
(6).安全性
引用 :因为引用不能为 NULL,也不会出现悬挂引用的问题,所以相对更安全。
指针:指针容易出现空指针和悬挂指针的问题,需要额外的小心和处理。
cpp
int *p = nullptr; // 空指针
// int a = *p; // 会导致未定义行为
二、inline
1.定义
inline是C++中的一个关键字,主要用于建议编译器在调用函数的地方直接插入该函数的代码,而不是通过常规的函数调用。这通常用于小型函数,以减少函数调用的开销。
2.使用方法
在C++中,使用inline非常简单。你只需在函数定义前加上inline关键字。例如:
cpp
inline int add(int a, int b) {
return a + b;
}
3.优点
- 性能提升:通过减少函数调用的开销(如压栈、弹栈等),可以提高程序性能,尤其是在频繁调用的小函数中。
- 代码可读性:小函数的使用使得代码更加模块化和易于理解。
4.注意事项
- 编译器的决定 :虽然你可以建议编译器使用inline,但编译器并不一定会接受这个建议。它可能根据函数的复杂度和其他因素决定是否进行内联。
- 代码膨胀 :如果一个inline函数被多次调用,编译器会在每个调用点插入函数体,可能导致代码膨胀,增加最终二进制文件的大小。
- 调试困难:内联函数在调试时可能会使得调用栈不如预期,因为调用点会被替换为函数体。
5.适用场景
- 短小函数 :适合将那些逻辑简单、体积小的函数标记为inline。
- 频繁调用的函数 :例如,在循环中频繁调用的简单函数,使用inline可能会有显著性能提升。
三、nullptr
NULL实际是⼀个宏,在传统的C头⽂件( stddef.h )中,可以看到如下代码:
cpp
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
• C++中 NULL 可能被定义为字⾯常量0,或者C中被定义为⽆类型指针( void* )的常量。不论采取何种定义,在使⽤空值的指针时,都不可避免的会遇到⼀些⿇烦,本想通过 f(NULL) 调⽤指针版本的
f(int*) 函数,但是由于 NULL 被定义成0,调⽤了 f(int x) ,因此与程序的初衷相悖。 f((void*)NULL) ;
调⽤会报错。
• C++11中引⼊ nullptr , nullptr 是⼀个特殊的关键字, nullptr 是⼀种特殊类型的字⾯量,它可以转换
成任意其他类型的指针类型。使⽤ nullptr 定义空指针可以避免类型转换的问题,因为 nullptr 只能被
隐式地转换为指针类型,⽽不能被转换为整数类型。
cpp
#include<iostream>
using namespace std;
void f(int x)
{
cout << "f(int x)" << endl;
}
void f(int* ptr)
{
cout << "f(int* ptr)" << endl;
}
int main()
{
f(0);
// 本想通过f(NULL)调⽤指针版本的f(int*)函数
//但是由于NULL被定义成0,调⽤了f(int x),因此与程序的初衷相悖。
f(NULL);
f((int*)NULL);
// 编译报错:"f": 2 个重载中没有⼀个可以转换所有参数类型
// f((void*)NULL);
f(nullptr);
return 0;
}
总结
引用、内联函数和 nullptr
是 C++ 中的重要特性,它们在代码的可读性、性能和安全性上都有显著影响。了解并合理使用这些特性,有助于编写出高效且可维护的代码。希望这篇博客对你有所帮助!如果有任何问题或想法,欢迎在评论区交流!