C++小白的逆袭之路——初阶(第一章:C++入门!)


1.1C++关键字(C++98)


1.C++总计63个关键字,C语言32个关键字

注:下面,我们只是看一下C++有多少关键字,不对关键字进行具体的讲解。后面学到再讲。

2.关键字:

在这里插入图片描述


1.2命名空间


1.2.1命名空间的定义


1.定义命名空间:

定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名空间的成员。

2.细节:

1)命名空间中可以定义变量、函数、类型:

//命名空间可以起自己名字的缩写
//1.正常的命名空间定义
namespace LHY
{
  	//命名空间中可以定义变量/函数/类型
	int rand = 10;
  	int ADD(int x, int y)
  	{
  		return x + y;
  	}
  
  	struct Node
  	{
  	    struct Node* next;
  	   	int data;
  	};
}

2)命名空间可以嵌套:

  //test.cpp
  namespace N1
  {
	  	namespace N2
	  	{
	   		int a;
	    		int b;
				int Sub(int x, int y)
				{
					return x - y;
		  		}
	   	}
   }

3)重命名的命名空间会被合并:

//同一个工程中可以出现多个重名的命名空间,编译器最终会将他们合并成一个命名空间
//test.h
//ps:一个工程中的test.h和上面的test.cpp中的N1会合并成一个
namespace N1
{
	int Mul(int x, int y)
	{
			return x * y;
	}
}

如果重名的命名空间中还有重名成员,这样也是不行的,会报重定义的错误。

3.注意:

一个命名空间就定义了一个新的作用域,命名空间中所有内容都局限于该命名空间中。


1.2.2命名空间的使用


1.样例命名空间:

namespace LHY
{
	int rand = 0;
	//结构体类型数据
	struct Node
	{
		struct Node* next;
		int data;
	};
	//函数
	int ADD(int left, int rigth)
	{
		return left + right;
	}
	namespace mine
	{
		float f = 3.14;
	}
}

2.命名空间的使用方式有三种:

1)使用空间名及作用域限定符:

int main()
{
	printf("%d\n", LHY::rand);
	//加在struct后面,不能加在struct前面
	struct LHY::Node Tnode = { NULL, 10 };
	printf("%d\n", LHY::ADD(1, 3));
	//命名空间套娃
	printf("%f\n", LHY::mine::f);
	return 0;
}

2)使用using将命名空间中的某个成员引入:

using LHY::Node;	// 不是using struct LHY::Node
using LHY::ADD;
int main()
{
	struct Node node;
	int c = ADD(1, 4);
	//float f2 = mine::f;//报错,因为没有展开mine这个成员
	return 0;
}
//部分展开如果出现重名仍然报错

3)使用using namespace将命名空间全部展开:

using namespace LHY;
int main()
{
	struct Node node;
	int b = ADD(2, 5);
	// int c = rand; // 仍然报错,因为LHY展开后,里面的rand和全局的rand函数重名
	printf("%d\n", LHY::rand);	// 这样就不会报错了
	printf("%d\n". mine::f);
	return 0;
}

3.再去理解using namespace std;

#include<iostream>

// 先展开头文件,头文件中有命名空间std

// using namespace std;
// 将命名空间std全展开
// 如果上面没有这句话,而我们又想使用std中的内容,怎么办?

// 部分展开
using std::cout;
using std::endl;

int main()
{
	int i;
	std::cin >> i;		//加访问限制符
	cout << i << endl;
	return 0;
}

1.3C++输入&输出


1.4缺省参数


1.4.1省略参数概念


1.缺省参数的概念:

缺省参数是声明或定义函数时,为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。

2.代码演示

using namespace std;

//如果不传参数,默认是0
void Func(int a = 0)
{
	cout << a << endl;
}

int main()
{
	Func();			//不传参数,a是0
	Func(10);		//传参,a是10
	return 0;
}

输出结果:

0
10

1.4.2省略参数分类


1.全缺省

1)含义:

全部的参数都是缺省参数。

2)代码演示:

//全缺省
void Func(int a = 10, int b = 20, int c =30)
{
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl << endl;
}

int main()
{
	Func();
	//从左往右传参
	Func(1);
	Func(1, 2);
	Func(1, 2, 3);
	//不能指定传给哪一个参数
	//Fun(, 2, ); 				//错误写法
	return 0;
}

