【C++初阶】一、C++入门基础(详细总结)-程序员宅基地

技术标签: C++  c++  

一、什么是C++

C++是基于C语言而产生的,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行面向对象的程序设计。

二、C++关键字(C++98)

C++总计63个关键字,其中C语言占32个关键字
在这里插入图片描述

其中画圈的是C语言的关键字。这里要注意了:false和true并不是C语言的关键字。
所以说:C++兼容C的绝大多数语言特性

三、命名空间

  在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染namespace 关键字的出现就是针对这种问题的。

#include <stdio.h>
#include <stdlib.h>//这个头文件中包含 rand这个库函数

int rand = 0;//定义的rand这个变量,与库函数中的rand函数重名,所以命名冲突了

int main()
{
    
	printf("%d\n",rand);
}
//这时如果进行编译,则会报错
//编译报错:error C2365: “rand”: 重定义;以前的定义是“函数”

// 命名冲突问题
// 1、我们自己定义的变量、函数可能跟库里面重名冲突
// 2、进入公司项目组以后,做的项目通常比较大。多人协作,两个同事写的代码,命名冲突。
// C语言没有办法很好的解决这个问题
// CPP提出一个新语法:命名空间--关键字namespace

3.1命名空间定义

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

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

例如:将上方代码进行修正,如下:

#include <stdio.h>
#include <stdlib.h>
//定义了一个叫xnh的命名空间 -- 命名空间定义的是一个:域
namespace xnh
{
    
	int rand = 0;
}
int main()
{
    
	printf("%d\n", rand);//访问的是<stdlib.h>中的rand函数,打印的是以十进制打印的该函数地址
	
	printf("%d\n", xnh::rand);//访问的是xnh这个命名空间中的rand变量,打印显示为0
	return 0;
}

在上述代码中会发现,若想访问命名空间中的变量,则需要借助一个作用符 :: 这个符号叫做 域作用限定符,xnh :: rand 的意思就是,去左边这个叫xnh的域(命名空间)里面找rand这个变量。

若我们想打印全局域中的一个变量,可以如下图:
在这里插入图片描述

请注意:命名空间内的变量只能允许声明和初始化,而不能在其中进行赋值!

namespace xnh
{
    
	int a;//ok
	int b=10;//ok
	//b=20;//no
}
1.命名空间的普通定义
namespace xnh
{
    
	// 1、命名空间中可以定义变量/函数/类型
	int rand = 10;

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

	struct Node
	{
    
		struct Node* next;
		int val;
	};
}

//使用
int main()
{
    
	xnh::rand = 20;

	struct xnh::Node node;//注意结构体与函数和变量的不同

	xnh::Add(1, 2);

	return 0;
}
2.命名空间可以嵌套定义
//2. 命名空间可以嵌套定义
namespace N1
{
    
	int a;
	int b;
	int Add(int left, int right)
	{
    
		return left + right;
	}

	namespace N2
	{
    
		int c;
		int d;
		int Sub(int left, int right)
		{
    
			return left - right;
		}
	}
}

//使用
int main()
{
    
	N1::a = 1;
	N1::N2::c = 2;
	N1::Add(1, 2);
	N1::N2::Sub(3, 4);
}
3. 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中

我们在一个工程中,我们可以将函数声明和定义分开来写,如下:

//在List.h头文件中只写声明
 namespace xnh
{
    
	int rand;
	struct ListNode
	{
    
		//...
	};

	void ListInit();
	void ListPushBack(struct ListNode* phead, int x);
}

//在List.cpp源文件中写定义
namespace xnh
{
    
	void ListInit()
	{
    
		// ...
	}

	void ListPushBack(struct ListNode* phead, int x)
	{
    
		//...
	}
}

//在test.cpp源文件中使用
int main()
{
    
	struct xnh::ListNode ln;
	xnh::ListPushBack(NULL, 1);
	
	return 0;
}

虽然将xnh这个命名空间分开写在了List.hList.cpp两个文件中,但最后会合成同一个命名空间中

3.2命名空间的使用

1.加命名空间名称及作用域限定符

在这里插入图片描述

2.使用using namespace 命名空间名称引入

using namespace 命名空间名称;

