前言
从C语言到C++的转变无疑是巨大的:从面向过程编程到面向对象编程……如果一门心思扑到”封装、继承、多态“上学习,恐怕学到后面就会被一些语法整的困惑不解。
本文的目的就是尽量填平C语言与C++之间隐形的坑:C++常用但C语言却没有的基础知识。
本文不仅适合于用做初学C++的入门文章,还可以帮助不清楚C与C++之间差异的读者理清思绪。
一、namespace是什么?
1.情景引入
在C语言编写的代码中,存在着大量的变量、函数。而这些变量、函数又都存储于全局作用域中,这就引出了一个问题:如果变量名与函数名相同的情况下,会导致很多冲突。
如代码:
代码语言:javascript
AI代码解释
#include<iostream> using namespace std; int test(int a) { return a; } int main() { int test = 1; cout << test(test); return 0; }(cout相当于C语言的printf,cin相当于C语言中的scanf,<<是流插入运算符,>>是流提取运算符,cout/cin不需要像printf/scanf输入输出时那样手动控制格式,C++的输入输出可以自动识别变量类型。)
这段代码放在编译器上是编译错误的,原因也很显然:test重定义了。编译器不知道使用的test指的是函数还是变量,这就造成了命名冲突。
C语言没办法解决类似这样的命名冲突问题,而C++通过namespace关键字,成功解决了名称冲突的问题。
2.命名空间定义
namespace用法:
命名空间需要用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{} 中即为命名空间的成员。
前面提到C语言将所有的变量、函数都放在全局作用域中,从而造成冲突。
C++允许程序员通过namespace关键字自定义命名空间(作用域),自定义的命名空间同全局作用域一样,也可以以命名空间中可以定义变量/函数/类型。
如:
代码语言:javascript
AI代码解释
namespace a1 { int hello = 6; int test(int a) { return a; } }那么该如何使用已经定义好的命名空间呢?
3.命名空间的使用
命名空间的使用有三种方式:
①使用某变量/函数前,加空间名称与作用限定符"::".
代码语言:javascript
AI代码解释
int main() { cout << a1::hello; return 0; }②使用某变量/函数前,使用using将命名空间中某个成员引入全局作用域;
代码语言:javascript
AI代码解释
using a1::hello; int main() { cout << hello; return 0; }③使用某变量/函数前,使用using namespace “命名空间名”将整个空间引入;
(谨慎使用,防止再次出现冲突问题)
代码语言:javascript
AI代码解释
using namespace a1; int main() { cout << hello; return 0; }例如如上述代码通过加入namespace命名空间就能编译通过:
明确告诉编译器,我使用的是a1空间中的函数test,未加作用域限定符的就是全局作用域中的变量test。
代码语言:javascript
AI代码解释
#include<iostream> using namespace std; namespace a1 { int hello = 6; int test(int a) { return a; } } int main() { int test = 1; cout << a1::test(test); return 0; }现在再回过头看“using namespace std”是什么意思就一目了然了。
常见教材上一般直接让初学者直接使用“using namespace std”,却又迟迟不说明原因,其实原因很简单:
<iostream>头文件中包含有cin、cout等标准输入输出的声明,但考虑到命名冲突的问题,于是C++将相关声明都放进了std命名空间中。如果不将std空间通过using展开,那么使用std空间里的成员就需要作用限定符,如std::cout。 实际上std中可以不止有<iostream>头文件中的声明,其他头文件如<vector><string>的声明也可以在其中。
这里还需要指出一个容易误解的区域:很多初学者误以为cin等“函数”的实现是在std中。其实不然:std的主要作用是避免名称冲突,防止标准库的名字(如cout)和你自己写的代码名字(比如你定义一个cout变量)打架。它们的定义是在C++标准库的预编译二进制文件中,std中的声明是为了方便后续的编译链接。
最后指出:
std命名空间的内容是随着包含的头文件动态增长的,且未包含的头文件中的声明不会进入std。
每包含一个标准库头文件(如<vector>),该文件就会向std命名空间内添加新的声明,这是 C++ 模块化设计的关键特性,也是一般教材没有细说的地方。
二、缺省参数
1.缺省参数概念
缺省参数指:
在声明或者定义函数时,为形参预备一个值,在调用该函数时,如果没有传入实参就采用该形参的缺省值,否则使用指定的实参。
这个预备值的名称叫做缺省值,有缺省值的参数叫缺省参数,有缺省参数的函数叫缺省函数。
如
代码语言:javascript
AI代码解释
#include<iostream> using namespace std; void fun(int a = 100) { cout << a; } int main() { fun();//没有传参时就用缺省值 fun(10);//传参了就用实际参数 return 0; }2.缺省参数细节
缺省参数可以分为全缺省参数和半却参数。
全缺省参数就是该函数的所有参数都有缺省值;半缺省参数,指该函数的某一个参数有缺省值,但不是所有函数都有缺省值。
还有几点小细节:
①半缺省参数:若某个形参是缺省参数,那它之后的参数就必须是缺省参数;
②缺省参数不能在函数声明和定义中同时出现,防止重定义缺省值;
③缺省值必须是常量或者全局变量
C语言并不支持缺省值。
三、函数重载
1.函数重载概念
函数重载:
C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,用来处理实现功能类似数据类型不同的问题。注意:没有按返回值区分!
如
代码语言:javascript
AI代码解释
int add(int a, int b) { return a + b; } double add(double a, double b) { return a + b; } int main() { cout << add(1, 2) << endl; cout << add(3.1, 4.2) << endl; return 0; }编译器是怎么做到区分这两个重载函数的呢?
2.重载原理:C++的名字修饰规则
首先需要明确在不同的环境下,同一个程序中函数被编译后产生的名字是不一样的。
比如Linux与windows有关C++的名字修饰规则就截然不同,我们以较为清晰明了的Linux规则结合上述重定义代码为例做说明:
上述代码在Linux G++编译后形成的可执行文件中两个add函数的函数名如下,
我们看到两个add函数的函数名发生了改变,add(int ,int )型函数名,从add——>_Z3addii;add(double,double)型,从add——>_Z3adddd。
在Linux环境下C++名字修饰规则是:
- 前缀:
_Z表示 C++ 名称 - 长度+函数名:
5print表示函数名print有 5 个字符 - 参数类型编码:
i→intd→doublef→floatc→charP→ 指针(如Pi表示int*)
总结:g++的函数修饰后变成【_Z+函数长度 +函数名+类型首字母】。注意:没有返回值!
而C语言编译后,其中的函数名就不会有这样的变化。
所有C++能实现函数重载,C语言不能的根本原因是:C++有着另一套函数名字修饰规则。
四、引用
“引”用在C++中经常使用,但在C语言中完全没有“引用”的身影。
1.引用的概念
引用概念
引用,并非定义一个新变量,而是为已有变量取一个别名。引用与被引用的变量指向同一块地址空间。类型& 引用变量名(对象名) = 引用实体;
如
代码语言:javascript
AI代码解释
int main() { int a = 1; int& A = a; //下面两次输出完全一样 cout<<a<<endl; cout<<A<<endl; }从概念上看,既然引用与被引用变量指向同一块空间,所以对它们进行操作时是同样的效果:
"A=7"等价于"a=7",都是被指向的那块空间数据被修改为“7”。
注意:引用的类型必须与被引用的变量类型一致!
2.引用的特性
1. 引用在定义时必须初始化;2. 一个变量可以有多个引用;3. 引用一旦引用一个实体,再不能引用其他实体;4.常变量只能被常引用所引用;
1. 引用在定义时必须初始化;
如下述代码,编译器都会报错或者达不到预期效果
引用未初始化:
编译器报错:“error C2530: “A”: 必须初始化引用”
代码语言:javascript
AI代码解释
int main() { int a = 1; int& A; return 0; }3. 引用一旦引用一个实体,再不能引用其他实体;
引用不可更改引用对象:
这里本意是想更改引用A的指向,使A从指向a转化到指向b。
但引用不能更改指向,所以下述代码实质上是将b变量的值赋值给了A(a)变量;
代码语言:javascript
AI代码解释
int main() { int a = 1, b = 2; int& A = a; A = b; return 0; }4.常变量只能被常引用所引用;
所谓常变量,即被const修饰的变量,是不可更改的。常引用同理。
换句话说,常变量只有读权限,没有写权限,所以引用常变量的引用也必须只有读权限,而没有写权限。
代码语言:javascript
AI代码解释
int main() { const int b = 2; const int& B = b;//正确 int& A = b;//错误 return 0; }那么普通变量能否被常引用所引用呢?
答案是肯定的,让我们分析一下:
普通变量具有可读可写的权限,而常引用仅有可读权限。如果将普通变量被常引用所引用,这是发生权限的下降,是可接受的。
所以,编译器允许引用变量对原变量的操作权限下降,而不允许权限上升。
引用变量权限<=被引用变量权限
3.引用的使用场景
1)引用传参
还记得刚接触指针时写的swap函数吗?那时形参用的是指针实现,其原理是:函数调用时将原变量的地址传入函数,通过指针直接对该地址上的数据进行操作。
同理,引用也是指向被引用变量的同一块地址,所以引用也可以实现交换函数swap:
代码语言:javascript
AI代码解释
void swap(int& left, int& right) { int temp = left; left = right; right = temp; }2)引用做返回值
引用做返回值?如下所示
代码语言:javascript
AI代码解释
int& add(int a, int b) { int c = a + b; return c; }原理也很简单,在函数内部有个int c的局部变量,函数用引用的形式返回c变量存储的数据。
但这里有一个十分容易引起bug的细节:
首先明确函数中的c变量是一个局部变量,当函数调用完毕后,函数所用到的所用空间都会被系统释放,以便调用其他函数。
问题在于接受函数引用返回值的变量!如果是普通变量,那还好;但如果是引用变量,则可能出现bug。
比如下代码:打印出来的是3?还是7?咋一看打印的肯定是3才对。
https://www.dongchedi.com/article/7598265380330848830
https://www.dongchedi.com/article/7598273072999186968
https://www.dongchedi.com/article/7598274901841379865
https://www.dongchedi.com/article/7598276424889336345
https://www.dongchedi.com/article/7598275637006664254
https://www.dongchedi.com/article/7598279236490723865
https://www.dongchedi.com/article/7598274890558800409
https://www.dongchedi.com/article/7598282614747251224
https://www.dongchedi.com/article/7598276969335669310
https://www.dongchedi.com/article/7598278977895121470
https://www.dongchedi.com/article/7598282709198717464
https://www.dongchedi.com/article/7598281407030936089
https://www.dongchedi.com/article/7598277471599346201
https://www.dongchedi.com/article/7598283447504896536
https://www.dongchedi.com/article/7598287089956192793
https://www.dongchedi.com/article/7598287774483202584
https://www.dongchedi.com/article/7598288632398856728
https://www.dongchedi.com/article/7598287158570435097
https://www.dongchedi.com/article/7598292330680418878
https://www.dongchedi.com/article/7598288380383855129
https://www.dongchedi.com/article/7598290710689219134
https://www.dongchedi.com/article/7598288042881073689
https://www.dongchedi.com/article/7598291946742317593
https://www.dongchedi.com/article/7598293637713478206
https://www.dongchedi.com/article/7598290400063373848
https://www.dongchedi.com/article/7598292454667944472
https://www.dongchedi.com/article/7598291773202776638