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

这篇具有很好参考价值的文章主要介绍了【C++初探:简单易懂的入门指南】二。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

【C++初探:简单易懂的入门指南】二,C++,c++,java,开发语言,算法,程序人生,笔记,经验分享

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

【C++初探:简单易懂的入门指南】二,C++,c++,java,开发语言,算法,程序人生,笔记,经验分享

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

运行结果截图:

【C++初探:简单易懂的入门指南】二,C++,c++,java,开发语言,算法,程序人生,笔记,经验分享
上面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;
}

运行截图为:
【C++初探:简单易懂的入门指南】二,C++,c++,java,开发语言,算法,程序人生,笔记,经验分享
可以看到这里引用是不开空间的,因为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;
}

运行结果:

【C++初探:简单易懂的入门指南】二,C++,c++,java,开发语言,算法,程序人生,笔记,经验分享
这里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++,c++,java,开发语言,算法,程序人生,笔记,经验分享

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

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

细心的朋友可能会在vs2019上发现这样的问题,上述代码,即使不加static似乎也能正常运行。
【C++初探:简单易懂的入门指南】二,C++,c++,java,开发语言,算法,程序人生,笔记,经验分享
这里博主认为是编译器检查机制的一个漏洞,系统没有将空间及时的回收,如果我把代码改成这样,系统把空间回收使用后,d的值就变成随机值了,

【C++初探:简单易懂的入门指南】二,C++,c++,java,开发语言,算法,程序人生,笔记,经验分享

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

【C++初探:简单易懂的入门指南】二,C++,c++,java,开发语言,算法,程序人生,笔记,经验分享

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

运行结果截图:

【C++初探:简单易懂的入门指南】二,C++,c++,java,开发语言,算法,程序人生,笔记,经验分享
而且由于引用只是取别名,本质上它们是同一个变量,所以修改一个就修改了它们所有的值。

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

报错截图:

【C++初探:简单易懂的入门指南】二,C++,c++,java,开发语言,算法,程序人生,笔记,经验分享
所以我们在定义引用时,不能改变原变量的类型。
关于权限的问题,主要围绕const这个关键词展开:

  • const修饰的变量,当对其引用时,不能不加const,因为其是不可修改的常量,不加const是对其权限的放大,编译器是不允许的。
  • 但是如果一个变量没有被const修饰,在引用时,可以加上const,进行权限的缩小,这个编译器是允许的。
    【C++初探:简单易懂的入门指南】二,C++,c++,java,开发语言,算法,程序人生,笔记,经验分享
    但是缩小权限又是允许的:
    【C++初探:简单易懂的入门指南】二,C++,c++,java,开发语言,算法,程序人生,笔记,经验分享
    【C++初探:简单易懂的入门指南】二,C++,c++,java,开发语言,算法,程序人生,笔记,经验分享
    这里有个比较奇怪的现象,为什么我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;
}

运行截图:

【C++初探:简单易懂的入门指南】二,C++,c++,java,开发语言,算法,程序人生,笔记,经验分享
这里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;
}

运行截图:

【C++初探:简单易懂的入门指南】二,C++,c++,java,开发语言,算法,程序人生,笔记,经验分享
可以看到,由于传值传参和传值返回都需要额外拷贝开一份空间,而传引用不需要,所以效率是差别还是很大的,所以一般情况下,如果不是特殊需求,传引用的性价比还是更高的。

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

运行结果:

【C++初探:简单易懂的入门指南】二,C++,c++,java,开发语言,算法,程序人生,笔记,经验分享

  • 注意:引用的类型和被引用的对象是一致的,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;
}

运行截图:
【C++初探:简单易懂的入门指南】二,C++,c++,java,开发语言,算法,程序人生,笔记,经验分享

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

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

【C++初探:简单易懂的入门指南】二,C++,c++,java,开发语言,算法,程序人生,笔记,经验分享

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

auto关键词可以同时定义很多变量,但是它只会对第一个变量的类型进行推导,从而用这个变量的类型来定义其它变量,所以这些变量的类型要相同,详细请看下图:
【C++初探:简单易懂的入门指南】二,C++,c++,java,开发语言,算法,程序人生,笔记,经验分享

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

  1. auto关键字不能作为函数的参数,因为编译器无法对其类型进行推导。
  2. auto不能用来声明数组。
    【C++初探:简单易懂的入门指南】二,C++,c++,java,开发语言,算法,程序人生,笔记,经验分享
  • 这里有一点需要说明的时,虽然VS2019上,以auto作为返回值的类型是可以编译通过的,但还是不建议这样去做,因为如果我们后期想要去找到其返回值的类型还是比较麻烦的,因为可能出现这样的情况:
    【C++初探:简单易懂的入门指南】二,C++,c++,java,开发语言,算法,程序人生,笔记,经验分享
    这里还只有两个嵌套,如果工程量一大,嵌套的次数变多,想知道某个函数的返回值就是一件困难的事情,有人说可以用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;
}

运行结果:

【C++初探:简单易懂的入门指南】二,C++,c++,java,开发语言,算法,程序人生,笔记,经验分享

