带领你打开C++的神秘之门--完结篇

news2024/7/6 19:24:16

在这里插入图片描述

🎈个人主页:🎈 :✨✨✨初阶牛✨✨✨
🐻推荐专栏1: 🍔🍟🌯C语言初阶
🐻推荐专栏2: 🍔🍟🌯C语言进阶
🔑个人信条: 🌵知行合一
🍉本篇简介:>:讲解C++中的函数重载、引用、auto关键字、内联函数等.
金句分享:
✨生活本就沉默,但是跑起来有风!✨

前言

本篇文章内容很多,讲解c++入门的一些语法,最好是有C语言的基础,这样学起来更加轻松.内容丰富需要慢慢消化,花费时间也很长,总计约1.5w字,希望能对友友们有所帮助.

目录

  • 前言
  • 一、函数重载
    • 1.1 函数参数类型不同
    • 1.2 函数参数的个数不同
    • 1.3 函数参数顺序不同
    • 1.4 不构成函数重载
    • 为什么C不支持函数重载,而C++支持?(重点)
    • 1.5 “extern C”
  • 二、引用
    • 2.1 引用特点:
    • 2.2 使用场景:
        • 做参数:
        • 做返回值:
    • 2.3 常引用?
    • 2.4 从底层探究引用和指针.
  • 三、重新认识一下auto关键字
    • 3.1 auto关键字的介绍
    • 3.2 使用细节:
  • 四、内联函数
    • 4.1 观察内联函数的实现:
    • 4.2 内联函数的特点
    • 4.3 相关面试考点:
  • 五、C++的一颗“语法糖”🍭
    • 5.1 基于范围的for循环
    • 5.2 “糖虽好,注意粘牙哦!”
  • 六、认识nullptr
  • 七、结语:

一、函数重载

  函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。

理论看一遍就行啦,还是直接上栗子好理解吧!
🌰栗子

1.1 函数参数类型不同

//函数参数类型不同
#include <iostream>
#include <stdio.h>

//展开部分常用的
using std::cout;
using std::cin;
using std::endl;

namespace cjn
{
	//函数1
	int add(int e1, int e2)
	{
		cout << "整形:";
		return e1 + e2;
	}
	//函数2
	double add(double e1, double e2)
	{
		cout << "浮点形:";
		return e1 + e2;
	}

}
int main()
{
	int a = 0, b = 0;
	double c = 0, d = 0;
	cin >> a >> b;
	//a和b是int型,会调用int add(int e1, int e2)函数
	cout << cjn::add(a, b) << endl;

	cin >> c >> d;
	//c和d是double型,会调用double add(double e1, double e2)
	cout << cjn::add(c, d) << endl;
	return 0;
}

输入:

2 3
3.4 5.2

输出

整形:5
浮点形:8.6

函数1和函数2虽然函数名相同,但是函数的参数不同,构成函数重载.

1.2 函数参数的个数不同

🌰栗子

//函数参数个数不同时
#include <iostream>
#include <stdio.h>

using std::cout;
using std::cin;
using std::endl;

namespace cjn
{
	void fun()				//函数1
	{
		cout << "fun()" << endl;
	}
	void fun(int a)			//函数2
	{
		cout << "fun(int a)" << endl;
	}
}
int main()
{
	cjn::fun();		//会调用函数1
	cjn::fun(0);	//会调用函数2

	return 0;
}

运行结果:

fun()
fun(int a)

此时还有一个特殊情况:

namespace cjn
{
	void fun()				//函数1
	{
		cout << "fun()" << endl;
	}
	void fun(int a=4)			//函数2
	{
		cout << "fun(int a)" << endl;
	}
}
int main()
{
	cjn::fun();//此时编译器不知道应该调用哪一个函数
	return 0;
}

分析:

由于函数2设置了缺省值,所以在不传参时,会产生混乱.

1.3 函数参数顺序不同

🌰栗子

//函数参数顺序不同

#include <iostream>
#include <stdio.h>

using std::cout;
using std::cin;
using std::endl;

namespace cjn
{
	void fun(int a,char b)				//函数1
	{
		cout << "fun(int a,char b)" << endl;
	}
	void fun(char a,int b)				//函数2
	{
		cout << "fun(char a,int b)" << endl;
	}
}
int main()
{

	cjn::fun(1,'c');		//会调用函数1
	cjn::fun('a',0);		//会调用函数2

	return 0;
}

