C++入门基础(下)

news2024/11/19 7:28:26

目录

引用

引用概念

引用特性

1.引用在定义时必须初始化

2.一个变量可以有多个引用

3.引用一旦引用一个实体,再不能引用其他实体.

常引用

使用场景

1.作为参数使用

2.作为返回值使用

引用和指针的区别

内联函数

内联函数的概念

内联函数特性

宏的优缺点

auto关键字

auto简介

auto使用的细则

auto不能使用的场景

基于范围的for循环

语法

使用条件

指针空值nullptr

C++98中的指针空值
 

引用

引用概念

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

一个人可以有多种称呼

比如:李逵 ,在家被称为“铁牛”,在江湖上被称为:”黑旋风“.

但无论哪一种称呼都是指李逵本身这个人.

用法如下:

类型& 引用变量名 = 引用实体

举个例子:

输入以下代码:

#include<iostream>
using namespace std;

int main()
{
	int a = 5;
	int& ra = a;//对a取了个别名 ra,它们公用一块内存空间
	cout << &a << endl;//输出a的地址
	cout << &ra << endl;//输出b的地址

	return 0;
}

注意:引用类型必须和实体类型是同种类型的.

我们看输出结果:

94a62932b9be4373a825b200fa01fd5f.png

它们的地址一样,说明它们确实指向了同一块内存空间,内容相同.

所以说如果改变ra的值,a的值也会随之改变.

引用特性

1.引用在定义时必须初始化

2.一个变量可以有多个引用

3.引用一旦引用一个实体,再不能引用其他实体.

我们逐个来解释说明.

1.引用在定义时必须初始化

我们平常定义变量的时候,例如int a;char c;int* p...等等,都可以不用初始化,就是说不用给初值,但是引用初始化必须给初值.

int main()
{
	int a = 5;
	int& ra = a;
	int& rb;//错误,没有给初值
	return 0;
}

2.一个变量可以有多个引用

意思是一个人可以有多个外号,就像上面举的那个例子.

变量也是同样的道理,一个变量可以有多个别名.

int main()
{
	int a = 5;
	int& ra = a;
	int& rb = a;
	int& rc = a;
	cout << ra << endl;
	cout << rb << endl;
	cout << rc << endl;
	return 0;
}

输出结果如下:

aa11d95bf95d4436a0636f55f33cbfe6.png

3.引用一旦引用一个实体,再不能引用其他实体.

这个意思是比如你给李逵起了一个黑旋风的外号,这个外号以后就只能属于李逵他自己了,不能再把黑旋风这个外号给别人.

例如有两个变量a和变量b,我们给a起一个别名是ra,这个时候你就不能再把ra这个外号给b了.


int main()
{
	int a = 5;
	int b = 10;
	int& ra = a;
	ra = b;//千万注意!这里不是将b变成a的别名ra,而是将ra的值即a赋值为b.也就是说别名没有变,依然是a的别名,但是a(和别名)的内容变化了,变成了b
	&ra = b;//按道理来说,这样才是修改a的别名,但这样并无法编译,所以无法修改别名
	return 0;
}

常引用

这里会涉及一些权限的平移、放大与缩小问题.

我们知道,被const修饰的变量不可以被修改,相当于变成了只读权限了,不可以被写了.相当于权限变小了.

而我们平常不被const修饰的变量,既可以被修改,也可以被读取。所以它的权限比较大.

权限只可以被缩小和平移,不可以被放大!!!

这里还需要补充一点:在我们发生类型转化的时候,比如 int b = 0;double a = b;

编译器会先产生b的一份临时拷贝tmp(通过整型提升,类型为double),而临时常量具有常性,相当于tmp的类型为const double,再将tmp的值赋值给a.

int main()
{
	int a = 5;
	int& ra = a;//a的类型为int,ra的类型也为int,权限没有变化,可以平移,所以没有问题

	const int b = 10;
	//int& rb = &b;//错误b的类型为const int,而rb的类型为int,由于int权限大于const int,权限不可以被放大,所以错误
	const int& rb = b;//正确,此时rb的类型也为const int,权限可以平移

	int d = 15;
	double& rd = d;//错误,d先产生一份const double类型的临时变量,由于double权限 > const double,所以权限放大,错误
	const double& rdd = d;//正确
	return 0;
}

使用场景

1.作为参数使用

void Swap(int& left, int& right)
{
    int temp = left;
    left = right;
    right = temp;
}

因为参数是引用,所以他就是相当于实参的别名,所以如果交换,就切切实实交换了两个实参的值,而不是形参.

2.作为返回值使用

