【C++初探:简单易懂的入门指南】二

news2024/12/28 19:04:21

【C++初探:简单易懂的入门指南】二

  • 1.引用
    • 1.1引用做函数的参数
    • 1.2 引用做返回值
      • 1.2.1 关于引用做返回值的几点补充
    • 1.3 多引用(对一个变量取多个别名)
    • 1.4 引用类型一致性原则以及权限的问题阐述
    • 1.5引用的效率问题
    • 1.6引用和指针的比较
  • 2.auto关键字
    • 2.1 auto关键字的使用细则
    • 2.2 auto关键字不能使用的场景
  • 3.特殊的for循环(基于范围)
    • 3.1基于范围for的语法
    • 3.2 基于范围for的使用规则

❤️博客主页: 小镇敲码人
🍏 欢迎关注:👍点赞 👂🏽留言 😍收藏
🌞任尔江湖满血骨,我自踏雪寻梅香。 万千浮云遮碧月,独傲天下百坚强。 男儿应有龙腾志,盖世一意转洪荒。 莫使此生无痕度,终归人间一捧黄。🍎🍎🍎
❤️ 什么?你问我答案,少年你看,下一个十年又来了 💞 💞 💞

在这里插入图片描述

1.引用

引用是C++与C不同的点之一,它虽然是给变量取别名,算不上一个新定义的概念,但是它和typedef的区别还是存在的,例如它可以在函数做参数和返回值时使用,但是typedef没有这种功能,&是一个操作符,表示引用,你可以理解为要给一个变量取别名。

1.1引用做函数的参数

下面我们给出一段代码帮助你理解引用的最常见的功能:

#include<iostream>
using namespace std;

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

int main()
{
	int a = 3;
	int b = 4;
	int c = Add(a, b);
	cout << "a+b = " << c << endl;
	return 0;
}

运行结果截图:

在这里插入图片描述
上面Add函数的两个参数就是实参ab的别名,就相当于我们人类社会里的绰号,就比如你叫张三,你的同学可能叫你老张,你的家里人可能叫你三儿,虽然叫法不同,但是它们都代表你这个人,而且引用是不额外开空间的,我们可以利用下面的代码简单的验证一下:

#include<iostream>
using namespace std;

void Fun(int& b)
{
	cout <<  "b的地址为:" << &b << endl;
}

int main()
{
	int a = 3;
	Fun(a);
	cout << "a的地址为:" << &a << endl;
	return 0;
}

运行截图为:
在这里插入图片描述
可以看到这里引用是不开空间的,因为ba的别名,所以编译器不会给它们开两份空间。

1.2 引用做返回值

#include<iostream>
using namespace std;

int& Add(int& a, int& b)
{
	static int c = a + b;
	cout << "c的值为" << c << endl;
	cout << "c的地址为" << &c << endl;
	return c;
}

int main()
{
	int a = 3;
	int b = 4;
	int& d = Add(a, b);
	cout << "d的值为" << d << endl;
	cout << "d的地址为" << &d << endl;
	return 0;
}

运行结果:

在这里插入图片描述
这里d就是c的别名,所以它们的地址是一样的,static修饰c,是因为临时变量开在栈区,出了函数的作用域,它就销毁了,但是如果加了staticc就变成了静态变量,静态变量的空间是开在静态区的,程序的结束,它的生命周期才算结束,至于为什么d作为c的别名,在外面还可以访问,可以类比,函数以引用传参理解,这里博主认为引用扩大了c的的作用域,只要c的生命周期没结束,它以引用返回,在main函数里面我们就是能访问到c
如果你不相信,我们可以通过如下代码简单的验证一下:

#include<iostream>
using namespace std;

int& Add(int& a, int& b)
{
	static int c = a + b;
	cout << "c的值为" << c << endl;
	cout << "c的地址为" << &c << endl;
	return c;
}