运行结果:

fun(int a,char b)
fun(char a,int b)

1.4 不构成函数重载

函数的返回值不同 :不构成函数重载

//不支持函数重载
namespace cjn
{
	int fun(char a,int b)				//函数1
	{
		cout << "fun(int a,char b)" << endl;
	}
	double fun(char a,int b)				//函数2
	{
		cout << "fun(char a,int b)" << endl;
	}
}

为什么C不支持函数重载,而C++支持?(重点)

上面也说了,返回值不同也不支持函数重载,让我们从底层来揭秘吧!

C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。这些在C语言中的程序环境阶段有细讲.

我们知道,如果我们只有函数的声明,相当于只拿到了承诺,具体的函数定义并没有拿到,要在最后的链接阶段去通过符号表(很重要)拿到函数地址(兑现承诺).

而符号表中对于函数名的修饰规则在C和C++中是不同的.

例如:

//以下列函数为例

int Add(int a, double b)
{
	......
}

void Swap(double* e1, double* e2)
{
	double tmp = *e1;
	*e1 = *e2;
	*e2 = tmp;
}

由于Windows下vs的修饰规则过于复杂,而Linux下g++的修饰规则简单易懂,下图是g++修饰后的名字规则。
C语言和C++的函数名修饰的不同

在这里插入图片描述

在C语言中,只是简单的将函数名直接存入符号表,而C++则考虑到函数的参数类型,函数名长度等因素.

而g++环境下的C++中的函数修饰后变成【_Z+函数长度+函数名+类型首字母】

疑问:如果两个函数函数名和参数是一样的,返回值不同构成函数重载吗?

在这里插入图片描述

示例:

int add(int a, int b)
{
	;
}
double add(int a, int b)
{
	;
}

int main()
{
	add(2, 3);//调用哪个函数?返回值没有体现也无法体现在调用处
	return 0;
}

答案是:不构成函数重载,即使修改了底层函数名修饰规则也不行,因为在调用函数时,返回值无法体现,无法区分该调用哪个函数.

1.5 “extern C”

由于C和C++编译器对函数名字修饰规则的不同,在有些场景下可能就会出问题,比如:

情况1:

C++中调用C语言实现的静态库或者动态库,反之亦然

情况2:

多人协同开发时,有些人擅长用C语言,有些人擅长用C++

在这种混合模式下开发,由于C和C++编译器对函数名字修饰规则不同,可能就会导致链接失败,在该种场景下,就需要使用extern “C”。在函数前加extern “C”,意思是告诉编译器,将该函数按照C语言规则来编译。
🌰栗子:

为了能在C/C++工程中都能使用,函数声明时需加上extern “C”

#ifdef __cplusplus//如果是c++项目(这里要求用c语言实现)
extern "C"//此时这条语句会执行
{
#endif
int Add(int left, int right);
int Sub(int left, int right);
#ifdef __cplusplus
}
#endif

解释:

#ifdef _cplusplus
}
#endif
作用:

(1):c++工程中保证是以C的方式实现

_如果是C++工程,编译器已经定义_cplusplus宏,编译时该宏是可以被识别的,被声明的函数就被extern "C"修饰了,此时C++编译就知道,该函数是按照C的方式编译的,这样在链接时就会按照C的方式找函数名字.

(2):C工程中不受影响

如果是C工程,编译器未定义_cplusplus宏,编译时该宏无法被是被,则条件编译就无效,函数就不会被extern "C"修饰 .

二、引用

引入:

C指针玩法:

在C语言阶段,我们可以通过一个指针去找变量,指针就好比是老板的秘书(一级指针),老板太忙了,只给秘书(一级指针)留了联系方式,我们可以通过秘书(一级指针)去找变量,如果秘书(一级指针)也比较忙,就会给秘书也会自己的秘书留下联系方式,即秘书的秘书(二级指针).

C++引用玩法:

例如:有个小女孩真名叫“涂山苏苏”,我们也可以叫她“小蠢货”,”蠢货苏”等别名,或者“苏苏”等其它小名.这些名词虽然不一样,但是代表的内容都是“涂山苏苏”本人,这便是C++引用的做法,取别名.

图解:

在这里插入图片描述

示例代码:

# include <iostream>

using std::cout;
using std::cin;
using std::endl;