我们可以看到引用是控制台第三行,它的地址与数组每个元素的地址是相同的,说明编译器在迭代时如果是引用,每执行一次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;
}

报错截图:

【C++初探:简单易懂的入门指南】二,C++,c++,java,开发语言,算法,程序人生,笔记,经验分享

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

【C++初探:简单易懂的入门指南】二,C++,c++,java,开发语言,算法,程序人生,笔记,经验分享
所以你也无法知道范围,自然就会报错。

【C++初探:简单易懂的入门指南】二,C++,c++,java,开发语言,算法,程序人生,笔记,经验分享文章来源地址https://www.toymoban.com/news/detail-736779.html

到了这里,关于【C++初探:简单易懂的入门指南】二的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 从零开始学习 Java:简单易懂的入门指南之爬虫(十六)

    Pattern:表示正则表达式 Matcher:文本匹配器,作用按照正则表达式的规则去读取字符串,从头开始读取。 在大串中去找符合匹配规则的子串。 代码示例: 需求: ​ 把连接:https://m.sengzan.com/jiaoyu/29104.html?ivk sa=1025883i中所有的身份证号码都爬取出来。 代码示例: 需求: ​ 把

    2024年02月11日
    浏览(49)
  • 从零开始学习 Java:简单易懂的入门指南之多态(十)

    多态是继封装、继承之后,面向对象的第三大特性。 多态是出现在继承或者实现关系中的 。 多态体现的格式 : 多态的前提 :有继承关系,子类对象是可以赋值给父类类型的变量。例如Animal是一个动物类型,而Cat是一个猫类型。Cat继承了Animal,Cat对象也是Animal类型,自然可

    2024年02月12日
    浏览(29)
  • 从零开始学习 Java:简单易懂的入门指南之数组(五)

    概念: ​ 指的是一种容器,可以同来存储同种数据类型的多个值。 ​ 但是数组容器在存储数据的时候,需要结合隐式转换考虑。 比如: ​ 定义了一个int类型的数组。那么boolean。double类型的数据是不能存到这个数组中的, ​ 但是byte类型,short类型,int类型的数据是可以存

    2024年02月14日
    浏览(30)
  • 从零开始学习 Java:简单易懂的入门指南之面向对象(九)

    类的定义格式如下: 例如: 例如: 1.3.1 封装的步骤 1.使用 private 来修饰成员变量。 2.使用 public 修饰getter和setter方法。 1.3.2 封装的步骤实现 private修饰成员变量 public修饰getter和setter方法 1.4.1 构造方法的作用 在创建对象的时候,给成员变量进行初始化。 初始化即赋值的意

    2024年02月13日
    浏览(58)
  • 从零开始学习 Java:简单易懂的入门指南之类和对象(七)

    1.1 类和对象的理解 客观存在的事物皆为对象 ,所以我们也常常说万物皆对象。 类 类的理解 类是对现实生活中一类具有共同属性和行为的事物的抽象 类是对象的数据类型,类是具有相同属性和行为的一组对象的集合 简单理解:类就是对现实事物的一种描述 类的组成 属性:

    2024年02月14日
    浏览(52)
  • 从零开始学习 Java:简单易懂的入门指南之时间类(十七)

    java.util.Date`类 表示特定的瞬间,精确到毫秒。 继续查阅Date类的描述,发现Date拥有多个构造函数,只是部分已经过时,我们重点看以下两个构造函数 public Date() :从运行程序的此时此刻到时间原点经历的毫秒值,转换成Date对象,分配Date对象并初始化此对象,以表示分配它的时

    2024年02月11日
    浏览(38)
  • 从零开始学习 Java:简单易懂的入门指南之异常(二十八)

    异常,就是不正常的意思。在生活中:医生说,你的身体某个部位有异常,该部位和正常相比有点不同,该部位的功能将受影响.在程序中的意思就是: 异常 :指的是程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。 在Java等面向对象的编程语言中,异常本身

    2024年02月08日
    浏览(52)
  • 从零开始学习 Java:简单易懂的入门指南之包装类(十九)

    Java提供了两个类型系统,基本类型与引用类型,使用基本类型在于效率,然而很多情况,会创建对象使用,因为对象可以做更多的功能,如果想要我们的基本类型像对象一样操作,就可以使用基本类型对应的包装类,如下: 基本类型 对应的包装类(位于java.lang包中) byte

    2024年02月11日
    浏览(33)
  • 从零开始学习 Java:简单易懂的入门指南之反射(三十八)

    ​ 专业的解释: ​ 是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法; ​ 对于任意一个对象,都能够调用它的任意属性和方法; ​ 这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。 ​ 通俗的理解: 利用 反射 创建的对象 可

    2024年02月08日
    浏览(31)
  • 从零开始学习 Java:简单易懂的入门指南之线程同步(三十五)

    1.1卖票【应用】 案例需求 某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票 实现步骤 定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100; 在SellTicket类中重写run()方法实现卖票,代码步骤如下

    2024年02月08日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包