C++基础(2)

news2025/3/10 11:43:26

目录

1. 引用

1.1 引用的概念和定义

1.2 引用的特性

1.3 引用的使用

2. 常引用

3. 指针和引用的关系

4. 内联函数inline

5. nullptr


1. 引用

1.1 引用的概念和定义

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间, 它和它引用的变量共用同一块内存空间。

                                       类型&  引用别名 = 引用对象

不需要指针就可以交换两个变量的值 

#include<iostream>
using namespace std;
int main()
{
	int a = 2;
	//引用: 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;
}

运行结果:

#include<iostream>
using namespace std;

void Swap(int& rx, int& ry)//rx是x的别名,ry是y的别名
{
	int tmp = rx;
	rx = ry;
	ry = tmp;
}

int main()
{
	int x = 4, y = 5;
	cout << x << "  " << y << endl;

	Swap(x, y);
	cout << x << "  " << y << endl;

	return 0;
}

运行结果:

1.2 引用的特性

  • 引用在定义时必须初始化
  • 一个变量可以有多个引用
  • 引用一旦引用一个实体,再不能引用其它实体
#include<iostream>
using namespace std;
int main()
{
	int a = 10;	
	//int& ra;   报错:"ra":必须初始化引用

	int& b = a;
	int& c = b;


	int d = 20;
	b = d;  //这里并非让b引用d ,C++引用不能改变指向
	        //这里是赋值

	cout << a << "  " << &a << endl;
	cout << b << "  " << &b << endl;
	cout << c << "  " << &c << endl;
	cout << d << "  " << &d << endl;

	return 0;
}

运行结果:

1.3 引用的使用

  • 引用在实践中主要是用于引用传参和引用做返回值中减少拷贝提高效率和改变引用对象时同时改变被引用对象。
  • 引用传参跟指针传参功能是类似的,引用传参相对更方便一些。
  • 引用和指针在实践中相辅相成,功能有重叠性,但是各有特点,互相不可替代。C++的引用跟其他语言的引用(如Java)是有很大的区别的,除了用法,最大的点,C++引用定义后不能改变指向, Java的引用可以改变指向。

引用的类型不仅仅局限于普通变量,还可以是指针,比如我们之前学过的单链表头插或尾插,需要修改链表的头结点,如果链表为空链表,进行插入操作需要改变指针的指向,指针不再指向空,这时就需要传入二级指针用来改变一级指针的指向(具体可参考  单链表专题)。这里使用二级指针非常令人头疼。

typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

//链表头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = *pphead; //将newnode和头结点连接在一起
	*pphead = newnode;       //将链表头结点指向新的头
}

int main()
{
	SLTNode* slist = NULL; //空链表
	SLTPushFront(&slist, 1);
	return 0;
}

我们可以通过引用对其进行如下修改,形参Rslist是对slist 的引用,Rslist 就相当于 slist,等价于直接将链表传入,这里不需要使用二级指针了,相对而言简化了程序。

//链表头插
void SLTPushFront(SLTNode*& Rslist, SLTDataType x)//给指针变量slist取别名Rslist
{
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = Rslist; //将newnode和头结点连接在一起
	Rslist= newnode;       //将链表头结点指向新的头
}

int main()
{
	SLTNode* slist = NULL; //空链表
	SLTPushFront(slist, 1);
	return 0;
}

一些主要用C语言实现版本数据结构教材中,使用引用替代指针传参,目的是简化程序,避开复杂的指针,如果没学过C++的引用,就不容易读懂代码了。

2. 常引用

  • 可以引用一个const对象,但是必须用const引用。const引用也可以引用普通对象,因为对象的访问权限在引用过程中可以缩小,但是不能放大
#include<iostream>
using namespace std;
int main()
{
	const int a = 10;
	//int& ra = a; 
	//这⾥的引⽤是对a访问权限的放⼤   编译报错:“初始化” : 无法从“const int”转换为“int& ” 
		
	//正确引用
	const int& ra = a;
	return 0;
}
#include<iostream>
using namespace std;
int main()
{
	// (1) const引用变量
	int b = 20;
	const int& m = b; //加入const 这里的引用是对b访问权限的缩小
	b++;  //b是可以修改的,但是m不能修改

	//m++;  报错:"m":不能给常量赋值
	return 0;
}

#include<iostream>
using namespace std;
int main()
{
	// (2) const引用常量
	const int& c = 25;//编译器会给常量25开辟一块内存,并将引用名作为这块内存的别名
	                  //常量不可修改,引用不能放大权限,所以需要const来修饰

	return 0;
}
  • 需要注意的是类似 int& rb = a*3; double d = 12.34; int& rd = d; 这样一些场景下,a*3的和结果保存在一个临时对象中,在类型转换中会产生临时对象存储中间值,也就是说,rb和rd引用的都是临时对象,而C++规定临时对象具有常性,所以这里就触发了权限放大,必须要用常引用才可以。
  • 所谓临时对象就是编译器需要一个空间暂存表达式的求值结果时临时创建的一个未命名的对象, C++中把这个未命名对象叫做临时对象。