传引用返回:实质上是返回返回对象的别名

int& Count()
{
    static int n = 0;
    n++;
    // ...
    return n;
}

对于静态或全局变量,返回值可以用引用作为返回值,直接返回它本身,而不用再产生一份拷贝了.

但若不是全局变量会出现什么问题呢?

int& Count()
{
    int n = 0;
    n++;
    // ...
    return n;
}
int main()
{
    int ret = Count();
    cout << ret << endl;
}

先来看结果:

814c33cd4b8140409c47c08a1c98cf35.png

有的同学就说了,这是1啊,没有问题啊.

其实这是一种侥幸,进入函数之后,n++,此时n变成1.返回n的别名给ret

但仔细想一下,当把n的别名给ret的时候,是不是函数已经结束了!函数结束是不是临时变量就被回收了!

但是为什么结果是1呢?

因为此时编译器还没有清理或者修改增加别的新的内容. 里面的内容依然是1.

我们拿个例子来解释一下:你去住酒店,要住一晚上,然后第二天走了之后发现你的钱包和一些东西落在里面了,这个时候你赶紧返回去拿,发现钱包什么的都还在,你就拿到了它.这就是侥幸.

如果其中来了其他人或者保洁阿姨清理了这些东西呢?这个时候你也就拿不到了,所以说刚才编译器那次拿到这个1也是“侥幸”.

那么这次就没这么”侥幸“了.

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;
}

我们来看输出结果:

93083351fda745f8a7708dc57a7a2a6e.png

诶?ret不是Add(1,2),是1和2相加结果不应该是3吗?怎么会是7呢?

这次就没这么侥幸了.

首先第一次Add(1,2)返回了3,即把这个3留在了酒店.但此时3的主人已经走了.

后面又来了一个Add(3,4)即7,替代了这个3

这个时候3的主人回来再取,取到的已经不是原来它的东西了,只能是7了.这就造成了错误.

所以注意:

注意:如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。

引用和指针的区别

语法概念上,引用就是一个别名,没有独立空间。和其引用实体公用一块空间.

底层实现上,引用其实是有空间的,因为引用是按照指针的方式来实现的.

⭐引用和指针不同点:

1.引用在定义时必须初始化,指针没有要求

2.引用在初始化时引用一个实体后,就不能再引用其它实体,而指针可以在任何时候指向任何同一个类型的实体.

3.没有NULL引用,但有NULL指针

4.在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占的字节个数(32位平台下占4个字节)

5.引用自加即引用的实体加1,指针自加即指针向后偏移一个类型的大小.

6.有多级指针,但没有多级引用.

7.访问实体方式不同,指针需要显式解引用,引用则编译器自己处理.

8.引用比指针使用起来更加安全.

内联函数

内联函数的概念

以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,从而不会有函数压栈的开销,提高运行效率.

看下图:

在Debug模式下,我们转到反汇编来看一下

6751cdbee8a2493fa5cf292f87145f1f.png

 可以看到call这个命令,这个其实就是在调用函数,说明此时并没有展开.

在Release模式下我们再试一下:

4778da746770446aa0fd9fe5a6ddd31a.png

 可以发现Add函数被展开了,并没有call函数.直接进行相加操作了.

内联函数特性

1. inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数

2. inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联。(这条很重要,意味着你写inline但编译器不一定会展开,会根据代码的长短进行决断)

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

下面这段代码演示了这个定义与声明分离的问题:

//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()
{
f(10);
return 0;
}
/// 链接错误:main.obj : error LNK2019: 无法解析的外部符号 "void __cdecl f(int)" (?f@@YAXH@Z),该符号在函数 _main 中被引用

所以最好声明和定义写在一起.

宏的优缺点

优点:1.提高代码的复用性

2.提高性能

缺点:

1.不方便调试(预编译阶段进行了替换)

2.使代码可读性变差,可维护性差,容易误用

3.没有安全类型检查        

C++有哪些技术可以替换宏?

1. 常量定义 换用const
2. 函数定义 换用内联函数

auto关键字

auto简介

在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,但遗憾的是一直没有人去使用它,大家可思考下为什么?
C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。

总而言之就是:auto会自动推导变量的类型,而不用自己手动去写.通常类型名较长的时候用auto替代(或者自己也不知道变量是什么类型(doge))

看以下代码:

int main()
{
	auto a = 1;
	auto b = 1.5;
	auto c = 'c';

	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	return 0;
}

运行结果如下:

f70707ef41bd41ccba66433ac9d2f510.png

可以看到auto已经成功推导出来了变量的类型. 

需要注意的是:

使用auto定义变量时必须对其进行初始化在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型

