6.引用
6.1引用的概念
在 C++中,引用是一个已存在对象的别名。
1. 引用不是对象,它只是为已存在的对象起了另一个名字。所以,对引用的任何操作实际上都是对它所绑定的对象进行操作。
2. 引用可以避免对象的拷贝,提高程序的效率。特别是在传递大型对象作为参数时,使用引用可以避免不必要的拷贝开销。
3. 引用在函数参数传递和返回值中也有广泛应用。通过引用传递参数可以允许函数修改外部对象的值;而返回引用可以使函数的返回值作为左值使用。
类型& 引用别名 = 引用对象;
int main()
{
int a = 0;
int& b = a;
int& c = a;
int& d = b;
++d;
//具有相同的地址
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
cout << &d << endl;
//相同的值
cout << a << endl;
cout << b << endl;
cout << c << endl;
cout << d << endl;
return 0;
}
6.2引用的特性
引用在定义时必须初始化,并且一旦初始化后,就不能再重新绑定到其他对象。一个变量可以有多个引用。
#include <iostream>
using namespace std;
int main()
{
int a = 0;
//报错,引用变量ra需要初始值设定项
//int& ra;
int& b = a;
int c = 10;
//c++引用不能改变指向,这里只是一个赋值
b = c;
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
cout << a << endl;
cout << b << endl;
cout << c << endl;
return 0;
}
6.3引用的使用
引用在实践中主要是于引用传参、避免值传递时的拷贝开销(特别是对于大对象)、改变引用对象的同时改变被引用对象。
#include <iostream>
using namespace std;
typedef struct ListNode
{
int val;
struct ListNode* next;
}LTNode, * PNode;
// 指针变量也可以取别名,这⾥LTNode*& phead就是给指针变量取别名
// 这样就不需要⽤⼆级指针了,相对⽽⾔简化了程序
//void ListPushBack(LTNode** phead, int x)
//void ListPushBack(LTNode*& phead, int x)
//PNode = LTNode*
void ListPushBack(PNode& phead, int x)
{
PNode newnode = (PNode)malloc(sizeof(LTNode));
newnode->val = x;
newnode->next = NULL;
if (phead == NULL)
{
phead = newnode;
}
else
{
//...
}
}
int main()
{
PNode plist = NULL;
ListPushBack(plist, 1);
return 0;
}
6.4const的引用
我们先来复习一下const的知识:
在 C 语言中, const 是一个关键字,主要有以下作用:
一、修饰变量
1. 表示常量
- 用 const 修饰的变量在程序运行过程中其值不能被改变。例如: const int num = 10; ,这里 num 的值在整个程序中始终为 10,不能通过赋值等操作去修改它。
- 可以增强程序的可读性和可维护性,让其他人一看就知道这个变量的值是固定的。
2. 保护变量不被意外修改
- 在函数参数传递中,如果不想让函数内部修改传入的变量,可以用 const 修饰参数。例如: void func(const int *ptr); ,在这个函数中,不能通过指针 ptr 去修改它所指向的内容。
二、修饰指针
1. const 修饰指针指向的内容
- 如 int a = 10; const int *p = &a; ,这里不能通过指针 p 去修改 a 的值,但可以修改指针 p 本身使其指向其他变量。
2. const 修饰指针本身
- 如 int a = 10; int * const p = &a; ,这里指针 p 不能再指向其他变量,但可以通过指针 p 修改 a 的值。
3. const 既修饰指针指向的内容又修饰指针本身
- 如 int a = 10; const int * const p = &a; ,既不能通过指针 p 修改 a 的值,也不能让指针 p 指向其他变量。
三、修饰函数返回值
1. 如果函数返回一个指针,用 const 修饰可以保证返回的指针指向的内容不能被修改。
四、修饰函数参数和函数体
1. 参数为常量引用
- 可以提高函数的通用性,允许传入常量和变量,同时避免不必要的拷贝,提高效率。例如: void func(const int& arg); 。
2. 函数体中使用 const
- 在类的成员函数中,如果一个成员函数不修改类的成员变量,可以用 const 修饰这个成员函数。这样可以保证该函数不会修改对象的状态,也可以让常量对象调用这个函数。例如: class MyClass {public: int getValue() const; }; 。
接下来我们来学习const在c++引用中的用途:
(1) 可以引用⼀个const对象,但是必须用const引用。
(2)const引用也可以引用普通对象,因为对象的访问权限在引用过程中可以缩小,但是不能放大。
#include <iostream>
using namespace std;
int main()
{
const int a = 10;
//权限不能放大
//int& ra = a;
const int& ra = a;
//权限可以缩小
int b = 20;
const int& rb = b;
//只是拷贝赋值不是权限放大
const int x = 0;
int y = x;
//权限不能放大
const int a = 10;
const int* p1 = &a;
//const int*类型不能用来初始化int*类型的实体
//int* p2 = p1; 权限放大
//权限可以缩小
int b = 20;
int* p3 = &b;
const int* p4 = p3;
//const修饰p5本身而不是指向的内容,不是权限放大
int* const p5 = &b;
int* p6 = p5;
return 0;
}
#include <iostream>
using namespace std;
int main()
{
int a = 3;
//编译报错,无法从int转为int&
//int& ra = a * 3;
const int& ra = a * 3;
double b = 11.11;
//编译报错,无法从double类型的初始化int&类型的引用
//int& rb = b;
const int& rb = b;
return 0;
}
6.5引用传参和指针传参的区别
一、引用传参
特点:
- 引用必须在初始化时绑定到一个对象,且之后不能再重新绑定到其他对象。
- 在函数内部对引用参数的修改会直接影响到实参。
- 语法上更简洁,使用起来更直观,不需要像指针那样进行解引用操作。
二、指针传参
特点:
- 可以为空指针,表示不指向任何对象。
- 需要通过解引用操作(如 *param )来访问所指向的对象。
- 可以进行指针的算术运算,实现对数组等数据结构的遍历等操作。
三、两者比较
1. 安全性:
- 引用在一定程度上比指针更安全,因为引用不能为 NULL,并且一旦初始化就不能再指向其他对象,减少了错误发生的可能性。
- 指针如果使用不当可能会出现空指针解引用等错误。
2. 语法简洁性:
- 引用传参在语法上通常更简洁,更接近普通变量的使用方式。
- 指针传参需要更多的语法操作,如解引用和判断指针是否为 NULL。
3. 功能灵活性:
- 指针更灵活,可以进行各种指针运算,实现更复杂的操作。
- 引用的功能相对较为受限,但在大多数情况下已经足够满足需求。
总之,在选择引用传参还是指针传参时,需要根据具体的需求来决定。如果想要更简洁、安全的方式并且不需要进行复杂的指针操作,引用传参是一个不错的选择。如果需要更多的灵活性和特殊的指针操作,指针传参可能更合适。、
7.inline
在 C++中,“inline”修饰符用于声明内联函数。
一、内联函数的作用
内联函数可以减少函数调用的开销。当一个函数被声明为内联函数时,在编译阶段,编译器会将函数体直接插入到调用该函数的地方,而不是像普通函数那样进行函数调用的一系列操作(如保存寄存器、压栈等),这可以大大提高程序的执行效率。
二、使用内联函数的注意事项
1. 内联函数应该比较短小、简单。如果函数体过于复杂,编译器可能会忽略“inline”修饰,不将其作为内联函数处理。
2. 内联函数只是对编译器的一个请求,编译器不一定会完全按照请求将函数内联展开。编译器会根据函数的大小、复杂性以及其他一些因素来决定是否真正地将函数内联。
3.inlne不建议声明和定义分离到两个文件,分离会导致链接错误。因为inline被展开,就没有函数地址,链接时会出现报错。
#include <iostream>
using namespace std;
inline int add(int a, int b) {
return a + b;
}
int main() {
int result = add(3, 4);
cout << "Result: " << result << endl;
return 0;
}
在这个例子中,“add”函数被声明为内联函数,在调用“add(3, 4)”时,编译器可能会将“add”函数的函数体直接插入到调用处,从而提高程序的执行速度。
// F.h
#include <iostream>
using namespace std;
inline void f(int i);
// F.cpp
#include "F.h"
void f(int i)
{
cout << i << endl;
}
// main.cpp
#include "F.h"
int main()
{
// 链接错误:⽆法解析的外部符号 "void __cdecl f(int)" (?f@@YAXH@Z)
f(10);
return 0;
}
8.nullptr
在 C++中,“nullptr”是一种特殊类型的字面量,代表空指针。
一、引入原因
在 C++引入“nullptr”之前,通常用 0 或者 NULL 来表示空指针。但使用 0 或 NULL 存在一些问题,比如可能会引起歧义,因为 0 也可以被解释为整数 0,而不是空指针。
“nullptr”的引入解决了这个问题,它明确地表示空指针,不会与其他类型的 0 值混淆。
二、使用场景
#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”示例
//f((void*)NULL);
f(nullptr);
return 0;
}
三、优势
1. 类型安全:“nullptr”的类型是“std::nullptr_t”,它可以隐式转换为任何指针类型,但不能转换为整数类型等其他非指针类型,增强了类型安全性。
2. 明确性:使用“nullptr”可以清楚地表明代码中正在处理空指针,提高了代码的可读性和可维护性。