采用如下方式e 是否正确引用 d?

double d = 6.34;
int& e = d;

上面引用的方式是不可行的,编译器报错,这里涉及隐式类型转换。

例如:double n = 4.09;   int m = n;  由于 n 的类型不是 int ,需要隐式转换成 int ,从 double 到 int 会丢失精度,会把 n 的整数部分取出来生成一个临时变量,然后再将这个变量的值赋值给 m 。临时变量具有常性,不能被修改,我们上面代码然放大了权限,需要const来修饰。

:如果变量类型相同,赋值是不会产生任何临时变量的,如果类型不同,不管是赋值还是引用,都会产生临时变量。

修正:

double d = 6.34;
const int& e = d;

此时的e是对临时变量的引用,它是临时变量的别名。

对权限控制的应用

下面定义的函数参数是普通的引用,在传参的时候会出现错误:

我们在函数的形参部分加入const:

3. 指针和引用的关系

C++中指针和引用就像两个性格迥异的亲兄弟,在实践中他们相辅相成,功能有重叠性,但是各有自己的特点,互相不可替代。

  • 语法概念上引用是一个变量的别名不开空间,指针是存储一个变量地址,要开空间。
  • 引用在定义时必须初始化,指针建议初始化,但是语法上不是必须的。 
  • 引用在初始化时引用一个对象后,就不能再引用其他对象;而指针可以不断地改变指向对象。 
  • 引用可以直接访问指向对象,指针需要解引用才能访问指向对象。 
  • sizeof中含义不同,引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下 占4个字节,64位下是8byte) 。
  • 指针很容易出现空指针和野指针的问题,引用很少出现,引用使用起来相对更安全一些。

指针和引用使用起来不一样,但在底层实现是其实是一样的,比如运行下面代码:

#include<iostream>
using namespace std;
int main()
{
	int a = 0;
	int* p = &a;
	*p = 2;

	int& ra = a;
	ra = 3;

	return 0;
}

转到反汇编观察一下:

我们发现指针和引用在底层汇编实现是一致的,引用实际上也是通过指针来实现的。

4. 内联函数inline

用 inline 修饰的函数叫做内联函数,类似于C语言中的宏展开,编译时C++编译器会在调用的地方展开内联函数,这样调用内联函数就不需要建立栈帧了,就可以提高效率。

特性:

  • C语言实现宏函数也会在预处理时替换展开,但是宏没有类型检查,无论对还是错都是直接替换,而内联函数在编译时进行安全检查,C++设计了 inline 目的就是替代宏函数。
  • inline对于编译器而言只是一个建议,也就是说,你加了inline 编译器也可以选择在调用的地方不展开,不同编译器关于inline什么情况展开各不相同,因为C++标准没有规定这个。inline 适用于频繁调用的短小函数,对于代码很长或者有循环/递归的函数不适宜使用作为内联函数,加上inline也会被编译器忽略。
  • inline不建议声明和定义分离到两个文件,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接时会出现报错,都直接定义在头文件里就行。

//实现⼀个ADD宏函数的常⻅问题(错误写法)

 //#define ADD(int a, int b) return a + b;
 //#define ADD(a, b) a + b;
 //#define ADD(a, b) (a + b)

 // 正确的宏实现
#define ADD(a, b) ((a) + (b))

int main()
{
	int ret = ADD(1, 2);

	//为什么不能加分号?
	cout << ADD(1, 2) << endl;

	//为什么要加外⾯的括号?
	cout << ADD(1, 2) * 5 << endl;

	//为什么要加⾥⾯的括号?
	int x = 1, y = 2;
	ADD(x & y, x | y);   // -> (x&y+x|y)

	return 0;
}

我们观察以下代码内联函数有没有被展开:

#include<iostream>
using namespace std;

inline int Add(int x, int y)
{
	int ret = x + y;	
	return ret;
}

int main()
{
	int ret = Add(1, 2);	
	cout << ret << endl;	

	return 0;
}

调试转到反汇编,我们发现: 

上面编译器生成的汇编代码中存在call Add ,并且创建了函数栈帧,这说明内联函数没有被展开,这是由于vs编译器debug版本下面默认是不展开inline的,这样方便调试,debug版本想展开需要设置以下两个地方:

Debug版本下,完成了对编译器的设置后,再来转到反汇编观察: 

下面汇编代码中不存在call指令,Add函数直接被展开了 

内联函数展不展开,还得看编译器认不认,比如下面程序:

#include<iostream>
using namespace std;

