C++是C语言的继承,兼容绝大多数C的语法,在其基础上增加了一些语法,一般是为了解决C语言做不到的事情、修改不够好的地方
C语言是结构化和模块化的语言,适合处理较小规模的程序。对于复杂的问题,规模较大的程序,需要高度的抽象和建模时,C语言则不合适。为了解决软件危机, 20世纪80年代, 计算机界提出了OOP(object oriented programming:面向对象思想),支持面向对象的程序设计语言应运而生。
1982年,Bjarne Stroustrup 博士在C语言的基础上引入并扩充了面向对象的概念,发明了一种新的程序言。为了表达该语言与C语言的渊源关系,命名C++。因此:C++是基于C语言而产生的,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行面向对象的程序设计。
最重要的两个版本 | 内容 |
---|---|
C++98 | C++标准第一个版本,绝大多数编译器都支持,得到了国际标准化组织(ISO)和美国标准化协会认可,以模板方式重写C++标准库,引入了STL(标准模板库) |
C++11 | 增加了许多特性,使得C++更像一种新语言,比如:正则表达式、基于范围for循环、auto关键字、新容器、列表初始化、标准线程库等 |
- 操作系统以及大型系统软件开发
- 服务器端开发
- 人工智能
- 网络工具
- 游戏开发
- 嵌入式领域
- 数字图像处理
- 分布式应用
- 移动设备
asm | do | if | return | try | continue |
auto | double | inline | short | typedef | for |
bool | dynamic_cast | int | signed | typeid | public |
break | else | long | sizeof | typename | throw |
case | enum | mutable | static | union | wchar_t |
catch | explicit | namespace | static_cast | unsigned | default |
char | export | new | struct | using | friend |
class | extern | operator | switch | virtual | register |
const | false | private | template | void | true |
const_cast | float | protected | this | volatile | while |
delete | goto | reinterpret_cast |
在不同的作用域,我们可以定义同名的变量;在同一作用域,不能定义同名的变量。
所以在一个大项目中,多个人分工写代码,最后合在一起时,你写的代码和我写的代码中的命名可能会冲突,我们写的代码中的命名又可能会和标准库里的冲突。
假如张三写了个函数取名为 Add,我也写了个函数,取名也为 Add,代码合在一起时,可能会引起命名冲突等等。
如果没有包含
stdlib.h
头文件,代码可以正常运行;但包含之后,因为
stdlib.h
库中有一个叫rand
函数,全局变量rand
与其冲突了。
#include
#include /*rand*/
int rand = 10; //全局变量rand
int main()
{
printf("%d\n", rand); //error: "rand"重定义,以前的定义是"函数"
return 0;
}
在C/C++中,变量、函数和类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace 关键字的出现就是针对这种问题的。
命名空间相当于一个域,把各自写的东西隔起来。
定义命名空间,需要使用到 namespace 关键字,后面跟命名空间的名字,然后接一对 { } 即可,{ } 中即为命名空间的成员。
注意:一个命名空间代表定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。
//命名空间的普通定义
namespace L1 //L1为命名空间的名称
{
//命名空间中可以定义变量/函数/类型
int a = 10;
int Add(int x, int y)
{
return x + y;
}
struct Node
{
struct Node* next;
int val;
};
}
//命名空间的嵌套定义
namespace L2
{
int a = 10;
namespace L3 //嵌套定义一个名为L3的命名空间
{
int b = 20;
int sub(int x, int y)
{
return x - y;
}
}
}
//定义一个命名空间N1
namespace N1
{
int a = 10;
int b = 10;
int Add(int x, int y)
{
return x + y;
}
}
命名空间的使用有三种方式:
// 优点:用起来方便
// 缺点:把自己的定义暴露出去了,导致命名污染
//using namespace std; //std是包含C++标准库的命名空间
using namespace N1;
int main()
{
Add(1, 2);
return 0;
}
// 优点:不存在命名污染
// 缺点:用起来麻烦,每个都需要去指定命名空间
int main()
{
printf("%d\n", N1::a);
N1::Add(1, 2);
return 0;
}
// 优点:不会造成大面积的命名污染,又可以把常用的给展开
// 这是一个折中的解决方案
using N1::Add; //命名空间N1中Add函数和变量a用的非常多,将其展开到全局
using N1::a;
int main()
{
printf("%d\n", a);
Add(1, 2);
return 0;
}
C++把标准库里面的东西都放到命名空间 std 中,所以在实际开发中,为了避免你自己写的变量/函数/类型等等与标准库中的冲突,建议不要把命名空间 std 直接展开到全局,而是把常用的展开就行,一般规范的写法为:
#include
//using namespace std; //不要将std直接展开到全局
//把常用的展开就行
using std::cout;
using std::endl;
int main()
{
cout << "hello world" << endl;
return 0;
}
如果是日常练习,就不需要像上面这么规范,直接展开用
C语言有自己的输入输出函数 scanf 和 printf,那么C++也有自己独特的输入输出方式,cin 标准输入流和 cout 标准输出流,必须包含 头文件和 std 标准库命名空间。
注意:早期标准库将所有功能在全局域中实现,声明在 .h 后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在 std 命名空间下,为了和 C 头文件区分,也为了正确使用命名空间,规定C++头文件不带.h;旧编译器(vc 6.0)中还支持
格式,后续编译器已不支持,因此推荐使用 + std 的方式。 使用C++输入输出更方便,它可以自动识别变量的类型,不需增加数据格式控制,比如:整形–%d,字符–%c
#include
using namespace std; //C++标准库
int main()
{
int a;
cin >> a; // >> 输入运算符/流提取运算符
cout << a; // << 输出运算符/流插入运算符
cout << endl; //换行,等价于 cout << '\n';
return 0;
}
**缺省参数是声明或定义函数时为函数的参数指定一个默认值。**在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。
#include
using namespace std;
void Func(int a = 0)
{
cout << a << endl;
}
int main()
{
Func(); //没有传参时,使用参数的默认值
Func(10); //传参时,使用指定的参数
return 0;
}
以前入栈时必须得判断一下容量是否为0,若为0,给capacity赋一个初始值,但赋的这个初始值没办法灵活的去控制,有了缺省参数,我们就可以根据需要,来控制初始容量。
#include
using namespace std;
//定义一个顺序栈
struct Stack
{
int* a;
int top;
int capacity;
};
//初始化栈,给栈容量设置一个默认值4
void StackInit(struct Stack* ps, int DefaultCapacity = 4)
{
ps->a = (int*)malloc(sizeof(int) * DefaultCapacity);
ps->top = -1;
ps->capacity = 0;
}
//入栈
void StackPush(struct Stack* ps)
{
if (ps->top == ps->capacity + 1)
{
ps->capacity *= 2;
//......
}
//......
}
int main()
{
//假如我明确知道这里至少要存100个数据到st1中去
struct Stack st1;
StackInit(&st1, 100);
//假如我不知道要存多少个数据到st2中去
struct Stack st2;
StackInit(&st2);
return 0;
}
#include
using namespace std;
void Func(int a = 10, int b = 20, int c = 30)
{
cout << a + b + c << endl;
}
int main()
{
//调用全缺省参数的函数方式很灵活
Func();
Func(1);
Func(1, 2);
Func(1, 2, 3);
return 0;
}
#include
using namespace std;
void Func(int a, int b = 10, int c = 20)
{
cout << a + b + c << endl;
}
int main()
{
//调用半缺省参数的函数
Func(1); //1传给a
Func(1, 2); //1传给a,2传给b
Func(1, 2, 3); //1传给a,2传给b,3传给c
return 0;
}
- 半缺省参数必须从右往左依次来给出,不能间隔着给
- 缺省参数不能在函数声明和定义中同时出现(建议声明的时候给缺省参数,定义的时候就不要给了)
//注意:如果声明与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值。 //test.h void Func(int a = 10); //test.c void Func(int a = 20) { //...... }
- 缺省值必须是常量或者全局变量
//不能这样给缺省值 void Func(int a, int b = x);//error
- C语言不支持缺省参数(编译器不支持)
自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重载了。
以前有一个笑话,国有两个体育项目大家根本不用看,也不用担心。一个是乒乓球,一个是男足。前者是 ” 谁也赢不了!”,后者是 “ 谁也赢不了!”
可以看出,同一句话,可能有多重意思。
函数重载是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 / 类型 / 顺序)必须不同,常用来处理实现功能类似但数据类型不同的问题。
int Add(int a, int b)
{
return a + b;
}
double Add(double a, double b)
{
return a + b;
}
long Add(long a, long b)
{
return a + b;
}
int main()
{
Add(10, 20);
Add(10.0, 20.0);
Add(10L, 20L); //L表示该常数是以长整型方式存储,long
return 0;
}
- 编译器能不能实现函数名相同,参数相同,返回值不同来构成函数重载?
结论:不能实现,无法重载仅按返回值类型区分的函数。
int func(); -> _Z3ifunc double func(); -> _Z3dfunc
如果把返回值带进名字修饰规则,那么编译器层面是可以区分的。
但是语法调用层面,无法根据参数列表确定调用哪个重载函数,甚至带有严重的歧义!
比如这条语句
func();
,调用时,这里是要调用哪一个呢?
C++编译器能识别C++函数名修饰规则,也能识别C的函数名修饰规则。
我们在C++项目开发中,可能会用到一些第三方的库,有些是纯C实现的库(动态库/静态库),它里面的函数名修饰规则是C的,而C++是兼容C的,C++编译器直接按照C的修饰规则去调用。
但如果是纯C的项目,用到C++实现的库(动态库/静态库),比如:tcmalloc是google用C++实现的一个库,他提供
tcmallc()
和tcfree()
两个接口来使用(更高效,替代malloc和free函数),这个库可以给C++项目用,但不能给C项目用,会存在链接失败,因为C编译器无法识别C++的修饰规则。如果想要给C用,需要将C++库中的部分函数按照C的风格来编译,在函数前加
extern "C"
,意思是告诉编译器,将该函数按照C的修饰规则来编译。extern "C" void Add(int a, int b);
C++项目可以调用C++的库,也可以调用C的库。
C的项目可以调用C的库,如果要调用C++的库,需要在该函数前加上
extern "C"
。
下面两个函数能形成函数重载吗?——> 不能
void Func(int a = 10) { cout << "Func(int)" << endl; } void Func(int a) { cout << "Func(int)" << endl; }
C语言中为什么不能支持函数重载?
——> C和C++编译器的函数名修饰规则不一样,C编译器直接拿函数名在目标文件里面充当函数名和函数地址的映射,所以在链接的时候,是拿函数名去目标文件里面去找,函数名相同,虽然参数不同,但不知道找的是谁。
C++中函数重载底层是怎么处理的?
——> C++编译器不直接拿函数名去找,而是拿修饰后的函数名去找,参数不同修饰后的函数名就不同,就能够找到。
C++中能否将一个函数按照C的风格来编译?
——> 能,在函数前加上
extern "C"
未完待续,下一篇,我们将学习C++中最重要的一个知识之一,引用 …… 一起期待吧!