int main()
{
	int a = 3;
	int b = 4;
	int& d = Add(a, b);
	d++;
	Add(a, b);
	return 0;
}

运行结果截图:

在这里插入图片描述

  • 这里注意一点,由于c开在静态区,只要程序不结束,它的空间一直不会被系统回收,所以定义c那部分代码是不会重新执行一次的,这恰好可以帮助我们验证以引用返回,就可以在main函数里面访问原本作用域在Add函数里面的静态变量c

1.2.1 关于引用做返回值的几点补充

细心的朋友可能会在vs2019上发现这样的问题,上述代码,即使不加static似乎也能正常运行。
在这里插入图片描述
这里博主认为是编译器检查机制的一个漏洞,系统没有将空间及时的回收,如果我把代码改成这样,系统把空间回收使用后,d的值就变成随机值了,

在这里插入图片描述

但是如果你加了static就不会出现这种问题:

在这里插入图片描述

1.3 多引用(对一个变量取多个别名)

在C++中我们是支持对一个变量进行多次引用的:

#include<iostream>
using namespace std;

int main()
{
	int a = 3;
	int& b = a;
	int& c = a;
	int& d = a;
	cout << "b的地址为:" << &b << endl << "b的值为" << b << endl;
	cout << "c的地址为:" << &c << endl << "c的值为" << c << endl;
	cout << "d的地址为:" << &d << endl << "d的值为" << d << endl;
	c = 2;
	cout << "b的地址为:" << &b << endl << "b的值为" << b << endl;
	cout << "c的地址为:" << &c << endl << "c的值为" << c << endl;
	cout << "d的地址为:" << &d << endl << "d的值为" << d << endl;
	return 0;
}

运行结果截图:

在这里插入图片描述
而且由于引用只是取别名,本质上它们是同一个变量,所以修改一个就修改了它们所有的值。

1.4 引用类型一致性原则以及权限的问题阐述

上述代码如果加上这样一行就会报错:

#include<iostream>
using namespace std;

int main()
{
	int a = 3;
	int& b = a;
	double& c = a;
	int& d = a;
	cout << "b的地址为:" << &b << endl << "b的值为" << b << endl;
	cout << "c的地址为:" << &c << endl << "c的值为" << c << endl;
	cout << "d的地址为:" << &d << endl << "d的值为" << d << endl;
	c = 2;
	cout << "b的地址为:" << &b << endl << "b的值为" << b << endl;
	cout << "c的地址为:" << &c << endl << "c的值为" << c << endl;
	cout << "d的地址为:" << &d << endl << "d的值为" << d << endl;
	return 0;
}

报错截图:

在这里插入图片描述
所以我们在定义引用时,不能改变原变量的类型。
关于权限的问题,主要围绕const这个关键词展开:

  • const修饰的变量,当对其引用时,不能不加const,因为其是不可修改的常量,不加const是对其权限的放大,编译器是不允许的。
  • 但是如果一个变量没有被const修饰,在引用时,可以加上const,进行权限的缩小,这个编译器是允许的。
    在这里插入图片描述
    但是缩小权限又是允许的:
    在这里插入图片描述
    在这里插入图片描述
    这里有个比较奇怪的现象,为什么我const修饰b这个别名,b不能修改,我却可以通过修改a来修改a的值,与此同时b的值也被修改了,这里本博主也比较疑惑,大家可以在评论区或者私信来教教博主。
  • 关于引用还有一点需要说明,引用必须给初始值,引用的对象可以是全局变量、临时变量、但不能是常量(const修饰的变量例外)。

1.5引用的效率问题

引用的效率是很高的,因为它不会额外的去开空间,下面两段代码希望可以帮助你来理解:

  1. 传值和引用传参的效率比较
#include<iostream>
using namespace std;
#include <time.h>