这句代码的意思就是把整个命名空间展开,这样当我们使用命名空间下的变量、函数等等就不需要加作用域限定符了,用起来方便,但隔离失效了。

日常练习,小程序,这么用可以,项目最好不要这么用。
例如:
在这里插入图片描述

会发生如下的情况:
在这里插入图片描述

这样容易造成命名冲突问题,为了解决这个问题,出现了第三种引入方法。

3.使用using将命名空间中成员引入

第三种方法就是指定展开–把常用的展开,自己在定义的时候避免跟常用重名即可
例如:
在这里插入图片描述

这种方法可以防止命名冲突的问题,因为它只引入了一部分。

四、C++输入与输出

cout :输出
cin :输入

我们初学一门语言,输出Hello world是必不可少的,C++输出方法如下:

#include<iostream>
using namespace std;

int main()
{
    
	cout<<"hello world!"<<endl;
	return 0;
}

对以上程序有几点解释:

首先是iostream头文件,是标准输入/输出文件,可以支持使用cout和cin等函数,分别代替printf和scanf;

其次是using namespace std,就是我们上文讲到的命名空间,std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中

还有就是c++特有的输入输出,cout,cin,都有与之对应的符号,如下:

<< :流插入操作符。与cout配合使用,可以将所有你需要输出的变量或者字符串流入到cout中,让cout负责输出。我们将cout相当于控制台就好理解了
>> :流提取操作符。与cin配合使用,可以将你输入的值流入到某变量中。
就相当于把你在屏幕上输入的数据或字符串通过>>符号流入到所需的某变量中。我们可以将cin相当于键盘

还有就是 endl与"\n"(换行符)等价,都是进行换行的意思。

注意:使用 cout标准输出(控制台)cin标准输入(键盘) 时,必须包含< iostream >头文件以及std标准命名空间。

cout可以自动识别变量的类型

使用示例:

int a = 10;
char arr[] = "abcdef";
double b = 1.11;

float c = 0.0;

cin>>c;
cout<<a<<endl;
cout<<arr<<endl;
cout<<c<<endl;

在这里插入图片描述

五、缺省参数

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

//缺省参数
#include<iostream>

using namespace std;

//这儿的 0 就相当于缺省参数,如果实参什么都没传过来,缺省参数就赋值给a。
void func(int a = 0)
{
    
	cout << a << endl;
}

int main()
{
    
	//调用
	func(10);
	func();  //在c语言中这样写肯定是不行的,但是在c++中有了缺省参数,如果你什么都不传,只要你前面有缺省参数的存在,就能过。
	return 0;
}

在这里插入图片描述

缺省参数分类

1.全缺省参数

全缺省参数就是为函数的所有参数都设置一个默认参数,例如:

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

我们调用此函数时,有以下几种方法:

Func();
Func(1);
Func(1,2);
Func(1,2,3);

传进去的实参必须是从左向右传的,而且必须连续的。比如以下传参方法就是错误的

Func(,10,);//错误,传参必须是从左往右,连续传参
2.半缺省参数

半缺省参数就是将函数的参数部分初始化,例如:

void Func(int a, int b = 10, int c = 20)
{
    
    cout<<"a = "<<a<<endl;
    cout<<"b = "<<b<<endl;
    cout<<"c = "<<c<<endl;
}

注意:
1.半缺省参数必须只能从右向左依次给出,而且必须连续,不能间隔着给。
例如以下方式就是不行的

int Func(int a,int b=10,int c);//错误,不连续
int Func(int a=10,int b=20,int c);//错误,不是从右往左给出

2.缺省参数不能在函数声明和定义中同时出现,推荐在声明中给

//a.h
void Func(int a = 10);

// a.c
void Func(int a = 20)
{
    
	//.....
}

// 注意:如果声明与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值。

3.缺省值必须是常量或者全局变量
4. C语言不支持(编译器不支持)

六、函数重载

重载的意思是具有多重含义,那么函数重载即是一个函数具有多种功能,也就是对同一个函数名的不同的“解释”。

定义:
C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 顺序)必须不同,即参数个数,参数顺序,参数类型三者有一个不同即可。常用来处理实现功能类似数据类型不同的问题