inline int Add(int x, int y)
{
	int ret = x + y;
	ret += 1;
	ret += 1;
	ret += 1;
	ret += 1;
	ret += 1;
	ret += 1;
	ret += 1;
	ret += 1;
	ret += 1;
	return ret;
}

int main()
{
	int ret = Add(1, 2);	
	cout << ret << endl;	

	return 0;
}

转入反汇编观察: 

函数变复杂了,Add函数并没有被展开,很好的展现了内联函数的特性。

 

5. nullptr

NULL实际是一个宏,在传统的C语言标准库头文件(stddef.h)中,可以看到如下代码:

#ifndef NULL
    #ifdef __cplusplus
         #define NULL   0  
    #else
         #define NULL   ((void *)0)
    #endif
#endif

条件编译的宏定义,能确保在不同编程环境下正确处理NULL的定义。 

在C++中,NULL 可能被定义为整数 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(NULL); //调用f(int x) 与预期不符
	f((int*)NULL);

	//f((void*)NULL);  报错: "f": 没有重载函数可以转换所有参数类型

	f(nullptr);//被隐式转换为int*指针类型

	return 0;
}

运行结果:

上面程序中 f(NULL)语句,本想调用指针版本的 f(int*)函数,但是由于NULL被定义成 0,调用了f(int x)因此与程序的初衷相悖。

  • C++11中引入nullptr,从C++11开始使用 nullptr 取代NULL,nullptr是一个特殊的关键字,nullptr是一种特殊类型的字面量,它可以转换成任意其他类型的指针类型。使用nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被隐式地转换为指针类型,而不能被转换为整数类型。

nullptr可以明确的表达空指针的含义,NULL可能引起歧义,因此,在 C++ 中,通常推荐使用 nullptr 而不是 NULL。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2292374.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

electron typescript运行并设置eslint检测

目录 一、初始化package.json 二、安装依赖 三、项目结构 四、配置启动项 五、补充&#xff1a;ts转js别名问题 已整理好的开源代码&#xff1a;Type-Electron: 用typescript开发的electron项目脚手架&#xff0c;轻量级、支持一键配置网页转PC - Gitee.com 一、初始化pac…

modbus协议处理

//------------------------0x01-------------------------------- //MDA_usart_send: aa 55 01 00 06 00 02 00 05 //转modbusTCP——Master——send&#xff1a;地址00002&#xff0c;寄存器数量&#xff1a;00005 00 00 00 00 00 06 01 01 00 02 00 05 //ModbusTCP——Slave…

java-(Oracle)-Oracle,plsqldev,Sql语法,Oracle函数

卸载好注册表,然后安装11g 每次在执行orderby的时候相当于是做了全排序,思考全排序的效率 会比较耗费系统的资源,因此选择在业务不太繁忙的时候进行 --给表添加注释 comment on table emp is 雇员表 --给列添加注释; comment on column emp.empno is 雇员工号;select empno,en…

c++可变参数详解

目录 引言 库的基本功能 va_start 宏: va_arg 宏 va_end 宏 va_copy 宏 使用 处理可变参数代码 C11可变参数模板 基本概念 sizeof... 运算符 包扩展 引言 在C编程中&#xff0c;处理不确定数量的参数是一个常见的需求。为了支持这种需求&#xff0c;C标准库提供了 &…

linux 函数 sem_init () 信号量、sem_destroy()

&#xff08;1&#xff09; &#xff08;2&#xff09; 代码举例&#xff1a; #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #include <unistd.h>sem_t semaphore;void* thread_function(void* arg) …

基于python的体育新闻数据可视化及分析

项目 &#xff1a;北京冬奥会体育新闻数据可视化及分析 摘 要 随着社会的不断进步与发展&#xff0c;新时代下的网络媒体获取的信息也更加庞大和繁杂&#xff0c;相比于传统信息来源更加难以分析和辨别&#xff0c;造成了新时代媒体从业者撰写新闻的难度。在此背景下&#xff…

代码随想录算法【Day36】

Day36 1049. 最后一块石头的重量 II 思路 把石头尽可能分成两堆&#xff0c;这两堆重量如果相似&#xff0c;相撞后所剩的值就是最小值 若石头的总质量为sum&#xff0c;可以将问题转化为0-1背包问题&#xff0c;即给一个容量为sum/2的容器&#xff0c;如何尽量去凑满这个容…

如可安装部署haproxy+keeyalived高可用集群

第一步&#xff0c;环境准备 服务 IP 描述 Keepalived vip Haproxy 负载均衡 主服务器 Rip&#xff1a;192..168.244.101 Vip&#xff1a;192.168.244.100 Keepalive主节点 Keepalive作为高可用 Haproxy作为4 或7层负载均衡 Keepalived vip Haproxy 负载均衡 备用服务…

如何运行Composer安装PHP包 安装JWT库

