文章目录
- 一、什么是C++
- 二、C++关键字(C++98)
- 三、命名空间
-
- [3.1 命名空间的定义](#3.1 命名空间的定义)
- [3.2 命名空间的使用](#3.2 命名空间的使用)
- 四、C++的输入输出
- 五、缺省参数
-
- [5.1 缺省参数的概念](#5.1 缺省参数的概念)
- [5.2 缺省参数分类](#5.2 缺省参数分类)
-
- [5.2.1 全缺省参数](#5.2.1 全缺省参数)
- [5.2.2 半缺省参数](#5.2.2 半缺省参数)
- [5.2.3 注意事项](#5.2.3 注意事项)
- 六、函数重载
-
- [6.1 函数重载的概念](#6.1 函数重载的概念)
- [6.2 函数重载的条件](#6.2 函数重载的条件)
- [6.3 C++支持函数重载的原因](#6.3 C++支持函数重载的原因)
- [6.4 补充:extern "C"](#6.4 补充:extern “C”)
一、什么是C++
C语言是结构化和模块化的语言,适合处理较小规模的程序。对于复杂的问题,规模较大的程序,需要高度的抽象和建模时,C语言则不合适。为了解决软件危机,20世纪80年代,计算机界提出了OOP(object oriented programming:面向对象) 思想,支持面向对象的程序设计语言应运而生
1982年,Bjarne Stroustrup博士在C语言的基础上引入并扩充了面向对象的概念,发明了一种新的程序语言。为了表达该语言与C语言的渊源关系,命名为C++。因此,C++是基于C语言而产生的,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行面向对象的程序设计
C++参考文献:
参考资料 - C++ 参考资料 (cplusplus.com)
C++ 参考手册 - cppreference.com
cppreference.com
提示:第一个链接不是C++官方文档,标准也只是更新到C++11,但是以头文件形式呈现,内容比较易看好懂,后两个链接分别是C++官方文档的中文版和英文版,信息很全,更新到了最新的C++标准,没有第一个那么易看,各有优点,相互结合使用即可
书籍推荐
《C++ Primer》:主要讲解语法,是一本经典的语法书籍,前中后期都可以看
《STL源码剖析》:主要从底层实现的角度结合STL源码,庖丁解牛式剖析STL的实现。很好的帮助我们学习别人用语法是如何实现出简洁高效的数据结构和算法代码,如何使用泛型封装等。让我们不再坐井观天、闭门造车。建议中后期看
《Effective C++》:这本书主要讲解了55个如何正确高效使用C++的条款,建议中后期看一遍,工作一两年后再看一遍,会有不一样的收获
二、C++关键字(C++98)
C++总计63个关键字,C语言32个关键字

注:这里稍微知道一下有这些关键字即可,后面学到具体应用时再进行细讲
三、命名空间
在C/C++中,变量、函数和类都是大量存在的,这些变量、函数和类的名称都将作用于全局作用域中,可能会导致很多命名冲突
使用命名空间的目的就是对标识符和名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的
C语言项目类似下面程序这样的命名冲突是普遍存在的问题,C++引入namespace就是为了更好的解决这样的问题
c
#include<stdio.h>
#include<stdlib.h>
int rand = 100;
int main()
{
printf("%d\n",rand);
return 0;
}

由于预处理阶段会将头文件进行展开,而在我们的stdlib.h头文件中存在着名为rand的随机数函数,而C语言是不允许在相同作用域下定义多个同名符号的,因此会报出重定义的错误
c
#include<stdio.h>
#include<stdlib.h>
int rand = 0; //#include<stdlib.h> 引入了标准库中的 rand() 函数,这里又定义为全局变量,顾重定义
int main()
{
int rand = 0; //这里rand是局部变量,作用域不同,局部优先,因此不会报错
rand(); //由于rand是局部优先,这里的rand是个局部变量,顾编译器发现 rand 是 int 类型,不能作为函数调用,因此报错
printf("%d", rand);
return 0;
}
在上面的代码中,我们无论将rand定义成全局变量还是局部变量,都无法实现我们想要的效果,只能变换名字
但是在C++中,新增了命名空间来对标识符的名称进行本地化,以避免命名冲突或名字污染,上面的问题就被很好的解决了
3.1 命名空间的定义
定义命名空间,需要使用到 namespace 关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名空间的成员。命名空间内的成员可以是变量,也可以是函数、类型,甚至可以是另一个命名空间
① namespace本质是定义出一个域,这个域跟全局域各自独立,不同的域可以定义同名变量,同一个域不可以定义同名变量
② C++域中有函数局部域,全局域,命名空间域,类域;
域影响的是编译时语法查找一个变量/函数/类型出处(声明和定义)的逻辑,所以有了域隔离,名字冲突就解决了。
局部域和全局域除了会影响编译查找逻辑,还会影响变量的生命周期,命名空间域和类域不会影响变量生命周期
③ namespace只能定义在全局,不能定义在局部(例如不能在main函数里面定义)。当然它还可以嵌套定义使用,甚至多层嵌套
④ 项目工程中多文件中定义的同名namespace会认为是一个namespace,不会冲突
⑤ C++标准库都放在一个叫std(standard)的命名空间
1. 命名空间的普通定义
cpp
//命名空间的普通定义
namespace N1 //N1为命名空间的名称
{
//在命名空间中,既可以定义变量,也可以定义函数
int a;
int Add(int x, int y)
{
return x + y;
}
}
2. 命名空间可以嵌套定义
cpp
//命名空间的嵌套定义
namespace N1 //定义一个名为N1的命名空间
{
int a;
int b;
namespace N2 //嵌套定义另一个名为N2的命名空间
{
int c;
int d;
}
}
3. 同一个工程中允许存在多个相同名称的命名空间,编译器最后会将其成员合成在同一个命名空间中
eg:一个工程中的test.h和test.cpp中两个N1会被合并成一个
cpp
//test1.cpp
namespace Dream
{
int a = 5;
int b = 10;
}
//test2.cpp
namespace Dream
{
int Add(int x, int y)
{
return x + y;
}
}
//上面两个同名命名空间编译器最终会进行合并,结果如下:
namespace Dream
{
int a = 5;
int b = 10;
int Add(int x, int y)
{
return x + y;
}
}
所以我们不能在相同名称的命名空间中定义两个相同名称的成员
注意:一个命名空间就定义了一个新的作用域,命名空间中所有内容都局限于该命名空间中0
3.2 命名空间的使用
那么,定义了命名空间后,我们要如何使用它呢?如果我们直接对命名空间的成员进行访问,编译器会报错:
cpp
#include<stdio.h>
namespace Dream
{
int b = 10;
}
int main()
{
printf("%d", b); //报错,b只在Dream作用域内有效
return 0;
}

编译查找一个变量的声明/定义时,默认只会在局部或者全局查找,不会到命名空间里面去找
我们一般有三种使用方法:
1. 加命名空间名称及作用域限定符
符号"::"在C++中叫做作用域限定符,我们通过"命名空间名称::命名空间成员"便可以访问到命名空间中相应的成员
cpp
#include <stdio.h>
namespace N
{
int a;
double b;
}
int main()
{
N::a = 10;//将命名空间中的成员a赋值为10
printf("%d\n", N::a);//打印命名空间中的成员a
return 0;
}
cpp
namespace Dream
{
int b = 10;
namespace other
{
int b = 5;
}
}
int main()
{
printf("%d", Dream::b); //表示Dream命名空间内的b,即输出10
printf("%d", Dream::other::b); //表示Dream命名空间内的other命名空间内的b,即输出5
return 0;
}
两个变量b虽然名称相同,但被划分到了两个命名空间中,作用域不同,因此不会出现重定义的问题。并且,通过前面加上对应的命名空间我们可以实现对这两个变量b的访问
2. 使用using将命名空间中的成员引入
如果命名空间中的某个变量需要在程序中频繁的进行使用,每次都要在前面加上命名空间未免显得过于繁琐,我们还可以通过"using 命名空间名称::命名空间成员"的方式将命名空间中指定的成员引入。这样一来,在该语句之后的代码中就可以直接使用引入的成员变量了
cpp
//使用using将命名空间中的成员引入
#include <stdio.h>
namespace N
{
int a;
double b;
}
using N::a;//将命名空间中的成员a引入
int main()
{
a = 10;//将命名空间中的成员a赋值为10
printf("%d\n", a);//打印命名空间中的成员a
return 0;
}
3. 使用using namespace 命名空间名称引入
最后一种方式就是通过"using namespace 命名空间名称"将命名空间中的全部成员引入。这样一来,在该语句之后的代码中就可以直接使用该命名空间内的全部成员了
cpp
//使用using namespace 命名空间名称引入
#include <stdio.h>
namespace N
{
int a;
double b;
}
using namespace N;//将命名空间N的所有成员引入
int main()
{
a = 10;//将命名空间中的成员a赋值为10
printf("%d\n", a);//打印命名空间中的成员a
return 0;
}
cpp
#include<iostream>
using namespace std;
int main()
{
return 0;
}
第一条语句的作用是包含输入输出流,下面我们会进行说明,这里我们可以暂且将理解为C语言的#include<stdio.h>
第二条语句是用来展开命名空间std的。std的英文全拼是Standard,即标准的意思。C++标准程序库中的所有标识符都被定义在这个命名空间中。顾这里将整个命名空间引入是为了后续更方便的使用C++标准程序库的标识符,如函数、类型等等
① 制定命名空间访问,项目中推荐这种方式
② using将命名空间中的某个成员展开,项目中经常访问的不存在冲突的成员推荐这种方式
③ 展开命名空间的全部成员,项目不推荐,冲突风险很大,日常小练习程序为了方便推荐使用
四、C++的输入输出
在学习C语言时,我们写的第一个代码就是hello world,那么在我们第一次接触C++时,是不是也应该使用C++对美好的世界打个招呼呢?我们来试试C++是怎么实现输入输出的吧!
cpp
#include<iostream>
using namespace std; //展开std命名空间
int main()
{
cout << "hello world!!!" << endl; //打印输出
return 0;
}

说明:
- 使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream>头文件以及按命名空间使用方法使用std。
- < iostream>是 Input Output Stream 的缩写,是标准的输入、输出流库,定义了标准的输入、输出对象
- cout和cin是全局的流对象,它们分别是ostream和istream类型的对象。而endl是特殊的C++符号,表示换行输出,他们都包含在包含在< iostream>头文件中
- <<是流插入运算符,>>是流提取运算符,这实际上是一种运算符重载
- 使用C++输入输出更方便,不需要像printf/scanf输入输出时那样需要手动控制格式,即%d、%f等等。C++的输入输出可以自动识别变量类型
注意:早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在std命名空间下,为了和C语言头文件区分,也为了正确使用命名空间,规定C++头文件不带.h,这就是为什么< iostream>也是头文件的原因。旧编译器(vc 6.0)中还支持< iostream.h>格式,后续编译器已不支持,因此推荐使用< iostream>+std的方式,简单来说:去掉.h就是为了引入命名空间机制,避免名字冲突,让代码更清晰!
cpp
#include <iostream>
using namespace std;
int main()
{
int i;
double d;
char arr[20];
cin >> i;//读取一个整型
cin >> d;//读取一个浮点型
cin >> arr;//读取一个字符串
cout << i << endl;//打印整型i
cout << d << endl;//打印浮点型d
cout << arr << endl;//打印字符串arr
return 0;
}
cpp
#include<iostream>
using namespace std;
//对于cout,流插入的东西不会直接到终端,而是先到一个缓冲区,有了刷新标志才会出现在终端
int main()
{
//在io需求比较高的地方,如部分大量输入的竞赛题中,加上下面3行代码
//可以提高C++IO效率
ios_base::sync_with_stdio(false);//取消c和c++的兼容
//作用:解除cin与cout的绑定
cin.tie(nullptr);
cout.tie(nullptr);
return 0;
}
- C++的cin/cout与C的scanf/printf是同步的,这样可以混合使用,这样子每次输入输出都需要同步,导致性能下降,通过ios_base::sync_with_stdio(false);将其设为false后,取消同步,大幅提高C++流的速度,但不能混合使用C和C++的IO函数(如不能同时用cin和scanf)
- 默认情况:cin和cout是绑定的,意味着在每次使用cin输入前,cout的缓冲区会被自动刷新,通过cin.tie(nullptr);
cout.tie(nullptr);解除cin与cout的绑定,这样子输入输出操作可以独立进行,避免了不必要的缓冲区刷新,提高效率
五、缺省参数
5.1 缺省参数的概念
缺省参数是指在声明或定义函数时,为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参
cpp
#include<iostream>
using namespace std;
void Print(int a = 0)
{
cout << a << endl;
}
int main()
{
Print();//没有指定实参,使用参数的默认值(打印0)
Print(10);//指定了实参,使用指定的实参(打印10)
return 0;
}
5.2 缺省参数分类
5.2.1 全缺省参数
全缺省参数,即函数的全部形参都设置为缺省参数
cpp
//全缺省参数
void Print(int a = 10, int b = 20, int c = 30)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
int main()
{
Func();
Func(10);
Func(20,30,40);
return 0;
}
5.2.2 半缺省参数
半缺省参数,即函数的参数不全为缺省参数
cpp
//半缺省参数
void Print(int a, int b = 10, int c=20)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
int main()
{
Func(); //错误调用,第一个参数没有缺省值,需要传参
Func(10); //第一个参数传入10,其余参数用缺省值
Func(20,30,40); //全部用指定的实参
return 0;
}
5.2.3 注意事项
- 半缺省参数必须从右往左依次给出,不能间隔给
cpp
//错误写法
void Print(int a = 10, int b, int c) {};
void Print(int a = 10, int b = 20, int c) {};
void Print(int a = 10, int b, int c = 30) {};
//正确写法
void Print(int a, int b, int c = 30) {};
void Print(int a, int b = 20, int c = 30) {};
- 缺省参数不能在函数声明和定义中同时出现,其目的是为了防止我们在声明和定义中给出了不同的缺省值,从而导致歧义
cpp
//错误的写法
//test.h
void Func(int a = 10);
// a
test.cpp
void Func(int a = 20)
{}
- 缺省值必须是全量或者全局变量
cpp
//正确示例
int x = 30;//全局变量
void Print(int a, int b = 20, int c = x)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
- C语言不支持带缺省参数的函数(编译器不支持)
补充:函数声明就是只有函数的返回值,名称和参数,简单理解就是没有{}的函数;函数定义就是函数带着实现{}
六、函数重载
6.1 函数重载的概念
函数重载:它是一种函数的特殊情况。C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似、数据类型不同的问题
假设我们要实现一个Add函数实现两个整型以及两个浮点型的相加,在C语言中,我们应该这么写:
cpp
//C语言写法
int iAdd(int x, int y)
{
return x + y;
}
double dAdd(double x, double y)
{
return x + y;
}
int main()
{
iAdd(1, 2);
dAdd(1.0, 2.0);
return 0;
}
由于实参的类型不同,我们需要写两个Add函数分别实现整形和浮点型的相加,并且为了避免重定义,两个函数名必须不同
而C++引入了函数重载,我们就能很舒服的使用相同名称来定义这两个参数不同的函数:
cpp
//C++写法,两个Add函数构成函数重载
int Add(int x, int y)
{
return x + y;
}
double Add(double x, double y)
{
return x + y;
}
int main()
{
Add(1, 2);
Add(1.0, 2.0);
return 0;
}
6.2 函数重载的条件
C++构成函数重载的条件是形参列表必须不同。形参列表不同分为以下三种:
- 1. 参数个数不同
cpp
//参数个数不同
#include<iostream>
using namespace std;
void Fun(int x)
{
cout << "void Fun(int x)" << endl;
}
void Fun()
{
cout << "void Fun()" << endl;
}
int main()
{
Fun(1);//调用第一个
Fun();//调用第二个
return 0;
}

- 2. 参数类型不同
cpp
//参数类型不同
#include<iostream>
using namespace std;
int Add(int x,int y)
{
cout << "void Fun(int x,int y)" << endl;
return x + y;
}
double Add(double x,double y)
{
cout << "void Fun(double x,double y)" << endl;
return x + y;
}
int main()
{
Add(1,2);//调用第一个
Add(1.5,3.8);//调用第二个
return 0;
}

- 3. 参数顺序不同
cpp
//参数顺序不同
#include<iostream>
using namespace std;
int Add(int x, double y)
{
cout << "void Fun(int x,double y)" << endl;
return x + y;
}
double Add(double x, int y)
{
cout << "void Fun(double x,int y)" << endl;
return x + y;
}
int main()
{
Add(1, 2.4);//调用第一个
Add(1.5, 5);//调用第二个
return 0;
}

注意:是参数类型的顺序不同,而不是变量名顺序不同,即以下写法不构成函数重载:
cpp
//变量名顺序不同不构成函数重载,形参的名称只是标识,本质上还是同一个函数
void Fun(int x , double y){};
void Fun(int y , double x){};
- 4. 缺省函数的重载
此外,带缺省参数的函数也可以构成函数重载,编译并不会报错,但使用上可能会出现一些很尴尬的问题,举例如下:
cpp
//缺省函数的重载
#include<iostream>
using namespace std;
void Fun(int x,double y = 1.5)
{
cout << "void Fun(int x,double y = 1.5)" << endl;
}
void Fun(int x)
{
cout << "void Fun(int x)" << endl;
}
int main()
{
Fun(1,1.9);////这里会调用第一个函数没问题
Fun(1);//此时既可以调用第一个函数,也可以调用第二个函数,存在歧义,会报错
}
由于缺省函数的重载很容易引发歧义,顾我们一般不也会这么写
6.3 C++支持函数重载的原因
可能会有很多小伙伴会疑惑:为什么C++支持函数重载,而C语言不支持函数重载呢?
在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接
详情可以看 程序环境详解
我们知道,在编译阶段会将程序中的每个源文件的全局范围的变量符号分别进行汇总。在汇编阶段会给每个源文件汇总出来的符号分配一个地址(若符号只是一个声明,则给其分配一个无意义的地址),然后分别生成一个符号表。最后在链接期间会将每个源文件的符号表进行合并,若不同源文件的符号表中出现了相同的符号,则取合法的地址为合并后的地址(重定位),若都是定义,则都是有效地址,进行符号表合并后,符号表就会出现相同符号不同地址的情况,会引发符号表的歧义,此时会报重定义的错误。
那为什么C语言不行,放到C++就可以呢
在C语言中,汇编阶段进行符号汇总时,一个函数汇总后的符号就是其函数名,所以当汇总时发现多个相同的函数符号时,编译器便会报错。而C++在进行符号汇总时,对函数的名字修饰做了改动,函数汇总出的符号不再单单是函数的函数名,而是通过其参数的类型和个数以及顺序等信息汇总出 一个符号,这样一来,就算是函数名相同的函数,只要其参数的类型或参数的个数或参数的顺序不同,那么汇总出来的符号也就不同了
也就是C++引入了函数名修饰规则,函数在符号表中除了名称,还一并将参数类型代入修饰
注:不同的编译器下的函数名修饰规则可能有所不同
总结:
- C语言不能支持重载,是因为同名函数没办法区分。而C++是通过函数修饰规则来区分的,只要函数的形参列表不同,修饰出来的名字就不一样,也就支持了重载
- 为什么函数重载要求参数不同,与返回值没关系
- 如果两个函数仅仅是返回值不同是不构成重载的,因为调用时编译器没办法区分
6.4 补充:extern "C"
有时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加"extern C",意思是告诉编译器,将该函数按照C语言规则来编译
注意:在函数前加"extern C"后,该函数便不能支持重载了