auto使用的细则

1. auto与指针和引用结合起来使用
用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&

d9d71104cb2c4e47baee3965bc65b4b5.png

可以看到,无论auto加不加*,对于指针类型,结果都是一样的.

但引用必须加上&

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

363c20748e3046078d19e5f35ef5f65b.png

 可以看到同一行类型不同并不能编译通过.

auto不能使用的场景

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

// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}

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

bfbace9a0c204f20a89ae631d90a9f64.png

3. 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法
4. auto在实际中最常见的优势用法就是跟以后会讲到的C++11提供的新式for循环,还有lambda表达式等进行配合使用

基于范围的for循环

语法

如果想要遍历一个数组,我们有以下两种方式

void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
array[i] *= 2;
for (int* p = array; p < array + sizeof(array)/ sizeof(array[0]); ++p)
cout << *p << endl;
}

对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围

可以改为以下代码:

void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for(auto& e : array)
e *= 2;

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

是不是非常简便.

注意:与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环

使用条件

1. for循环迭代的范围必须是确定的
对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。
注意:以下代码就有问题,因为for的范围不确定

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

2. 迭代的对象要实现++和==的操作。(关于迭代器这个问题,以后我会说明,现在大家了解一下就可以了)

指针空值nullptr

C++98中的指针空值

在良好的C/C++编程习惯中,声明一个变量时最好给该变量一个合适的初始值,否则可能会出现不可预料的错误,比如未初始化的指针。如果一个指针没有合法的指向,我们基本都是按照如下方式对其进行初始化:

void TestPtr()
{
int* p1 = NULL;
int* p2 = 0;
// ……
}

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

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

可以看到,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,比如

void f(int)
{
cout<<"f(int)"<<endl;
}
void f(int*)
{
cout<<"f(int*)"<<endl;
}
int main()
{
f(0);
f(NULL);
f((int*)NULL);
return 0;
}

程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。
在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。

需要注意的是:

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

2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr.

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

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

相关文章

scala spark dataframe 时间加减

参考Adding 12 hours to datetime column in Spark 只针对标准化时间戳 yyyy-MM-dd HH:mm:ss 如果是 yyyy-MM-dd HH:mm 转换后会自动补到 HH:mm:ss ss位补0 时间英文简写查询 HOUR 代表小时 MINUTE 代表分钟 SECOND 代表秒 DAY MONTH YEAR 正数代表向后 负数代表向前 …

AI绘画突然爆火?快速体验二次元画师NovelAI(diffusion)

目录0 写在前面1 diffusion vs GAN2 NovelAI3 AI绘画环境搭建4 体验AI创作0 写在前面 机器学习强基计划聚焦深度和广度&#xff0c;加深对机器学习模型的理解与应用。“深”在详细推导算法模型背后的数学原理&#xff1b;“广”在分析多个机器学习模型&#xff1a;决策树、支持…

到了30岁,我才有了深刻的感悟:千万不要一辈子靠技术生存

千万不要一辈子靠技术生存&#xff0c;这句话&#xff0c;我也是到了快30岁才有了深刻认知。 当我20多岁&#xff0c;年收入2-3W的时候&#xff0c;我会认为说这话的人都是自身技术不咋地&#xff0c;想靠技术吃饭吃不了。 然而&#xff0c;快30岁了&#xff0c;虽然技术小有…

【Java】之IO流

个人主页&#xff1a;天寒雨落的博客_CSDN博客-C,CSDN竞赛,python领域博主 特别标注&#xff1a;仅为自己的学习记录笔记&#xff0c;方便复习和加深记忆&#xff0c;仅供借鉴参考&#xff01; 目录 IO流概述 IO流分类 按数据的流向 按数据类型 字符流 字节流 字节流写数…

【Linux】虚拟机安装Ubuntu后的一些通用设置

文章目录前言一、虚拟机缩放设置二、实现本机和虚拟机之间复制粘贴共享三、ubuntu中vi文件时方向键等问题四、虚拟机扩容五、时区和时间格式设置六、防火墙相关七、中文输入法问题八、虚拟机和主机之间的互通前言 主要是记录虚拟机中安装ubuntu后一些常规设置操作。 一、虚拟…

当你使用MPLS时,不要忘记还有SD-WAN!

企业网络管理人员和IT部门主管在考虑其WAN架构时最常见的问题就是&#xff1a;“为什么我要选择SD-WAN而不是MPLS&#xff1f;”确实&#xff0c;选择新技术时通常会带来“不确定性”。 与MPLS相比&#xff0c;SD-WAN更便宜&#xff0c;性能更强&#xff0c;也带来了更低成本的…