int main()
{
	int a = 5;
	//下面都是对a的引用,即a的别名.
	int& b = a;
	int& c = a;
	int& d = a;
	int& e = a;

	cout << a << " " << b << " " << c << " " << d << " " << e << endl;
	return 0;
}

运行结果:

5 5 5 5 5

2.1 引用特点:

  1. 引用必须初始化.
int main()
{
	int a = 5;
	//如果我们引用不初始化
	int& b;
	return 0;
}

改代码会显示错误信息:

在这里插入图片描述

这也很好理解,引用就类似与取别名,如果连对象都没有,那别名还有啥意义.

  1. 引用一旦确定了引用实体后,就不能像指针一样改变指向了.
int main()
{
	int a = 5;
	int c = 1;
	
	int& b=a;
	b = c;//这样b就是c的别名了吗?
	cout << a << " " << b << " " << c << endl;
	return 0;
}

运行结果:

1 1 1

b=c并不是将b改为c的别名,而是赋值,将b的值给改了,b改了,那也就等于a也改了.

2.2 使用场景:

做参数:

引用指针在简单传参(后续有复杂的自定义对象传参,效果更加明显)是的对比.

在这里插入图片描述

做返回值:

(1)我们之前的一般返回是这样的.

//普通返回
int test()
{
	static int a = 0;
	a += 5;
	return a;
}

int main()
{
	int& c = test();
	cout << c << endl;
	return 0;
}

(2)改用引用作为返回值后:

//引用返回
int& test()//注意看返回值的类型
{
	static int a = 0;
	a += 5;
	return a;//返回的是a的别名
}
int main()
{
	int& c = test();//此时c是作为别名接收返回值
	cout << c << endl;
	return 0;
}

图解:

在这里插入图片描述

引用作为返回值的写法减少了拷贝,所以明显效率更高.

那以后我们都用引用作为返回值吗?(友情提示引用虽好,可不要贪杯哦!);

我们看一下下面的情况:

//引用返回
int& test()
{
	int a = 0;
	a += 5;
	return a;
}
int add(int a, int b)
{
	int sum = a + b;
	return sum;
}
int main()
{

	int& c = test();
	add(22, 44);
	cout << c << endl;
	return 0;
}

在这里插入图片描述

结果:

66

c 是函数test()的返回值,我们打印的是c,为啥结果确实add(22,44)的返回值,是巧合吗?

这就要考虑所在环境了,还是画图比较好理解,上图解!

在这里插入图片描述

如果引用做返回值时,返回的空间是被系统收回的,那就很危险.

引用作为返回值的时候,可以修改返回值,或者获取返回值,而不是获取返回值的拷贝(临时变量).

(1)普通返回:

//普通返回
int test()
{
	static int a = 0;
	a += 5;
	return a;
}

int main()
{

	int c = test();
	c =  95;//对c不会影响a,因为它只是a的拷贝返回
	cout << test() << endl;//调用了两次,所以结果为10
	return 0;
}

运行结果:

10

(2)引用返回:

//引用返回
int& test()
{
	static int a = 0;
	a += 5;
	return a;
}
int add(int a, int b)
{
	int sum = a + b;
	return a + b;
}
int main()
{

	int& c = test();
	c =  95;//对c修改,就是对a修改.
	cout << test() << endl;
	return 0;
}

运行结果:100

可以通过调试发现,test返回的是a的别名,所以c是a的别名的别名.

在这里插入图片描述

小结:

1.引用做参数基本上所有场景都可以.

  (1)方便做输出型参数(例如:swap()函数),可以用传引用改变实参.

  (2)传参是以别名的形式,中间没有拷贝,可以提高效率.

2.引用做返回值时需要特别注意,如果出了作用域,对象空间被系统收回了,就不能用引用返回.其它情况建议用引用返回,可以减少拷贝,提高效率.

  (1)同样,可以减少拷贝,提高效率.

  (2)可以修改返回值,或者是获取返回值.(后续会遇到这种情况).

2.3 常引用?

(1)权限放大:

  从只读–>可读,可写,权限放大会报错.因为不安全.

	//情况1
	const int a = 6;
	//错误写法
	int& ra = a; //该语句编译时会报错,因为a变量具有常性,而ra是可读可写,权限不能放大.
	//正确写法:
	const int& ra = a;//权限的平移


	//情况2
	//错误写法
	int& b = 5; // 该语句编译时会出错,b为常量
	//正确写法
	const int& b = 5;//权限的平移


	//情况3
	double d = 13.14;
	//错误写法
	int& rd = d; // 这里会发生隐式类型转换,而产生的临时变量具有常性.
	//正确写法
	const int& rd = d;

	//情况4
