首页 > 编程语言 > C++入门(命名空间,缺省参数,函数重载,引用,内联函数,auto,范围for)
2021
07-09

C++入门(命名空间,缺省参数,函数重载,引用,内联函数,auto,范围for)

一.C++关键字

C++总共有63个关键字,在入门阶段我们只是大致了解一下就可,在后续博客中会逐渐讲解

二.命名空间

相信学过C++的同学,一定都写过下面这个简单的程序

#include<iostream>
using namespace std;
int main()
{
	cout<<"hello world"<<endl;
	return 0;
}

我们先来看第二行代码,using namespace std , 这行代码是什么意思呢 ?

这里我们就要来引入命名空间的概念,命名空间是用来解决C语言命名冲突问题的,在我们的C语言阶段,如果我们写了下面的程序,是不能通过编译的,原因是因为scanf函数包含在 <stdio.h>这个库里,是一个全局的函数,而我们用scanf去命名全局变量,会报重定义的错误,这就导致了命名冲突,C语言是无法解决这个问题的,因此C++为了解决这个问题,引入了命名空间,来做名字的隔离

#include<stdio.h>
int scanf = 10;
int main()
{
	printf("%x\n",scanf);
}

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

上面的代码改正后如下

#include<stdio.h>
namespace N
{
	int scanf = 10;
}
int main()
{
	printf("%x\n",scanf);  // 以十六进制打印出scanf函数的地址
	printf("%x\n",N::scanf); // 以十六进制打印出 N命名空间域里的 scanf变量
}

其中 N::scanf 中的 :: 为域作用限定符,表明要打印的 scanf 是 N命名空间域里的

了解了命名空间后,回到我们最开始的问题 using namespace std 是什么意思呢?

C++库为了防止命名冲突,将自己库里的东西都定义在一个名为 std 的命名空间里,要使用标准库里的东西,有以下三种方式:

(1).指定命名空间

#include<iostream>
int main()
{
	std::cout<<"hello world"<<std::endl;
}

(2).把std整个展开,即 using namespace std,虽然使用起来比较方便,但如果我们自己定义的东西跟库里冲突了,就没办法解决了,因此在规范的工程项目中不推荐此种方式

#include<iostream>
using namespace std;
int main()
{
	cout<<"hello world"<<endl;
}

(3).对部分常用的库里面的东西展开

#include<iostream>
using std::cout;
using std::endl;
int main()
{
	cout<<"hello world"<<endl;
}

命名空间的几点注意事项 :

(1). 命名空间里既可以定义变量,也可以定义函数
(2).命名空间可以嵌套定义

namespace A
{
	int a;  // 定义变量
	int Add(int left,int right) // 定义函数
	{
		return left + right;
	}
	namespace B  // 嵌套定义
	{
		int b;
		int Sub(int left,int right)
		{
			return left - right;
		}
	}
}

(3).在同一个工程里可以存在多个相同名称的命名空间,在编译时最终会合成到一个命名空间里,因此注意不要定义同名的变量或函数,否则会报重定义的错误

三.缺省参数

(1).缺省参数的概念

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

void Testfunc(int a = 0) // 缺省参数
{
	cout<<a<<endl;
}
int main()
{
	Testfunc(10); // 使用给定的实参
	Testfunc();   // 使用默认值
}

(2). 缺省参数的分类

全缺省参数 : 函数参数都指定了默认值

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

半缺省参数 : 函数参数部分指定了默认值

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

注意 :

(1).半缺省参数必须从右往左依次给出,不能间隔给出

void TestFunc(int a = 10,int b,int c = 20) // 错误写法
{
	cout<<a<<endl;
	cout<<b<<endl;
	cout<<c<<endl;
}

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

a.h
void TestFunc(int a = 10);
a.c
void TetsFunc(int a)
{
	cout<<a<<endl;
}

(3).缺省参数的值必须为常量或全局变量

四.函数重载

(1).函数重载的概念

C语言并不支持同名函数的存在,若定义了同名函数会报重定义的错误,C++在C语言的基础上引入了函数重载的概念,即函数的名称可以相同,但函数的参数列表不能相同(参数的类型,参数的个数,参数的顺序),函数的返回值不能作为重载的标志,原因会在后面解释

// 函数重载
int Add(int left,int right)
{
	return left + right;
}
double Add(double left,double right)
{
	return left + right;
}

C++重载机制很好理解,但C++是怎么支持重载的呢?为什么C语言不支持重载呢?

在讲述这个问题之前,我们要先回顾一下我们之前学的编译链接过程

编译可分为以下三个阶段

1.预处理
预处理阶段主要做的事情有以下几点
(1).头文件的展开
(2).进行宏替换
(3).去掉注释
(4).执行条件编译指令

经过预处理阶段后生成后缀名为.i的文件

2.编译
编译阶段主要做的事情有以下几点
(1).词法分析
在词法分析过程中,我们的源代码程序会被输入到扫描器中,扫描器会将源代码的字符序列分割成不同的记号并分类,如关键字,标识符,字面量,同时扫描器将分好类的记号存储到对应的位置,为后面的操作做好铺垫
(2).语法分析
语法分析是通过建立一颗语法树来实现的,我们所写的语句是由多个表达式组成的,因此我们的语法树是一颗以表达式为结点的树,在语法分析的过程中,操作符的优先级和结合性也被确定下来了,如果在语法分析过程中,出现了语法错误,编译器就会报语法分析阶段的错误
(3).语义分析
语法分析仅仅对语法进行检测,但并不知道语义是否正确,这就需要语义分析器上场了,语义分析阶段主要做的是类型的匹配,转换,比如我们将一个浮点型表达式赋值给一个整型表达式,需要进行隐式类型转换,语义分析需要完成这个步骤,将一个浮点型赋值给一个指针,语义分析会发现类型不匹配,编译器会报错,经过语义分析阶段后,语法树的各个节点会被标记上类型,需要类型转换的,会插入相应的转换节点

