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++ 入门基础知识的资料请关注我们其它相关文章!

(0)

相关推荐

  • 快速入门的一些C\C++书籍

    人们常常问我有什么C++和编程的书籍推荐,今天就为大家分享了几本 第一个注意项:如果你打算学习C++,请务必学习最新版的C++ 2011.这个版本的C++移除了许多由C++强大带来的一些痛苦之处.另外,也不用担心C++ 2014的书籍,大多数编译器已经开始支持它了. 学习编程 学习编程包含以下几个重要方面: 了解语言的语法 知道那些特性可以使用和何时使用 写出可读性好的代码:编译器可以理解,但是下一个人是否可以阅读呢? 在一个更高层次设计结构良好的程序 为了学习一门语言,通常我们可以找到叫<X语

  • 使用C++进行Cocos2d-x游戏开发入门过程中的要点解析

    总结了下,新手引导的要点有以下几个: 画面的变化. 触摸和按钮响应. 逻辑处理及代码组织. 下面我们就详细讲述这几个要点: 一.画面的变化. 对个这个不同的游戏的做法不尽相同,归结起来大概有这么几种: 1.画面整体变暗 这个比较简单,cocos2d就有现成接口: CCSize visibleSize = CCDirector::sharedDirector()->getVisibleSize(); //第一个参数是颜色ccc4(r,g,b,a) a取值(0~255),越大越不透明 下面两个参数为

  • C++编程中队内联函数的理解和使用

    函数调用过程 c++经过编译生成可执行程序文件exe,存放在外存储器中.程序启动,系统从外存储器中将可执行文件装载到内存中,从入口地址(main函数起始处)开始执行.程序执行中遇到了对其他函数的调用,就暂停当前函数的执行,并保存下一条指令的地址作为从被调函数返回后继续执行的入口点,保存现场.然后转到被调函数的入口地址执行被调函数.遇到return语句或者被调函数结束后,恢复先前保存的现场,从先前保存的返回地址处继续执行主调函数的其余部分. 内联函数 函数调用需要进行现场保护,以便在函数调用之后继

  • 详解C++中的内联函数和函数重载

    内联函数(内嵌函数,内置函数) 调用函数时需要一定的时间和空间的开销.C++提供一种提高效率的方法,即在编译时将函数调用处用函数体替换,类似于C语言中的宏展开.这种在函数调用处直接嵌入函数体的函数称为内联函数(inline function),又称内嵌函数或内嵌函数. 指定内联函数的方法很简单,只需要在定义函数时增加 inline 关键字. 注意:是在函数定义时增加 inline 关键字,而不是在函数声明时.在函数声明时增加 inline 关键虽然没有错误,但是也没有任何效果 inline 关键

  • C++编程中的命名空间基本知识讲解

    命名空间是一个声明性区域,为其内部的标识符(类型.函数和变量等的名称)提供一个范围.命名空间用于将代码组织到逻辑组中,还可用于避免名称冲突,尤其是在基本代码包括多个库时.命名空间范围内的所有标识符彼此可见,而没有任何限制.命名空间之外的标识符可通过使用每个标识符的完全限定名(例如 std::vector<std::string> vec;)来访问成员,也可通过单个标识符的 using 声明 (using std::string) 或命名空间中所有标识符的 using 指令 (C++) (usi

  • C++ STL入门教程(1) vector向量容器使用方法

    一.简介 Vectors 包含着一系列连续存储的元素,其行为和数组类似. 访问Vector中的任意元素或从末尾添加元素都可以在O(1)内完成,而查找特定值的元素所处的位置或是在Vector中插入元素则是O(N). 二.完整程序代码 /*请务必运行以下程序后对照阅读*/ #include <vector> #include <iostream> #include <algorithm> #include <stdexcept> using namespace

  • C++命名空间实例解析

    命名空间是C++非常重要的概念,本文就以实例形式对其进行深入分析,具体内容如下: 通常来说,在C++中,命名空间(namespace)的目的是为了防止名字冲突.每个命名空间是一个作用域,在所有命名空间之外,还存在一个全局命名空间(global namespace),全局命名空间以隐式的方式声明,它并没有名字.在命名空间机制中,原来的全局变量,就是位于全局命名空间中(可以用::member的形式表示). 一.定义命名空间 1.每个命名空间都是一个作用域 和其他作用域类似,在命名空间中的每个名字必须

  • c++内联函数(inline)使用详解

    介绍内联函数之前,有必要介绍一下预处理宏.内联函数的功能和预处理宏的功能相似.相信大家都用过预处理宏,我们会经常定义一些宏,如 复制代码 代码如下: #define TABLE_COMP(x) ((x)>0?(x):0) 就定义了一个宏. 为什么要使用宏呢?因为函数的调用必须要将程序执行的顺序转移到函数所存放在内存中的某个地址,将函数的程序内容执行完后,再返回到转去执行该函数前的地方.这种转移操作要求在转去执行前要保存现场并记忆执行的地址,转回后要恢复现场,并按原来保存地址继续执行.因此,函数调

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

    一.C++关键字 C++总共有63个关键字,在入门阶段我们只是大致了解一下就可,在后续博客中会逐渐讲解 二.命名空间 相信学过C++的同学,一定都写过下面这个简单的程序 #include<iostream> using namespace std; int main() { cout<<"hello world"<<endl; return 0; } 我们先来看第二行代码,using namespace std , 这行代码是什么意思呢 ? 这里我们

  • C++示例分析内联函数与引用变量及函数重载的使用

    目录 1.内联函数 1.1为什么使用内联函数 1.2语法 2.引用变量 2.1为什么要使用引用变量 2.2语法 2.3对于C语言的改进 3. 函数重载 3.1默认参数 3.2函数重载 1.内联函数 1.1为什么使用内联函数 减少上下文切换,加快程序运行速度. 是对C语言中的宏函数的改进. 1.2语法 #include<iostream> using namespace std; inline double square(double x){ return x*x; } int main(){

  • 浅谈内联函数与宏定义的区别详解

    用内联取代宏:1.内联函数在运行时可调试,而宏定义不可以;2.编译器会对内联函数的参数类型做安全检查或自动类型转换(同普通函数),而宏定义则不会: 3.内联函数可以访问类的成员变量,宏定义则不能: 4.在类中声明同时定义的成员函数,自动转化为内联函数.文章(一)内联函数与宏定义 在C中,常用预处理语句#define来代替一个函数定义.例如: #define MAX(a,b) ((a)>(b)?(a):(b)) 该语句使得程序中每个出现MAX(a,b)函数调用的地方都被宏定义中后面的表达式((a)

  • 深入探讨:宏、内联函数与普通函数的区别

    内联函数的执行过程与带参数宏定义很相似,但参数的处理不同.带参数的宏定义并不对参数进行运算,而是直接替换:内联函数首先是函数,这就意味着函数的很多性质都适用于内联函数,即内联函数先把参数表达式进行运算求值,然后把表达式的值传递给形式参数.    内联函数与带参数宏定义的另一个区别是,内联函数的参数类型和返回值类型在声明中都有明确的指定:而带参数宏定义的参数没有类型的概念,只有在宏展开以后,才由编译器检查语法,这就存在很多的安全隐患.    使用内联函数时,应注意以下问题:    1)内联函数的定

  • C++ 引用与内联函数详情

    目录 引用初阶 什么是引用 为何要有引用 引用指向同一块空间 引用的特性 定义时必须初识化 一个变量可以多次引用 引用一旦引用了一个实例,不能在再引用其他的实例 引用进阶 常引用 权限 临时变量具有常属性 引用的场景 做参数 返回值 引用做返回值 引用不会开辟空间 引用和指针比较 内联函数 为何存在 内联函数 展开短小的函数 内联函数的特性 较大的函数编译器不会发生内联 声明定义一起 引用初阶 引用是C++的特性的之一,不过C++没有没有给引用特意出一个关键字,使用了操作符的重载.引用在C++中

  • Kotlin 内联函数详解及实例

    Kotlin 内联函数详解及实例 概述 在说内联函数之前,先说说函数的调用过程. 调用某个函数实际上将程序执行顺序转移到该函数所存放在内存中某个地址,将函数的程序内容执行完后,再返回到转去执行该函数前的地方.这种转移操作要求在转去前要保护现场并记忆执行的地址,转回后先要恢复现场,并按原来保存地址继续执行.也就是通常说的压栈和出栈.因此,函数调用要有一定的时间和空间方面的开销.那么对于那些函数体代码不是很大,又频繁调用的函数来说,这个时间和空间的消耗会很大. 那怎么解决这个性能消耗问题呢,这个时候

  • C语言中的内联函数(inline)与宏定义(#define)详细解析

    先简明扼要,说下关键:1.内联函数在可读性方面与函数是相同的,而在编译时是将函数直接嵌入调用程序的主体,省去了调用/返回指令,这样在运行时速度更快. 2.内联函数可以调试,而宏定义是不可以调试的.内联函数与宏本质上是两个不同的概念如果程序编写者对于既要求快速,又要求可读的情况下,则应该将函数冠以inline.下面详细介绍一下探讨一下内联函数与宏定义. 一.内联函数是什么?内联函数是代码被插入到调用者代码处的函数.如同 #define 宏(但并不等同,原因见下文),内联函数通过避免被调用的开销来提

  • c++中的内联函数inline用法实例

    问题描述:类中成员函数缺省默认是内联的,如果在类定义时就在类内给出函数定义,那当然最好.如果在类中未给出成员函数定义,而又想内联该函数的话,那在类外要加上 inline,否则就认为不是内联的.内联函数的inline要加在函数前面,不可以加在声明前面. class A { public:void Foo(int x, int y) { } // 自动地成为内联函数 } //正确写法: // 头文件 class A { public: void Foo(int x, int y); } // 定义文

随机推荐