【C++】C++入门2.0

news2024/12/24 22:41:09

各位读者老爷好,本鼠最近浅学了一点C++的入门知识!利用本博客作为笔记的同时也希望得到各位大佬的垂阅!

目录

1. 引用

1.1.引用的概念

1.2.引用的特性

1.3.引用的使用场景 

 1.4.引用的易错点

1.5.引用的优势

1.6.引用和指针

2.内联函数

2.1.内联函数的概念

2.2.内联函数的特性

3. auto关键字(C++11)

3.1.auto的概念

 3.2.auto的使用细则

4.基于范围的for循环(C++11)

4.1.范围for循环的语法

4.2.范围for的使用条件

5.指针空值nullptr(C++11)


1. 引用

1.1.引用的概念

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

“取别名”的方法:类型& 引用变量名(对象名) = 引用实体。如下例所示:

#include<stdio.h>
int main()
{
	int a = 10;
	int& ra = a;//引用,ra就是a的别名
	printf("%p\n", &a);
	printf("%p\n", &ra);
	return 0;
}

 我们看到ra和a的内存空间确实是同一块。

注意:引用类型必须和引用实体是同种类型的。如上,变量a和ra的类型都是int。

1.2.引用的特性

1.引用在定义时必须初始化,就是必须说清楚是谁的别名。

2.一个变量可以有多个引用,就是说一个变量可以取多个别名。

3.引用一旦引用一个实体,再不能引用其他实体,就是说引用不能改变指向。

#include<iostream>
using namespace std;
int main()
{
	int a = 10;
	int b = 20;
	//int& ra; 这条语句在编译时会出错,因为引用在定义时没有初始化
	int& ra = a;
	int& rra = a;
	rra = b;//这里是将b赋值给a的别名rra,可不是改变rra的指向
	cout << rra << endl;
	cout << &a << endl;
	cout << &ra << endl;
	cout << &rra ;
	return 0;
}

 

1.3.引用的使用场景 


1.做参数

举个例子:

#include<iostream>
using namespace std;
void swap(int& left, int& right)
{
	int tmp = left;
	left = right;
	right = tmp;
}
int main()
{
	int a = 10;
	int b = 20;
	swap(a, b);
	cout << "a=" << a << endl;
	cout << "b=" << b;
	return 0;
}

用引用的方式接受实参,这里left就是a的别名,right就是b的别名,通过别名直接进行交换。就不用通过地址解引用间接来进行交换了。 

  


2.做返回值

 举个例子:

#include<iostream>
using namespace std;
int& Func()
{
	static int n = 10;
	n++;
	return n;
}
int main()
{
	cout << Func() ;
	return 0;
}

这里Func函数的返回值是通过引用返回的哦,返回的是n的别名。

 


 1.4.引用的易错点


1.明确概念,举个例子:

#include<iostream>
using namespace std;
int& Func()
{
	static int n = 10;
	return n;
}
int main()
{
	int& ret = Func();
	int tmp = Func();
	return 0;
}

我们都知道函数Func返回的是n的别名。那么ret和tmp分别是什么?

其实ret是n别名的别名;tmp是将n的别名的值赋给tmp。我们可以验证:

通过调试我们可以看到ret和n是同一块内存空间,tmp的空内存间却与它们不同:

再看:

#include<iostream>
using namespace std;
int& Func()
{
	static int n = 10;
	return n;
}
int main()
{
	int& ret = Func();
	int tmp = Func();
	ret++;
	cout << ret << endl;
	cout << tmp;
	return 0;
}

 

 我们看到tmp的值确实是10。而且ret++不影响tmp,因为tmp不是n的别名,也不是ret的别名,更不是n的别名的别名,所以ret++不会影响tmp。


2.如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用 引用返回,如果已经还给系统了,则必须使用传值返回。为什么这么说,我们看一个例子:

#include<iostream>
using namespace std;
int& Add(int a, int b)
{
	int c = a + b;
	return c;
}
int main()
{
	int& ret = Add(1, 2);
	Add(3, 4);
	cout << "Add(1, 2) is :" << ret << endl;
	return 0;
}