经过编译阶段后,生成了后缀名为.s的汇编代码文件

3.汇编
汇编阶段所做的事情比较简单,汇编阶段将编译产生的汇编代码文件转换成二进制机器指令

经过汇编阶段生成后缀名为.o的目标文件

生成的目标文件是按照ELF文件格式进行存储的,ELF文件由多个段组成,如.text(代码段) .data(数据段) .symtab(符号表)等,这里重点要说的是符号表,符号表是一个数组,数组的元素是结构体,结构体描述了文件中符号的各种信息(符号名,符号值,符号类型等)

而C++支持函数重载,C不支持函数重载的原因是它们生成符号名时机制不同

C语言在生成符号表时,符号名是变量或函数名
C++在生成符号表时,符号名是函数名和形参列表的组合

如GCC编译器的修饰规则如下 :
(1).所有的符号都以_Z开头
(2).没有嵌套的名字后跟函数名,函数名前是函数名的字符串长度,后跟参数类型首字母
(3).对于嵌套的名字(在命名空间或类里),后面紧跟'N',然后是命名空间或类的名称,每个名字前是每个名字的字符串长度,后跟函数名,函数名前是函数名的字符串长度,后跟'E',后跟参数类型首字母

由此我们就知道了C++为什么支持重载,而C语言不支持重载,因为C++生成目标文件以后,同名函数只要参数列表不同,符号名就不相同,而C语言生成目标文件以后,同名函数的符号名相同,就会引发命名冲突

五.extern"C"

C++为了与C兼容,在符号的管理上,C++有一个用来声明或定义C的符号的extern "C"关键字的用法

extern "C"
{
	int func(int);
	int var;
}

C++编译器会将大括号里面的代码当成C语言的代码来处理

六.引用

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

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

void TestRef()
{
 	int a = 10;
 	int& ra = a;//<====定义引用类型
 
 	printf("%p\n", &a);
 	printf("%p\n", &ra);
 	// 打印的地址一样
}

注意 : 引用类型必须和引用实体是同种类型的

引用特性 :
(1).引用必须初始化
(2).引用一旦初始化,不能被更改
(3).一个变量可以有多个引用

void TestRef()
{
 	int a = 10;
 	// int& ra; // 该条语句编译时会出错
 	int& ra = a;
 	int& rra = a;
 	printf("%p %p %p\n", &a, &ra, &rra); // 地址都一样 
}

常引用 :

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;
}
const int a = 10;
 //int& ra = a; // 该语句编译时会出错,a为常量

编译出错的原因 :
原来a不能被修改,类型为 const int,但ra的类型为int,使权限提升了

double d = 12.34;
 //int& rd = d; // 该语句编译时会出错,类型不同
 const int& rd = d;

编译出错的原因 :
在进行类型转换时,会产生一个临时变量,rd是临时变量的别名,但因为临时变量具有常性,因此 int& rd = d;是错误的

引用做参数

void Swap(int& left,int& right)
{
	int tmp = left;
	left = right;
	right = tmp;
}

引用做返回值

// 正确写法
int& Count()
{
 	static int n = 0;
 	n++;
 	// ...
 	return n;
}

下面代码的运行结果是什么?

// 错误示范
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;
 	// Add(1,2) is : 7
 	return 0; 
}

错误在于返回了局部变量的引用,Add函数返回的是局部变量c的引用,c出了作用域以后,c的空间就被操作系统回收了

引用和指针的区别
(1).引用必须初始化,指针可以不初始化
(2).引用初始化一个实体之后,不能再引用另外一个实体,指针指向一个实体后,可以再指向另外一个实体
(3).不存在空引用,存在空指针
(4).在语法上,引用是给一个变量取别名,指针取的变量的地址
(5).在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
(6).引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
(7).有多级指针,但是没有多级引用
(8). 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
(9). 引用比指针使用起来相对更安全

七.内联函数

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

C语言为了小函数避免栈帧的消耗,提供了宏函数的支持,那为什么C++还要引入内联函数呢?

(1).宏函数在预处理阶段会被替换掉,不能进入函数内部进行调试
(2).宏函数不支持类型检查,语法复杂,容易出错

inline int Add(int x,int y)
{
	return x + y;
}
int main()
{
	int ret = Add(1,2);
	cout<<ret<<endl;
}

八.auto关键字(C++11)

在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量。

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

int main()
{
	int a = 10;
	auto b = a;
	// 类型声明成auto,可以根据a的类型自动推导出b的类型
}

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

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

int main()
{
 	int x = 10;
 	auto a = &x;  // 推导出 a 的类型为 int*
 	auto* b = &x; // 推导出 b 的类型为 int*
 	auto& c = x;  // 推导出 c 的类型为 int
 	cout << typeid(a).name() << endl;  // int*
 	cout << typeid(b).name() << endl;  // int*
 	cout << typeid(c).name() << endl;  // int
 	*a = 20;
 	*b = 30;
 	 c = 40;
 	 return 0;
}

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

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

(3). auto不能作为函数的参数

// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}

(4). auto不能直接用来声明数组

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

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

九.范围for

C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。

void TestFor()
{
 	int array[] = { 1, 2, 3, 4, 5 };
 	for(auto& e : array)
 		 e *= 2;
 
 	for(auto e : array)
		 cout << e << " ";
	// 2, 4, 6, 8, 10
}

以上就是C++入门(命名空间,缺省参数,函数重载,引用,内联函数,auto,范围for)的详细内容,更多关于c++ 入门基础知识的资料请关注自学编程网其它相关文章!

编程技巧