1. 使用Composer Composer是PHP的依赖管理工具&#xff0c;它允许你轻松地安装和管理PHP包。对于JWT&#xff0c;你可以使用firebase/php-jwt这个库&#xff0c;这是由Firebase提供的官方库。 安装Composer&#xff08;如果你还没有安装的话&#xff09;&#xff1a; 访问Co…

安全策略配置

1.拓扑信息 2. 实验需求 3.需求分析 1.需要在交换机LSW1配置分配vlan并且为配置通道 2/3/4/5 在web界面或者命令行制定相应的安全策略 由于存在默认的拒绝需求4中生产区在任何时刻访问不了web不允许单独配置&#xff0c;只配置动作为运行的策略 4.配置信息 先配置服务器 …

使用Chainlit快速构建一个对话式人工智能应用体验DeepSeek-R1

Chainlit是一个开源的 Python 包&#xff0c;用于构建可用于生产的对话式人工智能。 DeepSeek-R1 是一款强化学习&#xff08;RL&#xff09;驱动的推理模型&#xff0c;解决了模型中的重复性和可读性问题。在 RL 之前&#xff0c;DeepSeek-R1 引入了冷启动数据&#xff0c;进…

生成式AI安全最佳实践 - 抵御OWASP Top 10攻击 (下)

今天小李哥将开启全新的技术分享系列&#xff0c;为大家介绍生成式AI的安全解决方案设计方法和最佳实践。近年来生成式 AI 安全市场正迅速发展。据IDC预测&#xff0c;到2025年全球 AI 安全解决方案市场规模将突破200亿美元&#xff0c;年复合增长率超过30%&#xff0c;而Gartn…

家政预约小程序12服务详情

目录 1 修改数据源2 创建页面3 搭建轮播图4 搭建基本信息5 显示服务规格6 搭建服务描述7 设置过滤条件总结 我们已经在首页、分类页面显示了服务的列表信息&#xff0c;当点击服务的内容时候需要显示服务的详情信息&#xff0c;本篇介绍一下详情页功能的搭建。 1 修改数据源 在…

知识蒸馏教程 Knowledge Distillation Tutorial

来自于&#xff1a;Knowledge Distillation Tutorial 将大模型蒸馏为小模型&#xff0c;可以节省计算资源&#xff0c;加快推理过程&#xff0c;更高效的运行。 使用CIFAR-10数据集 import torch import torch.nn as nn import torch.optim as optim import torchvision.tran…

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.29 NumPy+Scikit-learn(sklearn):机器学习基石揭秘

2.29 NumPyScikit-learn&#xff1a;机器学习基石揭秘 目录 #mermaid-svg-46l4lBcsNWrqVkRd {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-46l4lBcsNWrqVkRd .error-icon{fill:#552222;}#mermaid-svg-46l4lBcsNWr…

【C语言】指针详解:概念、类型与解引用

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C语言 文章目录 &#x1f4af;前言&#x1f4af;指针的基本概念1. 什么是指针2. 指针的基本操作 &#x1f4af;指针的类型1. 指针的大小2. 指针类型与所指向的数据类型3. 指针类型与数据访问的关系4. 指针类型的实际意…

【OS】AUTOSAR架构下的Interrupt详解(上篇)

目录 前言 正文 1.中断概念分析 1.1 中断处理API 1.2 中断级别 1.3 中断向量表 1.4 二类中断的嵌套 1.4.1概述 1.4.2激活 1.5一类中断 1.5.1一类中断的实现 1.5.2一类中断的嵌套 1.5.3在StartOS之前的1类ISR 1.5.4使用1类中断时的注意事项 1.6中断源的初始化 1.…

UE编辑器工具

如何自己制作UE小工具提高工作效率 在虚幻编辑器用户界面中&#xff0c;可以使用各种各样的可视化工具来设置项目&#xff0c;设计和构建关卡&#xff0c;创建游戏性交互等等。但有些时候&#xff0c;当你确定了需要编辑器执行的操作后&#xff0c;可能想要通过编程方式调用它…

【Linux】25.进程信号(2)

文章目录 4.捕捉信号4.1 重谈地址空间4.2 内核如何实现信号的捕捉4.3 sigaction4.4 可重入函数4.5 volatile4.6 SIGCHLD信号&#xff08;了解&#xff09; 4.捕捉信号 4.1 重谈地址空间 用户页表有几份&#xff1f; 有几个进程&#xff0c;就有几份用户级页表–进程具有独立性…

洛谷 P1387 最大正方形 C语言

题目描述 在一个 n m 的只包含 0 和 1 的矩阵里找出一个不包含 0 的最大正方形&#xff0c;输出边长。 输入格式 输入文件第一行为两个整数 n, m (1 ≤ n, m ≤ 100)&#xff0c;接下来 n 行&#xff0c;每行 m 个数字&#xff0c;用空格隔开&#xff0c;0 或 1。 输出格式 …