int Test_Const()
{
    int x = 2, y = 3;
	int sum = x + y;
	return sum;
}

int main()
{
	//错误写法
	int& ret=Test_Const();//返回值是临时变量(因为函数栈帧被销毁了,需要借助寄存器,或者别的产生临时变量返回)的拷贝,临时变量具有常性.
	//正确写法
	const int& ret = Test_Const();
	return 0;
}

情况3的特别说明:隐式转换(操作数两边数据类型不同时,要保证数据两边的类型不变,需要借助临时变量).

在这里插入图片描述

(2)权限缩小:

  从可读可写–>只读,权限缩小,更加安全.

	int& Test_Const()
{
	int x = 2, y = 3;
	int sum = x + y;
	return sum;
}

int main()
{
	//权限平移
	int& ret = Test_Const();//注意Test_Const函数的返回值是sum的别名,并不是具有常性的临时变量,所以这里不会报错
	//缩小
	const int& ret = Test_Const();//从可读可写-->只读,权限缩小,更加安全,不会报错.
	return 0;
}

小结:

 权限可以平移和缩小,但是不可以放大,使用时需要注意.

2.4 从底层探究引用和指针.

示例代码:

int main()
{
	int a = 4;

	//从语法上看,引用不开空间,而是用别名直接对a操作
	int& b = a;
	b = 5;

	//从语法看,指针需要开空间存储a的地址,然后通过地址去找a
	int* p = &a;
	*p=20;
	return 0;
}

  我们通过调试窗口,打开反汇编窗口,观察汇编代码:

在这里插入图片描述

我们发现,在底层引用指针的实现逻辑是一样的.都需要开空间,但是在语法上,我们依旧认为它是没有开空间的.

就好比:

  老婆饼里面没有老婆,红烧狮子头里面没有狮子,娃娃菜里面没有娃娃.

  1. 引用语法概念上定义一个变量的别名,而指针是存储一个变量地址。
  2. 引用在定义时必须初始化,否则编译器不知道是谁的“别名”,而指针没有规定,只不过我们习惯性初始化为NULL而已.
  3. 引用在初始化时引用一个实体后,就不能再引用其他实体(上面有讲到),而指针可以在任何时候指向任何一个同类型实体,并不受限制.
  4. 没有NULL引用,但有NULL指针
  5. 在sizeof中含义不同引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占
    4个字节)
  6. 引用的赋值,就是对引用实体进行修改,而指针+1或者赋值则是对实体的地址编号操作,并不会影响实体.
  7. 不存在多级引用,但是可以多个引用可以引用一个实体,即一个实体有多个别名.指针可以有多级指针.
  8. 访问实体方式不同,指针需要显式解引用,引用编译器会自己转换处理.
  9. 安全性:引用比指针使用起来相对更安全 ,好歹不存在空指针吧!😂😂😂

三、重新认识一下auto关键字

3.1 auto关键字的介绍

在c语言中:

  auto是C语言的一个关键字,关键字主要用于声明变量的生存期为自动,这个关键字不怎么多写,因为所有的局部变量默认就是auto的。

int a=0;//默认就是自动生存期
//等价于下面的
auto int a=0;//写成这样也太麻烦了,我们一般直接省略不写.

  C语言中提供了存储auto,register,extern,static说明的四种存储类别。四种存储类别说明符有两种存储期:自动存储期和静态存储期。

自动存储期:

auto和register。自动变量指在局部创建该变量,然后出了局部的作用域,该变量的声明周期就结束了,还给操作系统了.

静态存储周期:

extern,static

C++赋予了auto新的“生命”:

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

🌰栗子

#include <stdlib.h>
# include <iostream>

using std::cout;
using std::cin;
using std::endl;


int main()
{
	int a = 3;
	double b = 4.1;
	auto c = a + b;//会自动推导出c的类型
	cout << c << endl;

	cout << typeid(c).name() << endl;//typeid(c).name()会打印变量c的类型,后续会介绍,这里了解一下就行.
	return 0;
}

  这样看似乎auto关键字的作用不是很大,我们可以直接写double c,那是因为没有遇到复杂的场景,试着看一下下面这段代码(我们暂时不需要看懂代码的作用,只需要关注类型名即可).

