C++ 基础入门-命名空间、c++的输入输出、缺省参数、函数重载、引用、内联函数超详细讲解

这篇文章主要对c++的学习做一个基础铺垫,方便后续学习。主要通过示例讲解命名空间、c++的输入输出cout\cin,缺省参数、函数重载、引用、内联函数,auto关键字,for循环,nullptr以及涉及到的周边知识,面试题等。为后续类和对象学习打基础。

目录

什么是c++?

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

一、命名空间

命名空间定义

1.正常的命名空间的定义

域:

命名空间域

展开命名空间

 std:所有C++库命名空间

命名空间可以嵌套

二、C++输入&输出

<<:

>>:

std命名空间的使用惯例:

三、缺省参数

缺省/默认参数:

全缺省(给出所有参数):

半缺省(从右往左连续给):

注意:

四、函数重载

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

*有关于预处理、编译、汇编、链接的详解,可见:

五、引用(别名)

引用概念

使用方法:

引用特性:

1.引用必须初始化

​编辑

2.引用定义后,不能改变指向

3.一个变量可以有多个引用,多个别名

常引用:只能权限缩小,不能权限扩大

使用场景:

 1、做参数(a、输出型参数 b、对象比较大,减少拷贝,提高效率) 这些效果,指针也可以,但是引用更方便

a、输出型参数

b、对象比较大,减少拷贝,提高效率

2、做返回值(a、修改返回对象 b、减少拷贝提高效率)

面试题:引用和指针的区别:

六、内联函数

面试题:宏的优缺点

内联函数概念:

查看方式:

​编辑inline 特性

inline是一种以空间换时间的做法

内联说明只是向编译器发出一个请求,编译器可以选择忽略这个请求:

inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址 了,链接就会找不到。

为什么要防止链接冲突?

在.h未做声明和定义分离时,为什么会出现这样的报错?

以下有三种解决方案:

1、当然就是声明和定义的分离

2、加静态static,修饰函数时,与全局函数相比,有链接属性,只在当前文件可见。(不会进符号表)

3、使用内联(内联也不支持声明与定义分离)

总结:若需在.h中定义函数:

七、auto关键字(C++11)

auto简介

auto使用示例:

auto不能推导的场景

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

范围for的语法

范围for的使用条件

九、指针空值nullptr(C++11)

注意:


什么是c++?

C语言是结构化和模块化的语言,适合处理较小规模的程序。对于复杂的问题,规模较大的 程序,需要高度的抽象和建模时,C语言则不合适。为了解决软件危机, 20世纪80年代, 计算机 界提出了OOP(object oriented programming:面向对象)思想,支持面向对象的程序设计语言应运而生。 1982年,Bjarne Stroustrup博士在C语言的基础上引入并扩充了面向对象的概念,发明了一 种新的程序语言。为了表达该语言与C语言的渊源关系,命名为C++。因此:C++是基于C语言而 产生的,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的 程序设计,还可以进行面向对象的程序设计。

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

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

以下是学习c++时会用到的关键字。

一、命名空间

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

命名空间定义

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

1.正常的命名空间的定义

域:

::域作用限定符

编译器搜索原则:

不指定域:

首先在当前局部域搜索再到全局域。

指定域:

如果指定了域,直接去指定域搜索

示例:

#include<stdio.h>

int x = 0;

int main()
{
	int x = 1;

	printf("hello world\n");
	printf("%d\n", x);
	printf("%d\n", ::x);

	return 0;
}

输出结果:

hello world
1
0

命名空间域

示例:

 proj1是命名空间的名字,一般开发中是用项目名字做命名空间名。

 命名空间中可以定义变量/函数/类型

#include<stdio.h>
#include<stdlib.h>


namespace proj1
{
	int rand = 0;

	int x = 0;

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

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

namespace proj2
{
	int x = 1;
}

int main()
{
	printf("%d\n", proj1::x);
	printf("%d\n", proj2::x);
	
	printf("%p\n", rand);
	printf("%d\n", proj1::rand);

	printf("%d\n", proj1::Add(1,2));


	return 0;
}

输出结果:

0

1
00007FFD91E94AD0
0
3

展开命名空间

同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。

示例:

Queue.h

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