先看结果也许会出乎你的意料:

 

结果是7的原因:我们看到Add函数返回值是c的别名。但是局部变量c出了Add函数作用域之后局部变量c的内存空间使用权已经还给操作系统了,那么局部变量c的内存空间存储的数据是什么就不好说了,这是返回c的别名就是一个野引用。我们看主函数:ret是c别名的别名,但是c的别名是一个野引用。但再次调用Add函数时(Add(3,4);),系统复用了局部变量c的内存空间,导致局部变量c的内存空间存储的值时7.所以打印出来的ret是7。

解决上面问题的方法就是传值返回,而不是使用引用返回,如上代码写法是错误的!


1.5.引用的优势

作为参数或者返回类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用作为参数或者返回类型,效率是非常低下的,尤其是当参数或者返回类型非常大时,效率就更低。


1.引用做参数效率的提高

#include <time.h>
#include<iostream>
using namespace std;
struct A 
{ 
	int a[10000];
};
void TestFunc1(A a)
{

}
void TestFunc2(A& a)
{

}
int main()
{
	A a;

	// 以值作为函数参数
	size_t begin1 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc1(a);
	size_t end1 = clock();

	// 以引用作为函数参数
	size_t begin2 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc2(a);
	size_t end2 = clock();

	// 分别计算两个函数运行结束后的时间
	cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
	cout << "TestFunc2(A&)-time:" << end2 - begin2 ;
	return 0;
}

在Debug版本下结果:

 


 2.引用做返回值效率的提高

#include <time.h>
#include<iostream>
using namespace std;
struct A
{ 
	int a[10000]; 
};
A a;
// 值返回
A TestFunc1() 
{ 
	return a;
}
// 引用返回
A& TestFunc2() 
{ 
	return a; 
}
int main()
{
	// 以值作为函数的返回值类型
	size_t begin1 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc1();
	size_t end1 = clock();

	// 以引用作为函数的返回值类型
	size_t begin2 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc2();
	size_t end2 = clock();

	// 计算两个函数运算完成之后的时间
	cout << "TestFunc1 time:" << end1 - begin1 << endl;
	cout << "TestFunc2 time:" << end2 - begin2 ;
	return 0;
}

在Debug版本下结果:

 


1.6.引用和指针

引用和指针的功能是类似的、重叠的。C++的引用是对指针使用比较复杂的场景进行一些替换,让代码更加易懂,但是引用不能完全替代指针,因为:引用一旦引用一个实体,再不能引用其他实体,就是说引用不能改变指向。

引用和指针的区别:

1.在语法层面:

  • 引用是别名,不开空间;指针是地址,需要开空间存地址。(要注意语法层面来说引用不开空间,但是在底层其实开了空间,因为引用的语法含义和底层实现是背离的,引用底层是用指针实现的)
  • 引用定义后必须初始化,指针可以初始化也可以不初始化。
  • 引用一旦引用一个实体,再不能引用其他实体,就是说引用不能改变指向,但指针可以。
  • 引用相对更安全。没有空引用,但有空指针;容易出现野指针,不容易出现野引用。

2.在底层:在汇编层面上,没有引用,都是指针,引用编译后也转换成指针了。


学完引用,看看能不能看明白这个代码:

typedef struct Node
{
	struct Node* next;
	struct Node* prev;
	int val;
}Node, * PNode;
void PushBack(PNode& phead, int x)
{
	//…………
}
int main()
{
	PNode plist = nullptr;
	PushBack(plist, 10);
	return 0;
}

首先:struct Node被typedef成Node,struct Node*被typedef成PNode。 

主函数:定义了一个类型是PNode的指针变量plist,并初始化成空指针。将plist和10作为实参传入PushBack。

PushBack函数:phead是plist的别名,phead类型是PNode(也是struct Node*)。


2.内联函数

引子:如果有一个函数要频繁调用100w次,那么需要建立100w次栈帧。如何解决这个问题呢?


C语言给出的答案就是宏。说起宏,如何用宏实现下面代码?