#include <string>
#include <map>
int main()
{
	std::map<std::string, std::string> m{ 
    { "name", "名字" },
	{ "age", "年龄" },
	{"sex","性别"} };
	std::map<std::string, std::string>::iterator it = m.begin();
	while (it != m.end())
	{
		//....
	}
	return 0;
}

由于在工程中很多情况下,我们不能展开头文件,难免会遇到这样的类型:(上面代码的it的类型)

std::map<std::string, std::string>::iterator

使用宏替换可以吗?

可以是可以,但是宏替换的缺点你是否能接受?

  1. 没有安全检查,直接进行替换.
  2. 可读性很差,也不方便调试.

那是否有小伙伴想到使用typedef进行类型重定义呢?

其实typedef也是有缺点的.

例如:

typedef char* pchar;
int main()
{
	char arr[] = "cjn";
	
	//const pchar p1;//此语句报错,这里是指向不可改变的常量指针,定义时需要初始化
	const pchar p1=arr;
	*p1 = 'x';//指向不可以改变,但指向的内容可以.
	printf("%s\n", arr);


	char* parr =arr;//定义一个字符指针
	const pchar* p2=&parr;//指向字符指针的地址
	**p2 ='a';//解引用后的指针与p1类型一样,指向的内容可以改
	//*p2 = NULL;//但是指针指向不能改
	printf("%s\n", arr);

	return 0;
}

const pchar等价于char* const表示指针的指向 不能改变.

因为此处类型pchar相当于是类型char* 的别名,const pchar表示const修饰的是char* ,即char*指针的的指向不能被修改,但是其指向的内容可以修改.

const pchar*的类型等价于 char * const* .

typedef有的场景很容易让我们误解类型.

3.2 使用细节:

  1. 使用时必须初始化:
int main()
{
	auto a;
	return 0;
}

在这里插入图片描述

原因:

  在编译阶段编译器需要根据初始化表达式来推导auto的实际类型,而未初始化变量,则无法进行推导。注意auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型

  1. 不能直接推导出引用类型.

    int main()
    {
    	int a = 3;
    	auto b = a;//如果想b是a的引用,这里并不能达到我们想要的效果.
    	//正确写法
    	auto& c = a;//此时c就表示是一个引用类型,auto可以推导出是int类型
    	return 0;
    }
    
  2. 如果是推导指针类型:

    此时,auto和auto*没有区别(记住,只有在推导指针类型时没有区别,试着理解一下,因为指针类型可以直接推导出来)

    int main()
    {
    	int a = 3;
    	auto b = &a;//这里auto会自动推导成int*,表示b是一个整形指针int*指向a
    	
    	auto* c = &a;//此处auto会推导称为int与*结合为int*
    	//所以说此处auto和auto*声明变量没区别.
    	return 0;
    }
    
  3. 同一行定义多个变量的情况:

    默认将第一个推导出来的类型作为整条语句其他变量的类型.

    int main()
    {
    	auto x = 3, y = 5;				//同类型不会报错
    	auto a = 3, b = 4.5, c = 3;		//不同类型会报错,因为会将推导出来的第一个类型作为这条语句所有变量的类型
    	return 0;
    }
    
  4. 不能用来声明数组:

int main()
{
	auto x[5] = { 1,2,3,4,5 };//报错
	return 0;
}

6.不能做形参推导:

void test(auto a)//报错
{
    ......
}

在这里插入图片描述

四、内联函数

概念:

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

//默认Debug下,内联不会起作用.
inline int add(int a, int b)
{
	return a + b;
}
int main()
{
	int c = add(2, 3);
	return 0;
}

需要修改默认属性,更加方便我们在debug版本下观察内联函数.

视频教程:

在这里插入图片描述

4.1 观察内联函数的实现:

在这里插入图片描述

在这里插入图片描述