IDEA安装及Clone代码

IDEA安装及Clone代码 文章目录1.IDEA下载2.IDEA安装3 IDEA clone(克隆) 代码1.IDEA下载 官网下载地址&#xff1a; DEA 分为两个版本&#xff1a; 旗舰版(Ultimate)和社区版(Community)。 旗舰版&#xff1a;收费(限 30 天免费试用)&#xff0c;功能全面&#xff0c;插件丰富…

公众号查题系统搭建

公众号查题系统搭建 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 查题校园题库&#xff1a;查题校园题库后台&#xff08;点击…

刚来的00后真的卷,听说工作还没两年,跳到我们公司直接起薪20k...

前段时间我们公司来了个00后&#xff0c;工作都没两年&#xff0c;跳槽到我们公司起薪18K&#xff0c;都快接近我了。后来才知道人家是个卷王&#xff0c;从早干到晚就差搬张床到工位睡觉了。 最近和他聊了一次天&#xff0c;原来这位小老弟家里条件不太好&#xff0c;一大家子…

c++内存管理:

目录 new和delete 使用方法&#xff1a; 注意事项&#xff1a; new申请不需要检查返回值 operator new和operator delete函数的讲解 c语言申请内存有哪些方法&#xff1a; 答&#xff1a;malloc calloc realloc三种 #include<stdlib.h> void test() {int*p1 (in…

Day11-尚品汇-退出登录

1.在Header组件里面&#xff1a; 1》绑定一个click事件 2》写其触发的方法 2.发请求通知服务器 1》先观察文档 2》.在api里面写代码&#xff1a; 3》在store仓库user.js里面也要写代码&#xff1a; 1&#xff09; 不单单向服务器发请求清除token&#xff0c;而且需要清除use…

【MLOPs】Docker

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

Python基础加强学习

一、python概述 1. python的应用领域 web开发大数据处理人工智能自动化运维开发云计算爬虫游戏开发 2. 安装python 要进行python开发&#xff0c;首先要安装python解释器&#xff0c;这里说的安装python说的就是安装python的解释器。 测试python是否安装成功&#xff0c;在…

基于springboot的校园二手网站

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

过滤器和拦截器的区别

目录 1 前言 2 区别 2.1 实现原理不同 2.2 使用范围不同 2.3 执行顺序不同 4 注入Bean的情况不同 1 前言 可能有些小伙伴们在接手公司的项目时&#xff0c;经常看到公司的项目中既有过滤器又有拦截器&#xff0c;那么它们既然都拦截的作用&#xff0c;那么各自扮演着什么…

pyinstaller打包出错记录

稍微记录一下最近在liunx上pyinstaller打包出错 目录稍微记录一下最近在liunx上pyinstaller打包出错1 号坑 Python3.7.0安装2号坑 成功打包但是执行失败小结后面代码的环境是在Windows子系统下的Ubuntu 20.04下进行的。vscode可以通过&#xff0c;配置WSL来进入环境&#xff08…

Pytorch+Python实现人体关键点检测

用PythonPytorch工程代码对人体进行关键点检测和骨架提取&#xff0c;并实现可视化。 使用背景&#xff1a; 物体检测为许多视觉任务提供动力&#xff0c;如实例分割、姿态估计、跟踪和动作识别。它在监控、自动驾驶和视觉答疑中有下游应用。当前的对象检测器通过紧密包围对象…

深度学习提高模型准确率方法

这里写目录标题深度学习数据使用更多数据更改图像大小减少颜色通道算法模型改进增加训练轮次迁移学习添加更多层调整超参数总结深度学习 我们已经收集好了一个数据集&#xff0c;建立了一个神经网络&#xff0c;并训练了模型&#xff0c;在测试和验证阶段最后得到的准确率不高…

8086通用寄存器

目录 概述 EU&#xff1a;负责执行指令完成两种操作&#xff1a;算数逻辑运算&#xff0c;计算存储器操作数的偏移地址 BIU&#xff1a;完成所有的总线操作 寄存器 AX BX CX DX SP,BP,SI,DI IP CS&#xff0c;DS&#xff0c;SS&#xff0c;ES 概述 8086和8088C…

神经网络每次结果不一样,神经网络预测问题

1、求助&#xff1a;神经网络两次训练的结果不一样 神经网络两次训练的结果不一样&#xff0c;这是因为每次训练的迭代初值不相同&#xff08;是随机的&#xff09;&#xff0c;所以得到的结果是有差异的。一般的话&#xff0c;软件开启第一次时&#xff0c;运行得到结果是比较…