比如:

//参数类型不同
int Add(int a, int b)
{
    
	return a+b;
}
double Add(double a, double b)
{
    
   return a+b;
}

//参数个数不同
int Func(int a);
int Func(int a,int b);

//参数顺序不同 -- 顺序不同指的是,形参类型的顺序不同
int Sub(int a, char b);
int Sub(char b, int a);

注意:函数重载与函数返回值类型无关。

6.1函数重载的原理

为什么C++支持函数重载而C语言却不支持呢?
在C/C++中,一个程序要运行起来,C/C++的源文件都是需要进行预处理,编译,汇编,链接,最后生成可执行程序的。和C的源文件一样,都是源文件先单独编译生成目标文件合到一起链接成可执行程序。

实际我们的项目通常是由多个头文件和多个源文件构成,函数的声明和定义分离会使编译器在编译源文件时暂时找不到函数的地址,那么其查找函数的规则则会在链接时体现。所以将函数的声明和实现放到两个文件中,更方便观察C++和C对于函数名字修饰规则的不同。

由于Windows下vs的修饰规则过于复杂,而Linux下gcc的修饰规则简单易懂,下面我们使用了gcc演示了这个修饰后的名字。

采用C语言编译器编译后结果:
在这里插入图片描述

结论:在linux下,采用gcc编译完成后,函数名字的修饰没有发生改变。

采用C++编译器编译后结果:
在这里插入图片描述

结论:在linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中。

通过这里就可以理解C语言无法支持重载了,因为c++会根据函数的参数对函数名进行修饰,只要参数不同,修饰出来的名字就不一样,就支持了重载,而C对函数名却不会修饰,所以没办法支持重载,因为同名函数没办法区分。

函数名不相同,其生成的函数地址也不会相同,所以在调用的时候也不会产生冲突

我们看完 函数重载 的定义后,可能会产生一个疑问,如:

返回值不同,构成重载吗??为什么呢?? 请看下面调用函数:

//两者返回值不同
int f(int a, int b)
{
    
	cout << "f(int a,char b)" << endl;
	return 0;
}

char f(int a, int b)
{
    
	cout << "f(int a,char b)" << endl;
	return 'A';
}

int main()
{
    
	f(1, 1);
	f(2, 2);
	//这里调用会存在二义性,因为调用时不指定返回类型,所以他不知道该调用哪个函数
	return 0;
}

返回值不同,不构成重载原因,并不是函数名修饰规则
真正原因是调用时的二义性,无法区分,
因为调用时不指定返回值类型

七、引用

引用是C++中一个重要的语法,在后期应用非常广泛。相较于指针,它更加方便也更好理解。

定义:

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

通俗来讲,就是为一个变量取一个别名。就如同家人为你取一个小名,两个名字都代表着同一个人。

引用的基本使用:
类型+& +引用变量名(对象名) = 引用实体;

void func() 
{
    
	int a = 10;
	int& b = a;//为a取别名叫b
	b = 20;
	int& c = b;	//为b取别名叫c
	c = 30;
}

运行结果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
从这里可以看出这里分别给a取了两个别名,并且别名值的改变也会影响变量a,因为别名本身代表的就是a,同时,这三个变量的地址都是同一地址,所以证明引用实体和引用变量共用同一块内存空间。

7.1 引用的特性

1.引用在定义时必须初始化
void func()
{
    
	int a = 10;
	//int& b;  //err,定义引用必须初始化
	int &b = a;//正确处理	
}
2.一个变量可以有多个引用
void func()
{
    
	int a = 10;
	int &b = a;//正确处理
	int &c = a;
	int &d = a;
}
3.引用一旦引用了一个实体,就不能再引用其他实体
int a = 10;
int& b = a;
int c = 20;
b = c;//这里是把c的值赋给b,并不是让b变为c的别名

在这里插入图片描述

7.2 常引用

常引用的定义:被const修饰的引用就是常引用。
常引用会涉及到权限的问题,如:

//权限放大
const int a = 0;
int& ra = a;//Err
//权限缩小
int b = 0;
const int& rb = b;
//权限相同
const int c = 0;
const int& rc = c;