4.2 内联函数的特点

  1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替
    换函数调用
    .

    缺陷:由于是在调用处展开,则代码量将会扩大,也就导致目标文件的增大.

    优势:少了调用开辟函数栈帧的开销,提高程序运行效率。

  2. 你说内联就内联?编译器是不会信任你的,你将编译器搞崩溃了(代码膨胀咋办?

    所以inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同**,一般建议:将函数规
    模较小
    (即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、**不是递归频繁调用的函数
    采用inline修饰,否则编译器将不会采用=内联方式.

在这里插入图片描述

所以不要以外任何情况下内联都是好的,要视情况而定,对于短小的函数,且大量频繁调用的采用内联比较合适.

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

#include <iostream>

using std::cout;
using std::cin;
using std::endl;


#include "add.h"
int main()
{
	int a = 2, b = 3;
	cout << add(a, b) <<endl;
}



//add.c


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



//add.h

inline int add(int a, int b);


  编译器可以编译通过,但是链接不上,因为声明时显示采用内联方式,内联函数的函数名并不会进符号表(因为会展开),而在主函数区中执行add(a, b)时,就找不到函数的地址,因为符号表中找不到.

4.3 相关面试考点:

(1) 宏函数的优缺点?
优点:
1.增强代码的复用性。(在预处理阶段直接进行宏替换)
2.提高性能。(没call指令,不需要为函数开辟栈帧)
缺点:
1.不方便调试宏。(因为预编译阶段进行了替换)
2.导致代码可读性差,可维护性差,容易误用。(标识符不具备指向性)
3.没有类型安全的检查 。(编译器不会报错)

(2) **C++**有哪些技术替代宏?

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

五、C++的一颗“语法糖”🍭

5.1 基于范围的for循环

在c++11中,有一种写法是基于范围的for循环,被称为“语法糖”,让我们尝一尝这颗“糖”甜不甜吧?🍭🍭🍭

以前我们打印数组中的每个元素,我们是这样的.

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	//对数组的每个元素*2
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		arr[i] *= 2;
	}
	//打印每个元素
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		cout << arr[i] << " ";
	}
	return 0;
}

使用“语法糖”后:

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	//对数组的每个元素*2
	
	错误写法
	//for (auto x:arr)
	//{
	//	x *= 2;
	//}
	
	//正确写法
	for (auto& x:arr)
	{
		x *= 2;
	}


	//打印每个元素
	for (auto x : arr)
	{
		cout << x << " ";
	}
	return 0;
}

解释:

  对于一个有范围的集合而言,程序员来手动再写一遍范围是没有必要的,多余之举,有时候还会写错(例如:忘记下标从0开始)。此事交给任劳任怨的编译器完成比较好,因此C++11标准中引入了基于范围的for循环。

格式:

for循环后的括号由冒号(😃 分为两部分:

第一部分:范围内用于迭代的变量(取名随意,与给变量名起名一样,有意义即可),
第二部分:表示被迭代的范围,数组名表示范围是整个数组.

5.2 “糖虽好,注意粘牙哦!”

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

void print_for(int array[])//数组传参过来之后就是首元素的地址,而不是整个数组,所以范围不确定
{
	for (auto& e : array)
		cout << e << endl;
}

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	print_for(arr);
	return 0;
}

六、认识nullptr

  在C语言阶段,我们提倡创建一个变量之后,要给定一个初始值,减少一些可能出现的未知错误(例如:野指针,随机值等),对于指针我们经常使用下面这段代码进行初始化.也就是NULL.

#include <stdio.h>

int main()
{
	int* p = NULL;
	return 0;
}

NULL究竟是什么呢?我们右击NULL,在弹出的快捷菜单中,选择“转到定义”命令,可以查看到下面这段解释.

在这里插入图片描述

  很明显,NULL就是个宏定义,而这里NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。

不论采取上面何种定义,在使用空值(NULL)的指针时,都不可避免的会遇到一些麻烦 .

栗子:

void f(int)
{
	cout << "f(int)" << endl;
}
void f(int*)
{
	cout << "f(int*)" << endl;
}
int main()
{
	f(0);
	f(NULL);//这里想调用f(int*)函数
	f((int*)NULL);
	return 0;
}

运行结果:

f(int)
f(int)
f(int*)

  这里因为NULL被定义为0,所以并没有调用f(int*)函数成功.因为语言必须向下兼容,所以不能直接修改NULL的定义,为了解决这一问题,C++11中引入了新的的nullptr为void*类型.

在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下
将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。
注意:

  1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的**。**
  2. 在**C++11中,sizeof(nullptr) **与sizeof((void*)0)所占的字节数相同。
  3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr

七、结语:

C++的入门知识就分享到这里了,下次会分享"类和对象的知识",那时候应该要等到暑假啦,码文不易,如果觉得文章有帮助的话,可以三连支持一波吗?💗💗💗

欢迎友友们私信与牛牛讨论问题.,只是牛牛的认知范围有限,目前只关注c语言,数据结构,C++等部分领域.
在这里插入图片描述

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

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

相关文章

【耗时一月】AWS Aurora 数据库 Failover 处理方案

Aurora简述 Amazon Aurora 是亚马逊自研的云原生数据库&#xff0c;除兼容性、性能、扩展性外&#xff0c;它在设计之初&#xff0c;就以极致的可用性作为目标&#xff0c;尽可能减少故障对应用程序的影响。 Amazon Aurora 在故障恢复方面的设计理念主要包括&#xff1a; 1. 能…

Lecture 11 Contextual Representation

目录 Problems with Word Vectors/Embeddings 词向量/嵌入的问题RNN 语言模型Bidirectional RNN 双向 RNNEmbeddings from Language Models 基于语言模型的嵌入ELMo 架构Downstream Task: POS Tagging 下游任务&#xff1a;词性标注ELMo 的表现如何&#xff1f;Other Findings上…

Word控件Spire.Doc 【其他】教程(7): 使用象征符号在 Word 中绘制复选框

Spire.Doc for .NET是一款专门对 Word 文档进行操作的 .NET 类库。在于帮助开发人员无需安装 Microsoft Word情况下&#xff0c;轻松快捷高效地创建、编辑、转换和打印 Microsoft Word 文档。拥有近10年专业开发经验Spire系列办公文档开发工具&#xff0c;专注于创建、编辑、转…

河北沃克HEGERLS仓储货架生产厂家|夹抱式伸缩货叉四向穿梭车新型物流机器人

众所周知仓库作业主要是围绕存取、搬运、拣选、输送分拣而进行的&#xff0c;而随着物流作业的多样化、复杂化&#xff0c;四向穿梭车作为新的存储技术&#xff0c;以其灵活、柔性等特点而备受瞩目。河北沃克在成功研发四向穿梭车的基础上又对其进行了产品的横向发展。目前&…

为什么会有刷掉第一名、刷掉400+的院校?

本期为大家整理热门院校-“南昌大学”的择校分析&#xff0c;这个择校分析专题会为大家结合&#xff1a;初试复试占比、复试录取规则&#xff08;是否公平&#xff09;、往年录取录取名单、招生人数、分数线、专业课难度等进行分析。希望能够帮到大家! –所有数据来源于研招网…

数据库信息速递 10年的数据库使用习惯变革,数据库的使用习惯在被改变 (译)...

开头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;在新加的朋友会分到2群&#xff08;共…

苹果的头显,只要看一眼就行

阅读本文大概需要 1.66 分钟。 今年的 WWDC23 开发者大会&#xff0c;不少人表示 iOS 更新了个寂寞&#xff0c;但 Vision Pro 头显却意外吸引眼球&#xff0c;看来苹果工程师都忙着搞头显去了。 苹果的头显终于还是来了&#xff0c;关于它的传闻&#xff0c;似乎这几年从未间断…

【立体视觉(一)】之成像原理与镜头畸变

【立体视觉&#xff08;一&#xff09;】之成像原理与镜头畸变 一、成像原理一&#xff09;针孔模型二&#xff09;坐标系转换1. 世界坐标系到相机坐标系2. 相机坐标系到图像坐标系3. 图像坐标系到像素坐标系4. 相机坐标系到像素坐标系5. 世界坐标系到像素坐标系 二、镜头畸变一…

数据结构 | 图的遍历(C语言)

一、数据结构定义 1、图 #define MaxVertexNum 100 // 最大可存储的节点数目/*图*/ typedef char VexterType; typedef int EdgeType;typedef struct GraphMatrix {VexterType Vexs[MaxVertexNum]; //结点 EdgeType Edges[MaxVertexNum][MaxVertexNum]; //边int vexnum, a…

【区块链】以太坊L2扩容方案与零知识证明

以太坊L2扩容方案与零知识证明 简介 简要概述以太坊L2层现有解决方案简要概述以太坊L2未来扩容的方向简要概述零知识证明的基本概念和零知识证明在以太坊的运用简要概述stark ware的两个产品&#xff0c;但并不推荐现有使用和研究简要概述polygon zkEVM&#xff0c;推荐使用和…