输出结果:

a = 10
b = 20
c = 30

a = 1
b = 20
c = 30

a = 1
b = 2
c = 30

a = 1
b = 2
c = 3

2.半缺省

1)含义:

有一部分参数是缺省参数。

2)代码演示:

//半缺省 -- 必须从右往左给值
void Func(int a, int b = 20, int c = 30)
{
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl << endl;
}
//错误给法
//void Func(int a = 10, int b, int c = 30);

int main()
{
	// Func();		//a必须给,不给报错
	Func(1);
	Func(1, 2);
	Func(1, 2, 3);
	return 0;
}

输出结果

a = 1
b = 20
c = 30

a = 1
b = 2
c = 30

a = 1
b = 2
c = 3

3.注意:

1)半缺省参数必须从右往左来给缺省值,不能间隔着给。

2)缺省参数不能再函数声明和定义中同时出现。

//注意
//缺省参数不能在定义和声明中同时出现
void Func(int a = 10);

void Func(int a = 10)
{

}
//直接报错

C++中,规定声明和定义分离时,声明给,定义不给。

3)缺省参数必须是常量或者全局变量。

4)C语言不支持缺省参数(编译器不支持)。

4.缺省参数的一个实际运用:

namespace LHY
{
	typedef struct Stack
	{
		int* base;
		int top;
		int capacity;
	}Stack;
}

using LHY::Stack;

void StackInit(Stack* stack, int N = 4)
{
	stack->base = (int*)malloc(sizeof(int) * N);
	stack->top = stack->capacity = 0;
}

int main()
{
	Stack stack;
	StackInit(&stack, 100);				//用多少空间,开辟多少空间
	int i = 0;
	for (i = 1; i <= 100; i++)
	{
		stack.base[i] = i;
		printf("%d ",stack.base[i]);
	}
	return 0;
}

1.5函数重载

自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词的真实含义,即该词被重载了。


1.5.1函数重载的概念


1.含义:

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

2.函数重载的几种情况:

1)参数类型不同:

int ADD(int left, int right)
{
	return left + right;
}

double ADD(double left, double right)
{
	return left + right;
}

int main()
{
	cout << ADD(1, 2) << endl;
	cout << ADD(1.1, 2.2) << endl;
	return 0;
}

输出结果:

3
3.3

2)参数个数不同:

void fun()							//无参数
{
	cout << 1 << endl;
}

void fun(int a)					//一个参数
{
	cout << a << endl;
}

int main()
{
	fun();
	fun(10);
	return 0;
}

输出结果:

1
10

3)参数顺序不同:

void Fun(int a, double b)
{
	cout << "int, double" << endl;
}

void Fun(double a, int b)
{
	cout << "double, int" << endl;
}

int main()
{
	Fun(1, 2.3);			// int, double
 	Fun(2.3, 1);			// int, double
  	return 0;
}

输出结果:

 int, double
 double, int

注意:

顺序不同是指类型顺序不同,不是变量的名字不同。

4)返回值不同,不构成重载:

int Fun()
{

}

void Fun()		// 报错,编译器将其认作同一个函数
{

}

5)有缺省参数的情况(重载结合缺省参数):

void fun(int a)
{
	cout << a << endl;
}

// 第二个函数
void fun(int a, int b = 20)
{
	cout << a << b << endl;
}

// 这个函数和第二个函数不构成重载函数,因为参数的类型,顺序,个数都相同
// void fun(int a, int b)
// {
//	
// }

int main()
{
	// fun(1);		//报错,因为编译器不知道调用哪一个函数,相当于缺省参数没有用
	fun(1, 2);		//调用第二个函数
	return 0;
}

fun(1)的情况,是因为语法存在二义性,第一个和第二个函数是构成重载的。

3.总结:

函数重载和缺省参数没有任何关系,不要混淆概念。


1.5.2C++支持函数重载的原理——函数名修饰(name Mangling)


为什么C++支持函数重载,而C语言不支持?

1.在C/C++中,一个程序要运行起来,需要经历以下几个阶段:
   预处理、编译、汇编、链接