	struct Queue
	{
		struct Node* head;
		struct Node* tail;
		int size;
	};

	void Init(struct Queue* pq);
	void Push(struct Queue* pq, int x);
}

List.h

#pragma once

namespace xxx
{
	struct QNode
	{
		int val;
		struct QNode* next;
		struct QNode* prev;
	};

	void Init(struct QNode* phead);
	void PushBack(struct QNode* phead, int x);
}
#include<stdio.h>
#include"List.h"
#include"Queue.h"

// 展开命名空间
using namespace xxx;

int main()
{
	//printf("hello world\n");

	struct QNode node1;
	struct xxx::QNode node2;
	struct xxx::QNode node3;


	return 0;
}

此时就会通过:using namespace xjh;

到我自定义的命名空间去寻找展开

 std:所有C++库命名空间

示例:


#include<iostream>
using namespace std;

int main()
{
	std::cout << "hello world" << std::endl;
	std::cout << "hello world" << std::endl;
	std::cout << "hello world" << std::endl;
	std::cout << "hello world" << std::endl;
	std::cout << "hello world" << std::endl;
	std::cout << "hello world" << std::endl;
	cout << "hello world" << endl;



	return 0;
}

命名空间可以嵌套

示例:

#include<iostream>
#include<stdio.h>
using std::cout;
using std::endl;
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()
{
	int ret = N1::Add(1, 2);
	int sub = N1::N2::Sub(5, 6);
	cout << ret << endl;
	cout << sub << endl;

}

输出结果:

3
-1

二、C++输入&输出

说明:

1. 使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件 以及按命名空间使用方法使用std。

2. coutcin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含

在< iostream >头文件中。

3. 是流插入运算符,>>是流提取运算符。

4. 使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式。 C++的输入输出可以自动识别变量类型

5. 实际上 cout 和 cin 分别是ostreamistream类型的对象,>>和也涉及运算符重载等知识,我们这里只是简单学习他们的使用。

<<:

1.左移 相当于扩大2倍

2.流插入 自动识别类型

示例:

#include<iostream>
using namespace std;

int main()
{
	// 1、左移
	int i = 100;
	i = i << 1;
	const char* str = "hello world";
	char ch = '\n';

	 2、流插入 自动识别类型
	cout << str << i << ch << endl;
	printf("%s%d %c", str,i,ch);


	return 0;
}

输出结果:

hello world200

hello world200

>>:

右移:缩小2倍

流提取:

示例:

#include<iostream>
using namespace std;

int main()
{
	
	int i;
	const char* str = "hello world";
	char ch;
	
	// 右移
	
	 流提取
	cin >> i >> ch;
	cout << str << i << ch << endl;


	return 0;
}

输出结果:

50 \n(这个是我自己输入的)
hello world50\

ps:关于cout和cin还有很多更复杂的用法,比如控制浮点数输出精度,控制整形输出进制格式等 等。因为C++兼容C语言的用法,这些又用得不是很多,我们这里就不展开学习了。

以下提供一个例子:

#include<iostream>
using namespace std;

int main()
{

	double d = 1.11111111;
	printf("%.2lf\n", d);
	cout << d << endl;

	return 0;
}

输出结果:

1.11
1.11111

std命名空间的使用惯例:

std是C++标准库的命名空间,如何展开std使用更合理呢?

1. 在日常练习中,建议直接using namespace std即可,这样就很方便。

2. using namespace std展开,标准库就全部暴露出来了,如果我们定义跟库重名的类型/对 象/函数,就存在冲突问题。该问题在日常练习中很少出现,但是项目开发中代码较多、规模 大,就很容易出现。所以建议在项目开发中使用,像std::cout这样使用时指定命名空间 + using std::cout展开常用的库对象/类型等方式。

三、缺省参数

缺省参数概念

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

这里与c语言的区别:c语言必须要和申明函数的格式相同。

缺省/默认参数:

示例:

#include<iostream>
using namespace std;
void Func(int a = 0)
{
	cout << a << endl;
}

int main()
{
	Func(1);
	Func();

	return 0;
}

输出结果:

1

0

全缺省(给出所有参数):

示例:

#include<iostream>
using namespace std;
 //全缺省
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(1, 2, 3);
	Func(1, 2);
	Func(1);
	Func();

	return 0;
}

输出结果:

a = 1
b = 2
c = 3

a = 1
b = 2
c = 30

a = 1
b = 20
c = 30

a = 10
b = 20
c = 30

半缺省(从右往左连续给):

示例:

#include<iostream>
using namespace std;
 //半缺省 从右往左连续给
void Func(int a, int b = 20, int c = 30)
{
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl << endl;
}

int main()
{
	Func(1, 2, 3);
	Func(1, 2);
	Func(1);

	return 0;
}

输出结果:

a = 1
b = 2
c = 3

a = 1
b = 2
c = 30

a = 1
b = 20
c = 30

注意:

1. 半缺省参数必须从右往左依次来给出,不能间隔着给,如果生命与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值。

 //a.h
  void Func(int a = 10);
  
  // a.cpp
  void Func(int a = 20)
 {}
  

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

3. 缺省值必须是常量或者全局变量

4. C语言不支持(编译器不支持)

四、函数重载

 C语言不允许同名函数
 CPP语言允许同名函数,要求:函数名相同,参数不同,构成函数重载

 1、参数类型的不同
 2、参数个数不同
 3、参数顺序不同(本质还是类型不同)

示例:

#include<iostream>
using namespace std;

// _Z3Addii
void Add(int left, int right);
//{
//	cout << "int Add(int left, int right)" << endl;
//	return left + right;
//}

// _Z3Adddd
double Add(double left, double right);
//{
//	cout << "double Add(double left, double right)" << endl;
//	return left + right;
//}

void f(int a, char b);
void f(char b, int a);