int Add(int a, int b)
{
	return a + b;
}

答案如下:

#define Add(a,b) ((a)+(b))

 用宏实现函数功能要注意下面几点:

  • 宏是一种替换:宏在预处理阶段进行了替换。
  • 宏不是函数,没有返回值和参数。
  • 必须用括号控制好运算的优先级
  • 宏定义在行末不加分号。

宏的优缺点:

 优点: 1.增强代码的复用性。 2.提高性能。

缺点: 1.不方便调试宏。(因为预编译阶段进行了替换) 2.导致代码可读性差,可维护性差,容易误用。 3.没有类型安全的检查 。4.语法复杂,不好控制。


而C++给出的答案是内联函数,可以避免宏的缺陷。

2.1.内联函数的概念

以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调 用建立栈帧的开销,内联函数提升程序运行的效率。

像这样:

inline int Add(int a, int b)
{
	return a + b;
}
int main()
{
	int sum = Add(1, 2);
	return 0;
}

如上述Add函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的 调用。

既然C++有内联函数的概念,我们是不是将所有函数都加上inline,这样不就没有栈帧的消耗了吗?肯定不行,因为:

2.2.内联函数的特性

1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会 用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运 行效率。

2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。就是说内联说明只是向编译器发出一个请求,编译器可以选择忽略这个请求。

3.内联函数声明和定义不可分离。分离会导致链接错误。因为inline被展开,就没有函数地址 了,链接就会找不到。

3. auto关键字(C++11)

3.1.auto的概念

C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一 个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。

说简单点,auto作用就是自动推导变量类型的,如:

#include<iostream>
using namespace std;
int main()
{
	double a = 13.14;
	auto b = a;
	auto c = 1;
	auto d = 'D';
	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() ;
	return 0;
}

简单介绍一下,typeid().name()可以获取变量类型,用法如代码。我们看看结果:

 

 3.2.auto的使用细则


1.使用auto定义变量时必须对其进行初始化

#include<iostream>
using namespace std;
int main()
{
	auto a;//error C3531: “a”: 类型包含“auto”的符号必须具有初始值设定项
	return 0;
}

不初始化无法通过编译。


2.auto与指针和引用结合起来使用 

用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&

#include<iostream>
using namespace std;
int main()
{
	double a = 13.14;
	auto& b = a;
	auto c = &a;
	auto* d = &a;
	cout << &a << endl;
	cout << &b << endl;
	cout << c << endl;
	cout << d;
	return 0;
}

变量a和b的内存空间是同一块,也就是指针变量c或者d所指向的那块。

 


3.在同一行定义多个变量

当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译 器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

