C++入门(2)

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;
}
不过需要注意的是类似 int& rb = a*3; double d = 12.34; int& rd = d; 这样⼀些场景下a*3的结果保存在⼀个临时对象中, int& rd = d 也是类似,在类型转换中会产生临时对象存储中间值,也就是时,rb和rd引用的都是临时对象,而C++规定临时对象具有常性,所以这里就触发了权限放大,必须要用常引用才可以。所谓临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象,C++中把这个未命名对象叫做临时对象。
#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”可以清楚地表明代码中正在处理空指针,提高了代码的可读性和可维护性。

  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值