struct A
{
	int a[100000];
};
void TestFunc1(A a) {}
void TestFunc2(A& a) {}
void TestRefAndValue()
{
	A a;
	// 以值作为函数参数
	size_t begin1 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc1(a);
	size_t end1 = clock();
	// 以引用作为函数参数
	size_t begin2 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc2(a);
	size_t end2 = clock();
	// 分别计算两个函数运行结束后的时间
	cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
	cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}

int main()
{
	TestRefAndValue();
	return 0;
}

运行截图:

在这里插入图片描述
这里clock函数是表示当前程序运行的时间,单位是毫秒,可以看到,传值和传引用的效率差的还是很大。

  1. 传值返回和传引用返回的效率比较
include<iostream>
using namespace std;
#include <time.h>

struct A
{
	int a[100000];
};
A a;
//传值返回
A TestFunc1() 
{
	return  a;
}
//传引用返回
A& TestFunc2() 
{
	return a; 
}
void TestRefAndValue()
{
	// 以值作为函数的返回值类型
	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(A)-time:" << end1 - begin1 << endl;
	cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}

int main()
{
	TestRefAndValue();
	return 0;
}

运行截图:

在这里插入图片描述
可以看到,由于传值传参和传值返回都需要额外拷贝开一份空间,而传引用不需要,所以效率是差别还是很大的,所以一般情况下,如果不是特殊需求,传引用的性价比还是更高的。

1.6引用和指针的比较

相同点:

  1. 效率都很高。
  2. 底层汇编代码很相似,引用的底层实现是指针,也就是引用在底层实现上是开了空间的。
  3. 传址引用和传引用返回都可以改变该变量的值,当然特殊情况例外(const修饰的变量)。

不同点:
1.引用在语法概念上没有开空间。
2.指针不初始化不会报错,但是引用不行。
3.创建一个引用之后,这个引用就不能再作为其它变量的别名了,但是指针变量可以指向其它相同类型的变量。

2.auto关键字

auto是C++上面的一个关键字,它可以自动识别右值的类型,我们主要介绍C++11标准的auto关键字。

#include<iostream>
using namespace std;

int& Add(int& a, int& b)
{
	static int c = a + b;
	return c;
}
int main()
{
	int a = 2;
	int b = 3;
	auto c = Add(a, b);
	cout << "b的类型为:" << typeid(b).name() << endl;
	return 0;
}

运行结果:

在这里插入图片描述

  • 注意:引用的类型和被引用的对象是一致的,typeid(变量名).name()可以用来打印变量的类型。

可能会有人认为这样没有什么实质的作用,但是当那个函数的返回值类型(因为C++有很多自定义类型)非常复杂时,auto关键字就非常方便了。

2.1 auto关键字的使用细则

auto关键字可以和指针、引用结合起来使用,但是必须给它初始化,否则语法上是无法通过的。

#include<iostream>
using namespace std;
int main()
{
	int a = 2;
	int b = 3;
	auto* c = &a;
	auto d = &a;
	auto& e = a;
	cout << "c的类型为:" << typeid(c).name() << endl;
	cout << "d的类型为:" << typeid(d).name() << endl;
	cout << "e的类型为:" << typeid(e).name() << endl;
	return 0;
}

运行截图:
在这里插入图片描述

  • 这里在定义指针时autoauto*没有什么区别,但是在定义引用时必须加上&操作符。

如果不初始化,就会报这样的错误:

在这里插入图片描述

这也间接说明了auto不是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量的类型必须由编译器在编译时期推导而得,所以如果你不给初始值,那么编译器就无法进行推导,auto在编译期间会被替换。

auto关键词可以同时定义很多变量,但是它只会对第一个变量的类型进行推导,从而用这个变量的类型来定义其它变量,所以这些变量的类型要相同,详细请看下图:
在这里插入图片描述