int main()
{
	Add(1, 2);
	Add(1.1, 2.2);

	//f(1, 'a'); // call f(?)
	//f('a', 1); // call f(?)

	// 
	f(1, 'a'); // call _Z1fic(?)
	f('a', 1); // call _Z1fci(?)

	return 0;

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

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

在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。

*有关于预处理、编译、汇编、链接的详解,可见:

Linux基础 - yum、rzsz、vim 使用与配置、gcc/g++的详细解说以及预处理、编译、汇编、链接的详细步骤ESc 、iso_linux rzsz-CSDN博客

C语言不支持重载  :

因为链接时,直接用函数名去找地址,有同名函数,区分不开

CPP如何支持的呢?

函数名修饰规则,名字中引入参数类型,各个编译器自己实现了一套

五、引用(别名)

引用概念

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

比如:李逵,在家称为"铁牛",江湖上人称"黑旋风"。

使用方法:

类型& 引用变量名(对象名) = 引用实体;

改变引用时,原值也会随之改变

示例:


#include<iostream>
using namespace std;

int main()
{
	int a = 0;

	// 引用,b就是a的别名
	int& b = a;

	cout << &b << endl;
	cout << &a<< endl;

	b++;
	a++;

	cout << b << endl;
	cout << a << endl;

	int& c = a;
	int& d = c;
	d++;
	cout << d << endl;

	return 0;
}

输出结果:

当引用做参数时,会比指针方便很多:

因为,传的就是原值的别名,因此连带原值直接修改

指针和引用功能类似,重叠,但是引用不能完全替代指针,因为引用定义后,不能改变指向

示例:对比以下两种 传的就是原值的别名,因此连带原值直接修改

#include<iostream>
using namespace std;
 //做参数
void Swap(int* a, int *b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

void Swap(int& a, int &b)
{
	int tmp = a;
	a = b;
	b = tmp;
}

int main()
{
	int x = 0, y = 1;
	Swap(&x, &y);
	cout << "x = " << x << " y = " << y << endl;
	Swap(x, y);
	cout << "x = " << x << " y = " << y << endl;
	return 0;
}

引用特性:

1.引用必须初始化

示例:

2.引用定义后,不能改变指向

示例:

C++的引用,对指针使用比较复杂的场景进行一些替换,让代码更简单易懂,但是不能完全替代指针。


引用不能完全替代指针原因:引用定义后,不能改变指向

特别是当我们写一个链表时:因为引用不能改变指向,因此在这里根本不适用。


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

//void PushBack(struct Node* phead, int x)
//{
//	// phead = newnode;
//}

//void PushBack(struct Node** pphead, int x)
//{
//	// *pphead = newnode;
//}

void PushBack(struct Node*& phead, int x)
{
	//phead = newnode;
}

int main()
{
	struct Node* plist = NULL;

	return 0;
}

typedef struct Node
{
	struct Node* next;
	struct Node* prev;
	int val;
}LNode, *PNode;

void PushBack(PNode& phead, int x)
{
	//phead = newnode;
}

//void PushBack(SeqList* ps, int x);
//void PushBack(SeqList& ps, int x);
//{}

int main()
{
	PNode plist = NULL;

	return 0;
}

3.一个变量可以有多个引用,多个别名

常引用:只能权限缩小,不能权限扩大

示例:

#include<iostream>
using namespace std;

void TestConstRef()
{
	const int a = 10;
	//int& ra = a;   // 该语句编译时会出错,a为常量
	const int& ra = a;
	// int& b = 10; // 该语句编译时会出错,b为常量
	const int& b = 10;
	double d = 12.34;
	//int& rd = d; // 该语句编译时会出错,类型不同
	const int& rd = d;
}

并且可用于:函数传参时,防止传的值不能被修改

使用场景:

 1、做参数(a、输出型参数 b、对象比较大,减少拷贝,提高效率)
 这些效果,指针也可以,但是引用更方便

示例:

a、输出型参数

b、对象比较大,减少拷贝,提高效率

#include<iostream>
#include <time.h>
using namespace std;
struct A { int a[10000]; };
void TestFunc1(A a) {}
void TestFunc2(A& a) {}
void main()
{
	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;
}

输出结果:可以观察出这时候使用引用更加提高效率

2、做返回值(a、修改返回对象 b、减少拷贝提高效率)

示例:

下面代码输出结果是什么?

为什么?

int& Add(int a, int b)
{
    int c = a + b;
    return c;
}
int main()
{
    int& ret = Add(1, 2);
    Add(3, 4);
    cout << "Add(1, 2) is :"<< ret <<endl;
    return 0;
}

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

面试题:引用和指针的区别:

语法:

1、引用是别名,不开空间,指针是地址,需要开空间存地址

2、引用必须初始化,指针不做必要

3、引用不能改变指向,指针可以

4、引用相对更加安全,没有空引用,但是有空指针,容易出现野指针,但是不容易出现野引用

5、sizeof 、 ++ 、解引用访问等方面的问题:(引用是引用类型大小,指针永远都是4 or 8)

底层:

汇编层面上,没有引用,都是指针,引用编译后也转换成指针了。

如图:

六、内联函数

面试题:宏的优缺点

宏的优点:

1、增强代码的复用性

2、提高性能

c++有哪些技术替代宏?

1. 常量定义 换用const enum

2. 短小函数定义 换用内联函数

知识衍生:实现两个数相加的宏函数:

易错点
1、不是函数       #define ADD(int a, int b) return a+b;
2、分号#define ADD(a,  b) a+b;
3、括号控制优先级#define ADD(a,  b) ((a)+(b));
核心点:宏是预处理阶段进行替换

为什么加里面的括号:

#include<iostream>
using namespace std;
#define ADD(a, b) ((a)+(b))

// 为什么要加里面的括号?
int main()
{
    if (ADD(1, 2))
    {}

    ADD(1, 2) * 3;

    int x = 1, y = 2;
    ADD(x | y, x & y);  // (x|y + x&y) 
//   |,&的优先级小于+

    return 0;
}

 宏的缺点:
 1、语法复杂,坑很多,不容易控制
 2、不能调试
 3、没有类型安全的检查

由此我们引入内联:

内联函数概念:

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

示例

#include<iostream>
using namespace std;

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

int main()
{
	int ret1 = Add(1, 2) * 3;

	int x = 1, y = 2;
	int ret2 = Add(x | y, x & y);
	return 0;
}

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

查看方式:

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

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

此时再打开反汇编就可以观察到,函数在此时直接展开

inline 特性

inline是一种以空间换时间的做法

如果编译器将函数当成内联函数处理,在编译阶段,会 用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运 行效率。

内联说明只是向编译器发出一个请求,编译器可以选择忽略这个请求:

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

假如有一个func()函数

func()函数有100行     1w个地方调用这个函数

假设inline展开了        100*1w

假设inline不展开        100+1w

《C++prime》第五版关于inline的建议:

inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址 了,链接就会找不到。

防止重复包含同一个头文件:

#pragma once = #ifdefy 1   

                           #define

                           #endif

为什么要防止链接冲突?

在.h未做声明和定义分离,会导致在多个.c文件中包含头文件时出现报错,多重定义的报错。

现在我在我的程序中写道这样的代码:

Stack.cpp

#include"Stack.h"

Stack.h

#include<iostream>

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

Test.cpp

#include"Stack.h"

int main()
{
	int ret = Add(1, 2);

	return 0;
}

运行后就会出现这样的报错:

在.h未做声明和定义分离时,为什么会出现这样的报错?

在stack.cpp与test.cpp中都包含一份Add函数(不构成函数重载),在链接时,会导致文件中出现两个一样的函数,从而重复定义出现报错。

以下有三种解决方案:
1、当然就是声明和定义的分离

2、加静态static,修饰函数时,与全局函数相比,有链接属性,只在当前文件可见。(不会进符号表)

进符号表:将地址加到符号表是为了让别人方便调用

Stack.h

#include<iostream>

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

Stack.cpp

#include"Stack.h"

Test.cpp

#include<iostream>

inline int Add(int a, int b)
{
	return a + b;
}
3、使用内联(内联也不支持声明与定义分离)

Stack.h

#include<iostream>

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

直接在.h中定义解决方案,不在.cpp中定义解决方案,同static,Add地址不会进符号表,inline直接展开。

总结:若需在.h中定义函数:

小函数用:inline(并且,在函数很长时,实际上也不会展开内联函数)

大函数用:static or 声明与定义分离

七、auto关键字(C++11)

类型别名思考

随着程序越来越复杂,程序中用到的类型也越来越复杂,经常体现在:

1. 类型难于拼写

2. 含义不明确导致容易出错

示例:

#include <string>
#include <map>
int main()
{
 std::map<std::string, std::string> m{ { "apple", "苹果" }, { "orange", 
"橙子" }, 
   {"pear","梨"} };
 std::map<std::string, std::string>::iterator it = m.begin();
 while (it != m.end())
 {
 //....
 }
 return 0;
}

std::map::iterator 是一个类型,但是该类型太长了,特别容 易写错。

这里可以通过typedef给类型取别名

比如:

#include <string>
#include <map>
typedef std::map<std::string, std::string> Map;
int main()
{
 Map m{ { "apple", "苹果" },{ "orange", "橙子" }, {"pear","梨"} };
 Map::iterator it = m.begin();
 while (it != m.end())
 {
 //....
 }
 return 0;
}

使用typedef给类型取别名确实可以简化代码,但是typedef有会遇到新的难题:

typedef char* pstring;
int main()
{
 const pstring p1;    // 编译成功还是失败?
 const pstring* p2;   // 编译成功还是失败?
 return 0;
}

在编程时,常常需要把表达式的值赋值给变量,这就要求在声明变量的时候清楚地知道表达式的 类型。然而有时候要做到这点并非那么容易,因此C++11给auto赋予了新的含义。

auto简介

在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,但遗憾的 是一直没有人去使用它,大家可思考下为什么? C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一 个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。

auto使用示例:

void func()
{
      //....
}

int main()
{
    int i = 0;
    int j = i;
    auto k = i;   
//自动识别i的类型加给k
    // auto x;  //auto不能做参数,返回值可做,但是很坑
    auto p1 = &i;
    auto* p2 = &i;
  //指针指向i 地址
    //auto* p3 = i;
    auto& r = i;

    void(*pf1)(int, int) = func; //函数指针
    auto pf2 = func;  

    return 0;
}

void func(int a, int b)
{

}
int main()
{

	void(*pf1)(int, int) = func;
	auto pf2 = func;

	cout << typeid(pf1).name() << endl;
	cout << typeid(pf2).name() << endl;

	return 0;
}

运行这个代码:

auto自动识别类型

特别是当我们学到后期:

int main()
{
	std::map<std::string, std::string> dict;
	//std::map<std::string, std::string>::iterator it = dict.begin();
	auto it = dict.begin();
    cout << typeid(it).name() << endl;

	return 0;
}

虽然auto很方便很好用,但是大多数情况,我们最好还是自定义好类型

auto不能推导的场景

如下,若我们使用许多的auto,在我们的程序中会出现这样的问题:

1. auto不能作为函数的参数

返回值可做,但是很坑

void TestAuto(auto a)
{}
auto f2()
{
	auto ret = 1;
	return ret;
}

auto f1()
{
	auto x = f2();
	return x;
}

auto TestAuto()
{
	auto a = f1();
	return a;
}

void func(int a, int b)
{

}
int main()
{
	
	auto it2 = TestAuto();

	return 0;
}

2. auto不能直接用来声明数组

void TestAuto()
{
    int a[] = {1,2,3};
    auto b[] = {4,5,6};
}

3. 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法

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

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

范围for的语法

示例:

#include"Stack.h"
#include <string>
using namespace std;

int main()
{
   //在C++98中如果要遍历一个数组,可以按照以下方式进行:

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

	// C++11
	// 依次取数组中值赋值给e,自动迭代,自动判断结束
	for (auto& e : array)
	{
		e *= 2;
	}
	cout << endl;

	for (auto e : array)
	{
		cout << e << " ";
	}
	cout << endl;

	return 0;
}

运行结果:

2
4
6
8
10

4 8 12 16 20

对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因 此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范 围内用于迭代的变量,第二部分则表示被迭代的范围。

范围for的使用条件

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

注意:以下代码就有问题,因为for的范围不确定

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

2. 迭代的对象要实现++和==的操作。(关于迭代器这个问题,以后会讲,现在提一下,没办法 讲清楚,现在大家了解一下就可以了)

九、指针空值nullptr(C++11)

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

void TestPtr()
{
 int* p1 = NULL;
 int* p2 = 0;
 
 // ……
}

在c语言中:NULL实际是一个宏且值为0

#ifndef NULL
#ifdef __cplusplus
#define NULL   0
#else
#define NULL   ((void *)0)
#endif
#endif

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

比如:

void f(int)
{
 cout<<"f(int)"<<endl;
}
void f(int*)
{
 cout<<"f(int*)"<<endl;
}
int main()
{
 f(0);
 f(NULL);
 f((int*)NULL);
 return 0;
}

程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的 初衷相悖。 在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器 默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。

注意:

1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入 的。

2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。

3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr

结语:

       随着这篇关于题目解析的博客接近尾声,我衷心希望我所分享的内容能为你带来一些启发和帮助。学习和理解的过程往往充满挑战,但正是这些挑战让我们不断成长和进步。我在准备这篇文章时,也深刻体会到了学习与分享的乐趣。

       在此,我要特别感谢每一位阅读到这里的你。是你的关注和支持,给予了我持续写作和分享的动力。我深知,无论我在某个领域有多少见解,都离不开大家的鼓励与指正。因此,如果你在阅读过程中有任何疑问、建议或是发现了文章中的不足之处,都欢迎你慷慨赐教。         你的每一条反馈都是我前进路上的宝贵财富。同时,我也非常期待能够得到你的点赞收藏,关注这将是对我莫大的支持和鼓励。当然,我更期待的是能够持续为你带来有价值的内容,让我们在知识的道路上共同前行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值