2. 实际项目通常由多个头文件和多个源文件构成,【当前a.cpp中调用了b.cpp中定义的Add函数时】,编译后链接前,由于Add是在b.cpp中定义的,a.o的目标文件中没有Add的函数地址,所以Add的地址在b.o中。
3. 链接阶段会专门处理这种问题,连接器看到a.o调用Add,但是没有Add的地址,就会到b.o的符号表中找Add的地址,然后链接到一起
4. 那么链接时,面对Add函数,链接器会使用哪个名字去找呢?这里每个编译器有自己的函数名修饰规则。
5.Windows下VS的修饰规则过于复杂,而Linux下g++的修饰规则简单易懂,下面我们使用g++演示这个修饰后的名字。
6.采用C语言编译器(gcc)编译后的结果

在这里插入图片描述
结论:在Linux下,采用gcc编译完成后,函数名字的修饰没有发生改变

7.采用C++编译器(g++)编译后的结果

在这里插入图片描述
结论:在Linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中

8.windows下名字修饰规则:

在这里插入图片描述
我们以int N::C::func(int)这个函数签名来猜测Visual C++的名称修饰规则。修饰后名字由“?”开头,接着是函数名由“@”符号结尾的函数名;后面跟着由“@”结尾的类名“C”和名称空间“N”,再一个“@”表示函数的名称空间结束;第一个“A”表示函数调用类型为“__cdecl”(函数调用类型我们在第四章详细介绍),接着是函数的参数类型及返回值,由“@”结束,最后由“Z”结尾。可以看到,函数名、参数类型、名称空间都被加入了修饰后名称。


1.6引用


1.6.1引用概念


1.概念:

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

2.引用类型的定义:

1)定义语法:
  类型& 引用变量名(对象名)= 引用实体

2)代码:

int main()
{
		int a = 0;
		int& b = a;
		//a,b地址一样,说明a,b就是一个变量
		cout << &a << endl;
		cout << &b << endl;
		//对a++,b会++,对b++,a也会++
		a++;
		b++;
		cout << a << endl;
		cout << b << endl;
		return 0;
}

一个可能的输出结果:

000000D36FDDF5C4
000000D36FDDF5C4
2
2 

1.6.2引用特性


特性:

1)引用在定义时必须初始化。
2)一个变量可以有多个引用。
3)引用一但引用一个实体,再也不能引用其他实体。

//测试引用的特性
void TestRef()
{
	int a = 10;
	//int &ra;		//这条语句编译时报错
	int& ra = a;	//初始化
	int& rra = a;	//多个别名
	printf("%p %p %p\n", &a, &ra, &rra);//地址一样
	
	int x = 0;
	ra = x;				//不是让ra成为x的别名,是将x的值赋给ra
	//int& ra = x;		//错误写法,ra已经是a的别名了
	//int& a = x;		//错误写法
}

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

1.6.3常引用


1.常引用涉及权限的问题

1)原则:权限可以平移,可以缩小,但不能放大

int main()
{	
	const int a = 0;

	//权限的放大,报错
	//int& b = a;
	//报错,本来a不可以被更改,如果这句代码可行,那就可以通过b来改变a了,相当于放大了a的权限

	//int b = a;可以,因为这是赋值拷贝,b不影响a
	
	//权限的平移
	const int& c = a;

	//权限的缩小
	int x = 0;
	const int& y = x;
	x = 3;					//x是可以改的
	printf("%d\n", y);		//y也随着x改,但是y不能自己改
	return 0;
}

2.拓展

1)代码1:

int main()
{
	int i = 0;
	double b = i;			//可以
	double& c = i;			//不可以
	const double& d = i;	//可以
	return 0;
}
//为什么?

原因

  • int变double我们知道,是整形提升(如果这一块有问题,可以去我的C语言进阶-整型提升 )。

  • int变double&,就涉及权限的问题。
    在这里插入图片描述
    类之间相互转换时,会创建一个临时变量(编译器自动创建的)这个临时变量具有常属性的,不能被修改,所以上述过程是一个权限提升,是不被允许的。

  • int变const double&属于权限平移,可以。

2)代码2:

int func()
{
	int a = 0;
	return a;
}

int main()
{
	int& ret = func();		//报错
	return 0;
}

原因:

func在返回的时候也创建了一个临时变量来存放a,这个临时变量是具有常属性的。

在这里插入图片描述


1.6.4使用场景


1.做参数

1)交换函数

void Swap(int& x, int& y)
{
	int temp = x;
	x = y;
	y = temp;
}

2)给单链表写一个PushBack

原C语言

typedef struct ListNode
{
	int val;
	struct ListNode* next;
}ListNode, *PListNode;

//C语言中二级指针的玩法
void PushBack(PListNode* pphead, int x)
{
	PListNode newnode = (PListNode)malloc(sizeof(ListNode));
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//...
	}
}

C++的引用

//改成用引用的方式
void PushBack(PListNode& phead, int x)
{
	PListNode newnode = (PListNode)malloc(sizeof(ListNode));
	if (phead == NULL)
	{
		phead = newnode;
	}
	else
	{
		//...
	}
}

2.做返回值:

1)对比传值返回和传引用返回

  • 传值返回:
    在这里插入图片描述

原理

返回时,n的值会先给到一个临时变量(如果n的空间比较小,这个临时变量一般在寄存器中,是函数自己创建的)Count函数的栈帧销毁后,将临时变量中的值给到ret。

总结

传值返回只是返回一个值,count函数结束后,栈帧销毁,n的那一块内存被释放。

  • 传引用返回
    在这里插入图片描述

原理:

  a.不管传值返回还是传引用返回,返回时都需要经历函数栈帧的销毁。
  b.但是,传引用返回,会有机会接触到销毁空间的地址,我们不用关心这个引用类型的名字是什么,只用知道,我们可以通过返回的这个引用,来改变引用对应地址的空间。
  c.如果引用对应的空间在函数返回时被释放了,那么,这个引用会成为一个野指针一样的存在,很危险。

代码演示:

i.没有被覆盖的情况

//传引用返回
int& Count()
{
	int n = 0;
	n++;
	//...

	return n;
}

void test2()
{
	printf("test2:\n");
	int ret = Count();	//用ret去接收别名所指向空间的值
	//无论输出多少回,ret的结果都不变,因为ret是一个新的变量,独占一块空间
	cout << ret << endl;
	cout << ret << endl;
}

void test3()
{
	printf("test3:\n");
	int& ret = Count();	//用ret去接收别名,相当于原来的那块空间现在又有了一个名字ret
	cout << ret << endl;
	cout << ret << endl;//若之前n的那块空间被覆盖,这里输出的结果将是一个随机值
}

int main()
{
	cout << "test2:\n" << endl;
	test2();
	cout << "test3:\n" << endl;
	test3();
	return 0;
}

在VS2022上测试的结果:

在这里插入图片描述
显然,n的空间没有被覆盖。

ii.被覆盖的情况

//ret被覆盖了
int& Add(int a, int b)
{
	int c = a + b;
	return c;
}

void test4()
{
	int& ret = Add(1, 2);
	Add(3, 4);
	cout << "Add(1, 2) is :" << ret << endl;
}

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

VS2022测试结果:

在这里插入图片描述
ret变成了随机值。

总结:

如果函数返回时,出了函数作用域,如果返回对象还在(还没给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。

3.传引用传参和传引用返回的特点:

1)传引用传参(任何时候都可以用):

  • 提高效率;
  • 用于输出型参数(形参的修改会影响实参);

2)传引用返回(若出了函数作用域,对象还在,可以用):

  • 提高效率;
  • 简化语法;

代码示例:(查询或修改一个顺序表)

C语言版:

typedef struct SeqList
{
	int arr[10];
	int size;
	int capacity;
}SeqList;

//C语言版本
//查询第i个位置的数据
int SLAT(SeqList* ps, int i)
{
	assert(i < ps->size);
	//...
	return ps->arr[i];
}

void SLModify(SeqList* ps, int i, int x)
{
	assert(i < ps->size);
	//...
	ps->arr[i] = x;
}

void test6()
{
	SeqList s;
	s.size = 3;
	SLModify(&s, 0, 10);
	SLModify(&s, 1, 20);
	SLModify(&s, 2, 30);
	printf("%d\n", SLAT(&s, 0));
	printf("%d\n", SLAT(&s, 1));
	printf("%d\n", SLAT(&s, 2));
}