2.2 auto关键字不能使用的场景

  1. auto关键字不能作为函数的参数,因为编译器无法对其类型进行推导。
  2. auto不能用来声明数组。
    在这里插入图片描述
  • 这里有一点需要说明的时,虽然VS2019上,以auto作为返回值的类型是可以编译通过的,但还是不建议这样去做,因为如果我们后期想要去找到其返回值的类型还是比较麻烦的,因为可能出现这样的情况:
    在这里插入图片描述
    这里还只有两个嵌套,如果工程量一大,嵌套的次数变多,想知道某个函数的返回值就是一件困难的事情,有人说可以用typeid(变量名).name()来知道其类型,我直接写出来不是更方便吗?

  • auto在实际中最常见的优势用法就是跟以后会提到的C++11提供的新式for循环,还有lambda表达式等进行配合使用。

3.特殊的for循环(基于范围)

3.1基于范围for的语法

在C语言/C++98中,如果我们想遍历一个数组,你可能会这样做:

#include<iostream>
using namespace std;

int main()
{
	int array[] = { 1,2,3,4,5,6,7,8 };
	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 << " ";
	}
	return 0;
}

但是对于一个本身就有范围的集合来说,我们程序员自己去控制范围似乎有点多余了,而且还很容易出错,因此C++11中引出了基于范围的for循环。它的for循环括号里被:分为两部分,左边是用来迭代的变量(迭代可以理解为遍历),右边是范围,代码是这样实现的:

#include<iostream>
using namespace std;