void TestAuto()
{
    auto a = 1, b = 2; 
    auto c = 3, d = 4.0;  // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}

4.auto不能作为函数的参数 

auto不能作为形参类型,因为编译器无法对a的实际类型进行推导

#include<iostream>
using namespace std;
// error C3533: 参数不能为包含“auto”的类型
int Add(auto a, auto b)
{
	return a + b;
}
int main()
{
	int i=Add(1, 2);
	return 0;
}

5.auto不能直接用来声明数组

int main()
{
	int a[] = { 1,2,3 };
	auto b[] = { 4,5,6 };//错误写法
	return 0;
}

4.基于范围的for循环(C++11)

4.1.范围for循环的语法

C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。 

意思是依次取数组元素的值赋值给范围内用于迭代的变量,自动迭代,自动判断结束。

举个栗子:

#include<iostream>
using namespace std;
int main()
{
	int array[] = { 1, 2, 3, 4, 5 };
	for (auto& e : array)
		e *= 2;

	for (auto e : array)
		cout << e << " ";
	return 0;
}

 注意:第一个for循环我们期待能改变(*2)数组元素,所以得使用引用。

 

当然与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环 。

4.2.范围for的使用条件

1. for循环迭代的范围必须是确定的

对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供 begin和end的方法,begin和end就是for循环迭代的范围。

以下代码的for循环迭代的范围就是不确定的,是错误的写法:

void TestFor(int array[])
{
    for(auto& e : array)
        cout<< e <<endl;
}

因为array实质是一个指针,而不是数组。

2.迭代的对象要实现++和==的操作。

5.指针空值nullptr(C++11)

C语言当中用NULL表示空指针,而C++用nullper表示空指针!

注意:

1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。

2.. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。

3.为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。 

感谢阅读!

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

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

相关文章

B端UI设计,演绎高情逸态之妙

B端UI设计&#xff0c;演绎高情逸态之妙

汽车IVI中控开发入门及进阶(二十三):i.MX8

前言: IVI市场的复杂性急剧增加,而TimeToMarket在几代产品中从5年减少到2-3年。Tier1正在接近开放系统的模型(用户可以安装应用程序),从专有/关闭源代码到标准接口/开放源代码,从软件堆栈对系统体系结构/应用层/系统验证和鉴定的完全所有权,越来越依赖第三方中间件和平…

STM32自己从零开始实操03:输出部分原理图

一、继电器电路 1.1指路 延续使用 JZC-33F-012-ZS3 继电器&#xff0c;设计出以小电流撬动大电流的继电器电路。 &#xff08;提示&#xff09;电路需要包含&#xff1a;三极管开关电路、续流二极管、滤波电容、指示灯、输出部分。 1.2数据手册重要信息提炼 联系排列&…

Rainbond 携手 TOPIAM 打造企业级云原生身份管控新体验

TOPIAM 企业数字身份管控平台&#xff0c; 是一个开源的IDaas/IAM平台、用于管理账号、权限、身份认证、应用访问&#xff0c;帮助整合部署在本地或云端的内部办公系统、业务系统及三方 SaaS 系统的所有身份&#xff0c;实现一个账号打通所有应用的服务。 传统企业 IT 采用烟囱…

NSS题目练习5

[NISACTF 2022]babyupload 打开后尝试上传php&#xff0c;jpg&#xff0c;png文件都没成功 查看源代码发现有个/source文件 访问后下载压缩包发现有一个python文件 搜索后知道大致意思是&#xff0c;上传的文件不能有后缀名&#xff0c;上传后生成一个uuid&#xff0c;并将uuid…

redis缓存token设置jwt令牌过期时间

登录接口 在上文中 我们已经设置了自定义登录接口自定义拦截器jwt登录校验接口模拟账号登录_jwt自定义拦截器-CSDN博客https://blog.csdn.net/2202_75352238/article/details/138424691?spm1001.2014.3001.5501 但是上文jwt过期时间是由yml文件中配置的&#xff0c;比较不优雅…

Amis源码构建 sdk版本

建议在linux环境下构建&#xff08;mac环境下也可以&#xff09;&#xff0c;需要用到sh脚本&#xff08;amis/build.sh&#xff09;。 Js sdk打包是基于fis进行编译打包的&#xff0c;具体可见fis-conf.js&#xff1a; amis-master源码下载:https://github.com/baidu/amis g…

【OceanBase诊断调优】—— obdiag 工具助力OceanBase数据库诊断调优(DBA 从入门到实践第八期)

1. 前言 昨天给大家分享了【DBA从入门到实践】第八期&#xff1a;OceanBase数据库诊断调优、认证体系和用户实践 中obdiag的部分&#xff0c;今天将其中的内容以博客的形式给大家展开一下&#xff0c;方便大家阅读。 2. 正文 在介绍敏捷诊断工具之前&#xff0c;先说说OceanBa…

【C语言】常见的动态内存的错误

前言 在动态内存函数的使用过程中我们可能会遇到一些错误&#xff0c;这里将常见的错误进行总结。 对NULL解引用 请看以下代码&#xff1a; 可以看到&#xff0c;这时我们的malloc开辟是失败的&#xff0c;所以返回的是空指针NULL&#xff0c;而我们却没有进行检查&#xff0…

使用PNP管控制MCU是否需要复位

这两台用到一款芯片带电池&#xff0c;希望电池还有电芯片在工作的时候插入电源不要给芯片复位&#xff0c;当电池没电&#xff0c;芯片不在工作的时候&#xff0c;插入电源给芯片复位所以使用一个PNP三极管&#xff0c;通过芯片IO控制是否打开复位&#xff0c;当芯片正常工作的…

反激电源压敏电阻设计

压敏电阻的作用&#xff1a;浪涌防护。在电源出现浪涌冲击时&#xff0c;保护核心器件不受到损坏。其实类似于稳压二极管 瞬间的瞬态波 1 压敏电压 单位是&#xff0c;虽然压敏电阻可以吸收很大的浪涌能量&#xff0c;但是不能承受mA以上的持续电流。压敏电压计算公式 2 通流容…

(函数)字符串拼接(C语言)

一、运行结果&#xff1b; 二、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h> # include <string.h>//声明字符串拼接函数&#xff1b; void splice(char a[100], char b[100]);int main() {//初始化变量值&#xff1b;char a[100] …

unity打包的WebGL部署到IIS问题

部署之后会出错&#xff0c;我遇到的有以下几种&#xff1b; 进度条卡住不动 明明已经部署到了IIS上&#xff0c;为什么浏览网页的时候还是过不去或者直接报错。 进度条卡住不动的问题其实就是wasm和data的错误。 此时在浏览器上按F12进入开发者模式查看错误&#xff08;下图…

【前端】Vuex笔记(超详细!!)

最近花了两周时间&#xff0c;完完全全的跟着Vuex官方的视频学完了Vuex并且详详细细的做了笔记&#xff0c;其中总结部分是我对于整个视频课程的总结&#xff0c;视频部分是跟着视频做的笔记&#xff0c;如果总结部分有不懂的话&#xff0c;直接去视频部分查找对应的笔记即可&a…

uniapp的tooltip功能放到表单laber

在uniapp中&#xff0c;tooltip功能通常是通过view组件的hover-class属性来实现的&#xff0c;而不是直接放在form的label上。hover-class属性可以定义当元素处于hover状态时的样式类&#xff0c;通过这个属性&#xff0c;可以实现一个类似tooltip的效果。 以下是一个简单的例…

9.Halcon3D点云力矩求解-平面拟合用法

1.实现效果 我们在使用3d相机对产品进行扫描生成点云的时候,由于安装问题,所以我们不可能保证每次产品扫描出来都在坐标系中位置和姿态非常标准。 上述算法描述的就是在某一个维度或者某几个维度上将点云数据和坐标系对齐; 至于怎么对齐,如何实现就是今天的内容。 本人能…

热电子光探测器的电磁场空间分布与FDTD材料折射率的导出

仿真实例 金属薄膜中金纳米孔阵列透射与反射&#xff0c; 并考虑其近场电磁分布 利用脚本进行电磁场及其光学响应的可视化 设置EOT型超表面结构&#xff0c;以及Structure library的使用 结构的参数化扫描与结果可视化 利用脚本计算峰值增强因子 多层平面结构激发T…

如何打造智能客服机器人的“超级大脑”?

一、引言 在数字化浪潮的推动下,智能客服机器人已成为企业服务升级、提升客户满意度的关键工具。然而,要让智能客服机器人真正发挥其在客户服务中的优势,打造其“超级大脑”至关重要。本文将深入探讨如何进一步提升智能客服机器人的智能水平,并结合其在多业务场景中面临的挑…

去掉el-table表头右侧类名是gutter,width=17px的空白区域(包括表头样式及表格奇偶行样式和表格自动滚动)

代码如下&#xff1a; <el-table:data"tableData"ref"scroll_Table":header-cell-style"getRowClass":cell-style"styleBack"height"350px"style"width: 100%"><el-table-column prop"id" l…

算法思想总结:哈希表

一、哈希表剖析 1、哈希表底层&#xff1a;通过对C的学习&#xff0c;我们知道STL中哈希表底层是用的链地址法封装的开散列。 2、哈希表作用&#xff1a;存储数据的容器&#xff0c;插入、删除、搜索的时间复杂度都是O&#xff08;1&#xff09;&#xff0c;无序。 3、什么时…