引用const修饰的常变量时,如果引用不加const,那么就造成了权限扩大显然时不允许的。权限不允许扩大但是可以不变和缩小,在用引用做参数时,可以加const修饰防止实参被修改。

double d = 1.11;
int i = d;

double d = 1.11;
const int& i = d;

将浮点型变量d赋值给整型变量i时会发生隐式类型转换,中间会产生一个临时变量,将d的数据截断放入临时变量中再将临时变量赋值给i。类似于不同整型数据进行比较会发生整型提升,其实就是将各自产生的临时变量进行整型提升再进行比较,这也是二者的值不会发生改变的原因。
在这里插入图片描述
这样的临时变量是个右值,具有常属性,所以只有常引用才能引用这样的临时变量。也就使得const Type&的引用类型可以引用任意类型的变量。

7.3 引用的使用场景

1.引用做参数
void Swap(int& rx, int& ry) 
{
       //传引用
	int tmp = rx;
	rx = ry;
	ry = tmp;
}
int main() 
{
    
	int x = 0, y = 1;
	Swap(x, y);
	cout << x << " " << y << endl;
	return 0;
}

但引用做参数时,在一些情况下会产生歧义,例如:

void Swap(int x, int y) 
{
       //传值
    int tmp = x;
    x = y;
    y = tmp;
}
void Swap(int* px, int* py) 
{
    	//传址
	int tmp = *px;
	*px = *py;
	*py = tmp;
}
void Swap(int& rx, int& ry) 
{
    	//传引用
	int tmp = rx;
	rx = ry;
	ry = tmp;
}
int main() 
{
    
	int x = 0, y = 1;
	Swap(&x, &y);//这里调用没问题,是进行传址调用
	cout << x << " " << y << endl;
	Swap(x, y);//err,这里就会产生错误,编译器将不会知道是调用传值还是传引用,所以会产生歧义
	cout << x << " " << y << endl;
	return 0;
}

三者参数列表分别为整型、指针类型和引用类型,虽构成了函数重载,但调用时仍会有歧义。

引用做参数举例:
在这里插入图片描述
如图所示,单链表中因可能修改头指针plist而使用二级指针传参的方法可以选择一级指针的引用替代。phead就是plist的引用,改变phead就是改变plist。

2.引用做返回值

传引用返回:这点比较难理解,先看下面的例子:

// 传引用返回
int& Add(int a, int b)
{
    
	int c = a + b;
	return c;
}
int main()
{
    
	int& ret = Add(1, 2);
	cout << ret << endl;
	return 0;
}

当Add函数栈帧销毁后,函数返回了c的引用,再执行int& ret=Add(1,2)相当于取c的值赋值给ret,而c的内存空间已返还给操作系统,就造成了非法访问。
在这里插入图片描述

mov eax, dword ptr [c]
//将变量c的值放入寄存器eax中
lea eax, [c]
//将变量c的地址放入寄存器eax中

返回值和返回引用的区别就是一个用临时变量存储了c的值返回的是拷贝对象,一个用引用访问了原来c的内存空间读取c的值返回的是返回值对象本身。之所以将地址放到寄存器中带回,是因为引用的底层实现是指针,一会要通过地址访问c的内存空间。
在这里插入图片描述
例如:

int& Add(int a, int b) 
{
    
	int c = a + b;
	return c;
}
int main() 
{
    
	int& ret = Add(1, 2);
	cout << ret << endl;
	Add(10, 20);
	cout << ret << endl;
	return 0;
}

在这里插入图片描述
在这里插入图片描述
两次打印ret一次是3一次是30。从这个例子可以看出,引用将c的地址带了回来ret也初始化成为c的引用,Add栈帧销毁又创建,每次的c变量都在同一块空间ret也引用了这块空间,所以下次调用Add函数时这块空间的值被修改成了30。

但这输出的30是唯一的值吗?答案是错误的。

需要注意的是这个ret的结果可能会是随机值,因为c是一个局部变量,随着栈帧的销毁会一块销毁,当空间被销毁了,这块空间就会归还给操作系统,程序员就没有使用权限了,而为什么是随机值,是因为这块空间可能会被清空置成随机值,当我们在打印ret结果值前面加一个printf时,ret就会变为随机值