C++版本:

//读取或修改第i个位置的值
int& SLAT(SeqList& s, int i)
{
	assert(i < s.size);
	//...
	return s.arr[i];
}
//这种返回没有任何风险,因为函数SLAT返回后,顺序表s的空间本身不会被释放

void test5()
{
	SeqList s;
	s.size = 3;
	SLAT(s, 0) = 10;				//修改
	SLAT(s, 1) = 20;
	SLAT(s, 2) = 30;
	cout << SLAT(s, 0) << endl;		//读取
	cout << SLAT(s, 1) << endl;
	cout << SLAT(s, 2) << endl;
}

实现了查询和修改的一体化,简化了代码

在理解C++版本的代码时,我们可以理解成,还有一个中间变量int& k。就拿SLAT(s, 0) = 10;这段代码来说,SLAT函数在返回是,先把s.arr[0]的引用给了k,此时k就是s.arr[0],我们让k = 10,当然就是让s.arr[0] = 10。在执行cout << SLAT(s, 1) << endl;时,就是打印k,也就是打印s.arr[0]


1.6.5传值、传引用效率比较


1.6.6引用和指针的区别


1.误区:

1)在语法概念上,引用是一个别名,没有独立空间,和其引用实体共用同一块空间。
2)但是,引用在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。

2.看一段代码

int main()
{
	int a = 0;
	int* p1 = &a;
	int& ref = a;

	++(*p1);
	++ref;

	return 0;
}

查看汇编代码

在这里插入图片描述

在这里插入图片描述
可以发现:

引用和指针的汇编代码几乎一致,说明引用的底层仍然是指针。

结论:

1)引用的底层实现,是依靠指针完成的。
2)既然底层是指针,那么引用的效率也是非常高的。

3.引用和指针的不同点:

1.引用概念上定义一个变量的别名,指针存储一个变量地址。
2.引用在定义时必须初始化,指针没有要求。
3.引用在初始化时引用一个实体后,就不能引用其他实体,而指针可以再在任何时候指向任何一个同类型实体。
4.没有NULL引用,但有NULL指针。
5.在sizeof中含义不同引用引用类型的大小,但指针始终是地址空间所占字节个数(32为平台下占4个字节)。
6.引用自加即引用的实体加1,指针自加即指针向后偏移一个类型大小。
7.有多级指针,但是没有多级引用
8.访问实体方式不同,指针需要先解引用,引用编译器会自己处理
9.引用比指针使用起来相对安全


1.7内联函数


引入:

宏的优缺点:

1.缺点:

1)容易出错,语法坑很多。
2)不能调试。
3)没有安全检查。

2.优点:

1)没有类型的严格限制。
2)针对频繁调用的小函数,不需要再建立栈帧了,提高了效率。

3.思考:

有没有什么东西可以替代宏?在包含宏全部优点的情况下,比宏更加实用?

答:有,内联函数。


1.7.1概念


1.何为内联函数:

1)笼统的介绍:

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

2)看一张图:

在这里插入图片描述
不使用内联函数时,编译器会使用call指令跳转到函数所在位置,进行栈帧的创建和销毁。

2.使用内联函数:

inline int Add(int left, int right)
{
	return left + right;
}
//以inline修饰的函数叫做内联函数,
//编译时C++编译器会在调用内联函数的地方展开,
//没有函数调用建立栈帧的开销,
//内联函数提升程序运行的效率
int main()
{
	int ret = 0;
	ret = Add(1, 2);
	return 0;
}

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

3.查看方式:

1)在release模式下,查看编译器生成的汇编代码中是否存在call

2)在debug模式下,需要对编译器进行设置,否则不会展开(因为debug模式下,编译器默认不会对代码进行优化,以下给出VS2022的设置方法):

在这里插入图片描述在这里插入图片描述


1.7.2特性


1.内联的本质(下面的inline指的是内联函数)

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

缺陷:

可能会使目标文件变大。

优势:

少了调用开销,提高程序运行效率。

例:

  • 现在有一个函数Func,里面有100行代码:

在这里插入图片描述
我们在100个位置调用这个函数:

1)如果这个函数时inline,合计有100*100共10000行代码。
2)如果不是inline,算上100句call和Func内部的代码,一共也才200行。

