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