例如:
在这里插入图片描述

总结:

出了函数作用域,返回变量不存在了,不能用引用返回 ,因为引用返回的结果是未定义的
出了函数作用域,返回变量存在,才能用引用返回。

传值与传引用效率对比

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

在这里插入图片描述
传引用和传指针差不多,每次调用都访问的是同一块空间,而传值每次调用都会开辟一样大的空间,所以传引用的效率比传值高很多,数据越大对性能的提升越大。引用可以作输出型参数或输出型返回值,就是达到形参改变外面的实参的目的。

7.4 引用和指针的区别

在这里插入图片描述
引用与指针的不同点:

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

语法层:
指针和引用是完全不同的概念,指针是开辟空间,存储变量的地址,引用不用开辟空间,仅仅是对变量取别名。

底层汇编时:
从上图汇编代码可以看出,引用是用指针实现的。

八、内联函数

在频繁调用一些小的,简单,仅有几行代码的函数时,我们就会反复的开辟栈空间,然后进行函数压栈,销毁等一系列操作,这样不仅会浪费空间,还降低效率。解决这种问题除了使用C语言的语法宏以外,C++还有新语法叫内联函数。

8.1 概念

inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行的效率。
在这里插入图片描述
如果在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的调用。
在这里插入图片描述
这时就没有调用ADD函数,而是运行到此语句时,将函数进行了展开,这样就提升了程序的效率。

内联函数只在release模式下起作用,若想在Debug模式下查看需要配置项目属性。
在这里插入图片描述

8.2 特性

  1. inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。
  2. 内联函数仅是对编译器的一种建议,编译器自动优化,如果不符上述要求,编译器会放弃优化。
  3. 内联函数不建议声明和定义分离,内联函数不会调用就没有函数地址分离会导致链接失败。

宏的优点是可以增强代码的复用性,提高性能,缺点是无法调试,可维护性差,没有类型检查。C++出台内联函数就是推荐使用内联函数。

九、auto关键字(c++11)

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

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

int TestAuto()
{
    
	return 10;
}
int main()
{
    
	int a = 10;
	auto b = a;
	auto c = 'a';
	auto d = TestAuto();

	//typeid(变量).name 查看变量类型
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;

	//auto e; 无法通过编译,使用auto定义变量时必须对其进行初始化
	return 0;
}

在这里插入图片描述

9.1 auto的使用

1.auto与指针和引用结合起来使用

用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&

int main()
{
    
 int x = 10;
 auto a = &x;    //用auto和auto*没有任何区别
 auto* b = &x;   //用auto和auto*没有任何区别
 auto& c = x;    //用auto声明引用类型时则必须加&
 cout << typeid(a).name() << endl;
 cout << typeid(b).name() << endl;
 cout << typeid(c).name() << endl;
 *a = 20;
 *b = 30;
 c = 40;
 return 0; 
 }

在这里插入图片描述

2.在同一行定义多个变量

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

void TestAuto()
{
    
 auto a = 1, b = 2; 
 auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}

9.2 auto不能推导的场景

1.auto做为函数的参数
// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)//err
{
    }
2.auto不能直接用来声明数组
void TestAuto()
{
    
 int a[] = {
    1,2,3};
 auto b[] = {
    456};//err
}
  1. 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法

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

十、基于范围的for循环(c++11)

10.1 范围for的语法

对于一个有范围的集合仍需说明它的范围,这无疑是多余的,因此C++11引入范围for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
:前是循环变量,后面是循环范围

//C
for (int i = 0; i < sz; i++) 
{
    
    cout << arr[i] << endl;
}

//C++
for (auto e : arr) 
{
    
    cout << e << endl;
}

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

范围 for 循环返回的对象是数组元素值的拷贝,所以若要写入数组元素的话,需要使用引用。

for (int i = 0; i < sz; i++) 
{
    
    arr[i] *= 2;
} 
for (auto& e : arr) 
{
    
    e *= 2;
}

10.2 范围for的使用条件

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

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

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

数组传参本质就是指针,所以不知道数组的具体范围,因此是错误的。