int main()
{
	int array[] = { 1,2,3,4,5,6,7,8 };
	for (auto& p : array)
	{
		p *= 2;
	}

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

这里auto可以换成数组相应的类型,但是使用auto编译器可以帮助我们在编译期间推导类型,十分方便,但是我们如果想要改变数组的值就得使用引用了,因为如果不是引用左边的变量只是我们数组值的一个拷贝,改变它不能改变我们数组中的值。

下面一段代码希望帮助你完全理解它们:

#include<iostream>
using namespace std;

int main()
{
	int array[] = { 1,2,3,4,5,6,7,8 };
	for (int i = 0; i < sizeof(array) / sizeof(array[0]); i++)
	{
		cout << &array[i] << " ";
	}
	cout << endl;
	for (auto p : array)
	{
		cout << &p << " ";
	}
	cout << endl;
	for (auto& p : array)
	{
		cout << &p << " ";
	}
	return 0;
}

运行结果:

在这里插入图片描述

我们可以看到引用是控制台第三行,它的地址与数组每个元素的地址是相同的,说明编译器在迭代时如果是引用,每执行一次for循环,p的空间就会被回收,不然由于创建引用变量后,这个引用变量不能作为其它变量的别名可知,打印出的地址应该是相同的才对,此时不相同,所以博主猜测应该是回收了,一次for循环执行一次引用变量的创建,至于普通的迭代,可以看出第二行的地址是完全相同的,说明编译器只给这个变量开了一次空间,剩下的每次for循环都是简单的把数组中的值赋值给它。

3.2 基于范围for的使用规则

  1. for循环的范围必须是确定的。
    对于数组而言,它的范围就是从第一个元素到最后一个元素
  • 注意,以下代码就有问题,它的范围是不确定的。
#include<iostream>
using namespace std;

void Fun(int array[])
{
  for (auto p : array)
  {
  	cout << p << " ";
  }
}
int main()
{
  int array[] = { 0,1,2,4,3,5 };
  Fun(array);
  return 0;
}

报错截图:

在这里插入图片描述

这是你这样写array似乎是一个数组,其实不然,它是一个保存了数组首元素的指针,你在里面计算数组的范围是无法计算出来的:

在这里插入图片描述
所以你也无法知道范围,自然就会报错。

在这里插入图片描述

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

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

相关文章

BSTree二叉树讲解

二叉搜索树的概念&#xff1a; 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&#xff0c;或者是具有以下性质的二叉树: 若它的左子树不为空&#xff0c;则左子树上所有节点的值都小于根节点的值 若它的右子树不为空&#xff0c;则右子树上所有节点的值…

重置 VCSA 6.7 root密码和SSO密码

原贴地址&#xff1a;https://www.cnblogs.com/airoot/p/16059033.html 问题描述 1、用root用户登录 VMware vCenter Server Appliance虚拟机失败&#xff0c;无法登录 2、vCenter Server Appliance 6.7 U1的root帐户错误尝试次数超过3次已锁定或帐户已过期 官方说明 在VC…

【Spring Boot 源码学习】RedisAutoConfiguration 详解

Spring Boot 源码学习系列 RedisAutoConfiguration 详解 引言往期内容主要内容1. Spring Data Redis2. RedisAutoConfiguration2.1 加载自动配置组件2.2 过滤自动配置组件2.2.1 涉及注解2.2.2 redisTemplate 方法2.2.3 stringRedisTemplate 方法 总结 引言 上篇博文&#xff0…

【C++基础入门】44.C++中对象模型分析(上)

一、回归本质 class 是一种特殊的 struct 在内存中 class 依旧可以看作变量的集合class 与 struct 遵循相同的内存对齐规则class 中的成员函数与成员变量是分开存放的 每个对象有独立的成员变量所有对象共享类中的成员函数值得思考的问题 下面看一个对象内存布局的代码&#x…

Go学习第十七章——Gin中间件与路由

Go web框架——Gin中间件与路由 1 单独注册中间件1.1 入门案例1.2 多个中间件1.3 中间件拦截响应1.4 中间件放行 2 全局注册中间件3 自定义参数传递4 路由分组4.1 入门案例4.2 路由分组注册中间件4.3 综合使用 5 使用内置的中间件6 中间件案例权限验证耗时统计 1 单独注册中间件…

Java项目之网络考试系统

视频教程&#xff1a; 01-创建数据库_哔哩哔哩_bilibili 源码下载&#xff1a;百度网盘 请输入提取码 准备工作 创建数据库配置IDEA后端导入前端 前言&#xff1a; 把代码掰开写进博客里&#xff0c;主要是让自己在整理笔记的过程中&#xff0c;多去思考完成这个功能的核心…

基于深度学习的单图像人群计数研究:网络设计、损失函数和监控信号

摘要 https://arxiv.org/pdf/2012.15685v2.pdf 单图像人群计数是一个具有挑战性的计算机视觉问题,在公共安全、城市规划、交通管理等领域有着广泛的应用。近年来,随着深度学习技术的发展,人群计数引起了广泛的关注并取得了巨大的成功。通过系统地回顾和总结2015年以来基于深…

rust学习——智能指针Rc

文章目录 Rc 与 ArcRcRc::clone观察引用计数的变化不可变引用一个综合例子Rc 简单总结 多线程无力的 RcArcArc 的性能损耗 总结 Rc 与 Arc Rust 所有权机制要求一个值只能有一个所有者&#xff0c;在大多数情况下&#xff0c;都没有问题&#xff0c;但是考虑以下情况&#xff1…

二维码智慧门牌管理系统升级解决方案:采集要素为智慧城市建设提供精准数据支持

文章目录 前言一、二维码智慧门牌管理系统的升级需求二、采集要素在系统升级中的应用三、消防栓、井盖等采集要素的应用 前言 随着城市化进程的加速&#xff0c;智慧城市的建设已成为未来城市发展的必然趋势。其中&#xff0c;二维码智慧门牌管理系统作为智慧城市的重要组成部…

基于Spring Boot的大学课程排课系统设计与实现

摘 要 大学课程排课是现代教育管理中重要的一环。目前&#xff0c;传统的排课方式已经无法满足日益增长的课程需求和学生个性化的诉求。因此&#xff0c;研究一种基于遗传算法的大学课程排课系统是非常必要的。本研究旨在开发一种基于SpringBoot Vue的大学课程排课系统&#x…

【Java 进阶篇】在Java Web应用中获取ServletContext对象详解

在Java Web应用开发中&#xff0c;ServletContext对象扮演着重要的角色&#xff0c;它允许你在整个Web应用程序中存储和共享数据。ServletContext对象是Servlet容器提供的一种用于管理Web应用程序的全局信息的方式。本文将详细探讨ServletContext对象的概念、用途以及如何在Jav…

算法笔记【8】-合并排序算法

文章目录 一、前言二、合并排序算法基本原理三、实现步骤四、优缺点分析 一、前言 合并排序算法通过采用分治策略和递归思想&#xff0c;实现了高效、稳定的排序功能。本文将深入探讨合并排序算法的原理、实现步骤&#xff0c;并讨论其优缺点。 二、合并排序算法基本原理 合…

AntDB数据库荣获 “2023年信创物联网优秀服务商”

日前&#xff0c;在2023世界数字经济大会暨第十三届智博会 2023京甬信创物联网产融对接会上&#xff0c;AntDB数据库再获殊荣&#xff0c;获评“2023年信创物联网优秀服务商”。 图1&#xff1a;2023年信创物联网优秀服务商颁奖现场 信创物联网是信息技术应用创新与物联网的结…

网络爬虫入门导学

一、内容组织 2、常用的python IDE工具 比较推荐以下几种&#xff1a; 其中IDLE是python自带的/默认的/常用的/入门级编写工具&#xff0c;包含交互式和文件式 适用于&#xff1a;简单直接/入门级/代码不超过300行 Sublime Text是专为程序员开发的第三方专用编程工具&#xff…

OPNET <<< Program Abort >>> Standard function stack imbalance

OPNET <<< Program Abort >>> Standard function stack imbalance OPNET 问题原因及解决办法 OPNET 问题 OPNET仿真时遇到此问题&#xff1a; <<< Program Abort >>> Standard function stack imbalance 原因及解决办法 出现此问题是因…

【逗老师的无线电】艾德克斯ITECH电源电子负载网口适配器

艾德克斯的产品还是不错的&#xff0c;但是ITECH的大部分中低端设备都不带网口&#xff0c;只带了一个串口&#xff0c;并且这个串口还是个完全非标定义的5V TTL串口&#xff0c;原装的适配器300多还只能转接成RS-232。 那么&#xff0c;这回咱们来整个骚活&#xff0c;直接给艾…

Go-Python-Java-C-LeetCode高分解法-第十二周合集

前言 本题解Go语言部分基于 LeetCode-Go 其他部分基于本人实践学习 个人题解GitHub连接&#xff1a;LeetCode-Go-Python-Java-C 欢迎订阅CSDN专栏&#xff0c;每日一题&#xff0c;和博主一起进步 LeetCode专栏 我搜集到了50道精选题&#xff0c;适合速成概览大部分常用算法 突…

简单明了!网关Gateway路由配置filters实现路径重写及对应正则表达式的解析

问题背景&#xff1a; 前端需要发送一个这样的请求&#xff0c;但出现404 首先解析请求的变化&#xff1a; http://www.51xuecheng.cn/api/checkcode/pic 1.请求先打在nginx&#xff0c;www.51xuecheng.cn/api/checkcode/pic部分匹配到了之后会转发给网关进行处理变成localho…

人工智能-线性回归的从零开始实现

线性回归的从零开始实现 在了解线性回归的关键思想之后&#xff0c;我们可以开始通过代码来动手实现线性回归了。 在这一节中&#xff0c;我们将从零开始实现整个方法&#xff0c; 包括数据流水线、模型、损失函数和小批量随机梯度下降优化器。 虽然现代的深度学习框架几乎可以…

预安装win11的电脑怎么退回正版win10?

对于新购的笔记本 通常来讲预装的系统是全新安装的&#xff0c;是没有之前Windows10系统文件的&#xff0c;无法回退。 可以打开设置-----系统----恢复-----看下是否有该选项。 ------------------------------------------------------------------------------- 若是在上述…