2.inline对于编译器而言只是一个建议,不同编译器关于inline实现的机制可能不同:

一般建议:

1)将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰。
2)函数规模过大(一般是超过10行)、或递归函数使用inline,大概率会被编译器忽略。

3.inline不建议声明和定义分离(写在不同文件中),分离会导致链接错误:

1)原因:

因为inline被展开,没有必要生成函数地址,链接就会找不到函数地址。

2)代码演示:

在这里插入图片描述
在这里插入图片描述

3)解决方案

  • 内联函数不要定义和声明分离,统统写在.h的文件中即可。
    在这里插入图片描述

  • 如果不定义在.h文件中,只能当前文件定义,当前文件使用。

  • 引入新问题:

如果有两个.cpp文件,里面都包含了含有内联函数定义的头文件,生成符号表时会不会重复?
答:不会,因为内联函数不生成指令,不生成地址进入符号表。

4.除了可以用内联函数来替代宏,还可以用const enum。

  1)inline替代的是宏函数。
  2)const enum替代的是宏常量。

5.扩展(inline的一些神奇现象):

下面是我gitee仓库中的一些代码:
  1. 写函数的.cpp文件。
  2. 测试用的.cpp文件,包含主函数。
  3. .h头文件。


1.8auto关键字(C++11)


1.8.1类型别名思考


1.8.2auto简介


1.8.3auto的使用细则


1.8.4auto不能推导的场景


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


1.9.1范围for的语法


1.在C++98中如果要遍历一个数组,可以这样写:

1)没有范围for之前的写法:

//没有范围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 << " ";
	}
	cout << endl;
}

输出结果:

2 4 6 8 10

2)有了范围for以后的写法:

//有了范围for的写法
void Testfor2()
{
	int array[] = { 1,2,3,4,5 };
	//改变一个数组
	for (auto& x : array)			//这里必须要取别名,否则就是一个赋值操作,没有意义
	{
		x *= 2;
	}
	for (auto x : array)
	{
		cout << x << " ";
	}
	cout << endl;
}

输出结果:

2 4 6 8 10
  • for(auto& x:array)是什么意思?

相当于将array数组中的元素依次取出,每取一个就执行一次auto& x = array[i],将取出的这个元素取别名为x。该写法用于修改数组元素。

  • for(auto x:array)是什么意思?

就是auto x = array[i],每取出一个元素就赋值给x。该写法只是为了遍历数组,不做修改。

2.注意:

  for的范围循环和不同循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环。

3.思想:

对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错。因此C++11中引入了基于范围的for循环。for循环后的括号由" : "分为两部分:

1)第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
2)之所以迭代变量的类型通常写成auto,是因为这样写可以涵盖所有的类型情况,要用什么类型的变量接收数组元素交给编译器自己来判断(当然要是你自己知道该用什么类型的变量,自己写也没问题)。


1.9.2范围for的使用条件


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

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

2)看一段问题代码:

// 数组名降级为指针
void Testfor3(int array[])
{
	for (auto x : array)		//直接报错
	{
		cout << x << " ";
	}
	cout << endl;
}

int main()
{
	int array[] = { 1,2,3,4,5 };
	Testfor3(array);
	return 0;
}

如果要迭代数组,迭代对象必须写成数组名,不能写成指针。

2.迭代的对象要实现++和==的操作(关于迭代器的问题,以后会讲)。


1.10指针空值nullptr(C++11)


引入:

C语言中的一个坑:

void f(int)
{
	cout << "f(int)" << endl;
}

void f(int*)
{
	cout << "f(int*)" << endl;
}

int main()
{
	f(0);
	f(NULL);
	f((int*)NULL);
	f(nullptr);
	return 0;
}

输出结果:

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

思考:

为什么传参传NULL,不会调用第二个函数?


1.10.1C++98中的指针空值


1.NULL实际是什么?

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

在这里插入图片描述

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

2.引入中介绍的问题代码:

void f(int)
{
	cout << "f(int)" << endl;
}

void f(int*)
{
	cout << "f(int*)" << endl;
}

int main()
{
	f(0);
	f(NULL);
	f((int*)NULL);
	f(nullptr);

	return 0;
}

程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。

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

3.注意:

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


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

-指短琴长-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值