2. 迭代的对象要实现++和==的操作

十一、指针空值nullptr(C++11)

C++98中的指针空值

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

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*)的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,比如:

#include <iostream>
using namespace std;
void Fun(int p)
{
    
	cout << "Fun(int)" << endl;
}
void Fun(int* p)
{
    
	cout << "Fun(int*)" << endl;
}
int main()
{
    
	Fun(0);           //打印结果为 Fun(int)
	Fun(NULL);        //打印结果为 Fun(int)
	Fun((int*)NULL);  //打印结果为 Fun(int*)
	return 0;
}

程序本意本意是想通过Fun(NULL)调用指针版本的Fun(int* p)函数,但是由于NULL被定义为0,Fun(NULL)最终调用的是Fun(int p)函数。

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

C++11中的指针空值

对于C++98中的问题,C++11引入了关键字nullptr

  1. 在使用nullptr表示指针空值时不需要包含头文件,因为nullptr是C++11作为关键字引入的。
  2. 在C++11中,sizeof(nullptr)与sizeof((void*)0)所占的字节数相同,大小都为4
  3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_58124165/article/details/121505854

智能推荐

sklearn构建stacking模型进行堆叠多模型分层级回归分析_sklearn stacking-程序员宅基地

文章浏览阅读2.4k次。sklearn构建stacking模型进行堆叠多模型分层级回归分析Stacking 就是当用初始训练数据学习出若干个基学习器后,将这几个学习器的预测结果作为新的训练集,来学习一个新的学习器。Stacking 的基础层通常包括不同的学习算法,因此stacking ensemble往往是异构的。stacking的步骤:假设有1000个样本,70%的样本作为训练集,30%的样本作为测试集。STEP1:在训练集上采用算法A、B、C等训练出一系列基学习器。STEP2:用这些基学习器的输出结果._sklearn stacking

Java 计划框架-程序员宅基地

文章浏览阅读180次。 所有类型的 Java 应用程序一般都需要计划重复执行的任务。企业应用程序需要计划每日的日志或者晚间批处理过程。一个 J2SE 或者 J2ME 日历应用程序需要根据用户的约定计划闹铃时间。不过,标准的调度类 Timer 和 TimerTask 没有足够的灵活性,无法支持通常需要的计划任务类型。在本文中,Java 开发人员 Tom White 向您展示了如何构建一个简单通用的计划框架,以用..._java计划任务框架

Qt串口通信简单编程步骤(QSerialPort)_qterialport-程序员宅基地

文章浏览阅读7.2k次,点赞6次,收藏52次。Qt有自带的串口通信类QSerialPort:提供访问串口的功能。您可以使用QSeralPortinfo助手类获得关于可用串行端口的信息,该帮助程序允许枚举系统中的所有端口。有助于获得您想要使用的串行端口的正确名称。您可以将助手类的对象作为参数传递给setPort()或setPortName()方法,以分配所需的串行设备。在设置端口之后,您可以使用open()方法以只读(r/o)、只写(w/o)或读写(r/w)模式打开它。注意:串行端口总是以独占访问的方式打开(也就是说,没有其他进程或线程可以访问已经打_qterialport

ProtoBuf 中 oneof 使用的坑(libprotobuf FATAL /protobuf_install/include/google/protobuf 问题)_check failed: (index) < (current_size_)-程序员宅基地

文章浏览阅读1.5w次,点赞5次,收藏3次。ProtoBuf 中 oneof 使用的坑在运行的时候,遇到了如下的报错:libprotobuf FATAL /protobuf_install/include/google/protobuf/repeated_field.h:1184] CHECK failed: (index) < (current_size_)后来发现是由于在Proto的message定义中,未妥善使用one..._check failed: (index) < (current_size_)

【转】区块链公链的 3 大性能难点、5 大体验障碍_区块链的性能影响因素-程序员宅基地

文章浏览阅读1w次,点赞5次,收藏4次。公链作为区块链世界的基础设施中的基石,极大地影响着上层应用的效率、成本以及用户体验。如果从比特币开始算起,公链一路走来已经 10 年了,但至今为止还远未到技术收敛的阶段。在这第 11 个年头,我细数一下已经被大家广为关注的方向,和一些尚未被大家关注的方向。性能难点 1——速度性能问题从区块链最开始就被大家意识到,直观的体验就是速度,也就是一个交易多久能被确认。最初这个瓶颈是共识算..._区块链的性能影响因素