纷享销客斩获“2022-2023年度用户推荐SaaS品牌”

近日&#xff0c;纷享销客受邀出席CSIC2023第八届SaaS应用大会暨云领奖颁奖典礼活动。纷享销客作为国内领先的CRM 云厂商&#xff0c;凭借产品创新以及强大的行业解决方案能力&#xff0c;以绝对实力荣获“2022-2023年度用户推荐SaaS品牌”。 据悉&#xff0c; C SIC云领奖自 …

【SQL武林秘籍】玩转表及其数据

目录 &#x1f4d6;前言 &#x1f600;数据库约束类型 1️⃣not null 非空约束 2️⃣unique 唯一约束 3️⃣default 默认值约束 4️⃣primary key 主键约束 5️⃣foreign key 外键约束 6️⃣check 限制约束 &#x1f609;新增(insert select) &#x1f604;查询(进…

Podman 是 Docker 的直接替代品吗?

在许多地方&#xff0c;你可以读到Podman是Docker的替代品。但是&#xff0c;真的像听起来那么简单吗&#xff1f;在这篇文章中&#xff0c;你将从一个可以立即投入生产使用的Dockerfile开始&#xff0c;并执行Podman命令&#xff0c;就像你使用Docker时会做的那样。让我们看看…

【Vite环境变量】import.meta.env 和 loadEnv使用和区别

前言 我们在做项目时需要各种配置信息&#xff08;如应用标题、API 地址等&#xff09;&#xff0c;这些配置信息可能在不同环境下有所不同&#xff08;如开发环境和生产环境&#xff09;。 如果每次更改开发或者更改生产环境需要修改源代码中的相关配置&#xff0c;这会导致…

老生常谈:接口幂等性,防止并发插入重复数据

分布式系统中&#xff0c;接口幂等性问题&#xff0c;对于开发人员来说&#xff0c;是一个跟语言无关的公共问题。不知道你有没有遇到过这些场景&#xff1a; 有时我们在填写某些form表单时&#xff0c;保存按钮不小心快速点了两次&#xff0c;表中竟然产生了两条重复的数据&a…

AI时代的三类人:探索掌握AIGC,引领未来的人才之路

&#xff08;本文阅读时间&#xff1a;6 分钟&#xff09; 1 AI时代&#xff1a;ChatGPT引领AIGC技术革命 对于那些热衷于探索新技术的小伙伴而言&#xff0c;ChatGPT早已超越了抽象的概念&#xff0c;我们对其能力已有所了解。那么&#xff0c;ChatGPT究竟能够做些什么呢&…

Java 集合全教程

一、集合简介 集合&#xff08;有时称为容器&#xff09;只是将多个元素分组到单个单元中的对象。集合用于存储、检索、操作和传达聚合数据。通常&#xff0c;它们表示形成自然组的数据项&#xff0c;例如扑克手&#xff08;纸牌集合&#xff09;、邮件文件夹&#xff08;字母…

【Java多线程进阶】synchronized工作原理

前言 本期讲解 synchronized 工作的原理以及常见的锁优化机制&#xff0c;相信大家在看完这篇博文后对 synchronized 工作流程有一定的理解。话不多说&#xff0c;让我们快速进入学习吧~ 目录 1. 锁的工作流程 2. 偏向锁 3. 轻量级锁和重量级锁 3.1 轻量级锁 3.2 重量级锁…

Kubernetes基本存储

Kubernetes基本存储 容器的生命周期可能很短&#xff0c;会被频繁地创建和销毁&#xff0c;容器销毁时&#xff0c;保存在容器中的数据也会被清除。为了持久化保存容器中数据&#xff0c;引入Volume概念。 Volume时Pod中多个容器共同访问的共享目录&#xff0c;它被定义在Pod中…

“碳”寻青蓝锦色,锦江酒店(中国区)用行动点亮酒店可持续发展

第52个世界环境日之际&#xff0c;为响应“减塑捡塑”号召&#xff0c;锦江酒店&#xff08;中国区&#xff09;以“‘碳’寻青蓝锦色”为主题&#xff0c;在6月5日至6月11日期间&#xff0c;开启第二届“绿色生活创益周”&#xff0c;通过线上线下联动&#xff0c;倡导时尚低碳…