Groovy语法介绍-程序员宅基地

文章浏览阅读97次。1. 介绍Groovy 是基于 JRE 的脚本语言,和Perl,Python等等脚本语言一样,它能以快速简洁的方式来完成一些工作:如访问数据库,编写单元测试用例,快速实现产品原型等等。Groovy 是由James Strachan 和 Bob McWhirter 这两位天才发明的(JSR 241 2004 年 3 月)。Groovy 完全以Java ..._groovy sh 语法

随便推点

google浏览器打不开网页解决办法,返回ERR_TUNNEL_CONNECTION_FAILED“_err_tunnel_connection_failed csdn-程序员宅基地

文章浏览阅读8.4k次,点赞4次,收藏8次。google浏览器打不开网页解决办法,而且显示:“网页可能暂时无法连接,或者它已永久性地移动到了新网址,返回ERR_TUNNEL_CONNECTION_FAILED”打开cmd:输入:ipconfig /flushdns亲测刷新一下dns配置就可以访问了,如果还不行在一次输入下面的指令后,重启电脑就可以了,但多数情况win10应该都不用重启nbtstat –rnetsh int ip resetnetsh winsock reset..._err_tunnel_connection_failed csdn

单片机c语言bin码与bcd码,PIC单片机的BIN码转BCD码-程序员宅基地

文章浏览阅读225次。;;********************************************************************; Binary To BCD Conversion RouTIne; This rouTIne converts a 16 Bit binary Number to a 5 Digit; BCD Number. T..._bin 格式转换成bcd c语言代码

进入正在运行的Docker容器的4种方式_进入正在运行的dcoker-程序员宅基地

文章浏览阅读3.4w次,点赞2次,收藏35次。在使用Docker创建了容器之后,如何进入该容器呢?进入Docker容器比较常见的几种做法如下:使用docker attach使用SSH使用nsenter使用exec一、使用docker attach进入Docker容器Docker提供了attach命令来进入Docker容器。接下来我们创建一个守护态的Docker容器,然后使用docker attach命令进入该容器。$..._进入正在运行的dcoker

基于高德地图api的热力图配置及显示调优_热力图颜色梯度-程序员宅基地

文章浏览阅读1.3w次,点赞6次,收藏47次。基于高德地图api的热力图配置及显示调优缘起使用热力图的基本配置加载js组件和数据加载组件加载数据重要参数说明显示调优rediusmax参考缘起领导要求根据公交订单的起点/终点数据,来展示用户下单的起点/终点的分布情况,这时用热力图来表示分布情况更加直观。在使用高德地图热力图api的过程中,被几个参数折磨到凌晨2点多,将经验总结如下。使用热力图的基本配置在热力图api实例中有标准代码。..._热力图颜色梯度

MySQL——习题:每个部门当前员工最高薪水_mysql每个部门的最高工资的语句-程序员宅基地

文章浏览阅读512次。有一个员工表dept_emp简况如下:有一个薪水表salaries简况如下:获取所有部门中员工薪水最高的相关信息,给出dept_no, emp_no以及其对应的salary,按照部门编号升序排列,以上例子输出如下:解法1:SELECT d1.dept_no, d1.emp_no, s1.salaryFROM dept_emp as d1INNER JOIN salaries as s1ON d1.emp_no=s1.emp_noAND d1.to_date='9999-01-01'A_mysql每个部门的最高工资的语句

The constructor Service(URL, QName, WebServiceFeature[]) is undefined-程序员宅基地

文章浏览阅读697次。Service(URL, QName, WebServiceFeature[]) is undefined 原因是CXF自带的javax.xml.service 版本高过 jdk的javax.xml.service。 一个解决办法是: 在使用wsdl2java时,加入参数 -frontend jaxws21 这个问题的消息解释可以在这里看到 : http://david-commerce.itey..._the constructor service(url, qname, webservicefeature[]) is undefined

推荐文章

热门文章

相关标签