基本介绍
C++的起源
1979年,当时的 Bjarne Stroustrup 正在⻉尔实验室从事计算机科学和软件⼯程的研究⼯作。⾯对项⽬中复杂的软件开 发任务,特别是模拟和操作系统的开发⼯作,他感受到了现有语⾔(如C语⾔)在表达能⼒、可维护性 和可扩展性⽅⾯的不⾜。
于是,牛逼的祖师爷决定自己创造语言!
1983年,Bjarne Stroustrup 在C语⾔的基础上 添加了 ⾯向对象编程 的特性,设计出了C++语⾔的雏形, 此时的C++已经有了类、封装、继承等核⼼概念,为后来的⾯向对象编程奠定了基础。这⼀年该语⾔被正式命名为C++。
重点:C++祖师爷——Bjarne Stroustrup
C++——在C语⾔的基础上产生的
可以进行C语言的过程化程序设计
可以进行以抽象数据类型为特点的基于对象的程序设计
可以进行面向对象的程序设计
C++的发展
时间:1998年
阶段:C++98
内容:第一个版本发布,绝大多数编译器都支持,得到了国际标准化组织(ISO)和美国标准化协会认可,以模板方式重写
C++
标准库,引入了STL
(标准模板库)。
时间:2011年
阶段:C++11
内容:增加了许多特性,使得
C++
更像一种新语言,比如:正则表达式、基于范围for循环、auto关键字、新容器、列表初始化、标准线程库等
时间:2020年
阶段:C++11
内容: 引入了许多新的特性,比如:模块(Modules)、协程(Coroutines)、范围(Ranges)、概念(Constraints)等重大特性,还有对已有特性的更新:比如Lambda支持模板、范围for支持初始化等
目前主要实用标准为 C++98
和 C++11
命名空间
产生背景:在C/C++中,变量、函数和后⾯要学到的类都是⼤量存在的,这些变量、函数和类的名称将都存在于全局作⽤域中,可能会导致很多冲突。
目的:对标识符的名称进⾏本地化,以避免命名冲突。
#include <iostream>
using namespace std;
namespace ONE {
int n = 3;
}
namespace TWO {
int n = 4;
}
int main() {
cout << ONE::n << endl;
cout << TWO::n << endl;
return 0;
}
命名空间的定义
定义命名空间,需要使⽤到namespace关键字,后⾯跟命名空间的名字,然后接⼀对{}即可,{}中
即为命名空间的成员。命名空间中可以定义变量/函数/类型等。
namespace 关键字{
//内容——可以定义变量/函数/类型等
}
• namespace本质是定义出⼀个域,这个域跟全局域各⾃独⽴,不同的域可以定义同名变量。
• C++中域有函数局部域,全局域,命名空间域,类域;
域影响的是编译时语法查找⼀个变量/函数/ 类型出处(声明或定义)的逻辑,所有有了域隔离,名字冲突就解决了。
局部域和全局域除了会影响编译查找逻辑,还会影响变量的声明周期
命名空间域和类域不影响变量声明周期
• namespace只能定义在全局,当然他还可以嵌套定义。
#include <iostream>
using namespace std;
namespace ONE{
int n = 3;
namespace TWO {
int n = 4;
}
}
int main() {
cout << ONE::n << endl;
cout << ONE::TWO::n << endl;
return 0;
}
• 项⽬⼯程中多⽂件中定义的同名namespace会认为是⼀个namespace,不会冲突。
• C++标准库都放在⼀个叫std(standard)的命名空间中。
命名空间的使用
一共有三种方法:
方法一:指定命名空间访问(推荐)
// 指定命名空间访问
#include <iostream>
using namespace std;
namespace ONE{
int n = 3;
}
int main() {
cout << ONE::n << endl;
return 0;
}
方法二:using将命名空间中某个成员展开(项⽬中经常访问的不存在冲突的成员推荐)
//using将命名空间中某个成员展开
#include <iostream>
using namespace std;
namespace ONE{
int n = 3;
}
using ONE::n;
int main() {
cout << n << endl;
return 0;
}
方法三:展开命名空间中全部成员(冲突风险大,项目中不推荐)
//展开命名空间中全部成员
#include <iostream>
using namespace std;
namespace ONE{
int n = 3;
}
using namespace ONE;
int main() {
cout << n << endl;
return 0;
}
输入输出
<iostream> 是 Input Output Stream 的缩写,是标准的输⼊、输出流库,定义了标准的输⼊、输
出对象。
• cout/cin/endl等都属于C++标准库,C++标准库都放在⼀个叫std(standard)的命名空间中,所以要 通过命名空间的使⽤⽅式去⽤他们。
• std::cin 是 istream 类的对象,它主要⾯向窄字符的标准输⼊流。
• std::cout 是 ostream 类的对象,它主要⾯向窄字符的标准输出流。
• std::endl 是⼀个函数,流插⼊输出时,相当于插⼊⼀个换⾏字符加刷新缓冲区。
• <<是流插⼊运算符,>>是流提取运算符。
#include <iostream>
using namespace std;
int n = 3;
int main() {
cout << n << endl;//此处endl写成'/n'也是同样的效果
return 0;
}
使用C++输入输出的优势
• 更⽅便,可以⾃动识别变量类型,不需要像printf/scanf输⼊输出时那样,需要⼿动指定格式。
• C++的流能更好的⽀持⾃定义类型对象的输⼊输出。
缺省函数
缺省参数是声明或定义函数时为函数的参数指定⼀个缺省值。在调⽤该函数时,如果没有指定实参 则采⽤该形参的缺省值,否则使⽤指定的实参。
缺省参数分为全缺省和半缺省参数• 全缺省就是全部形参给缺省值
• 半缺省就是部分形参给缺省值。(不是一半哦)
•C++规定半缺省参数必须从右往左依次连续缺省,不能间隔跳跃给缺省值。
• 带缺省参数的函数调⽤,C++规定必须从左到右依次给实参,不能跳跃给实参。
#include <iostream>
using namespace std;
//全缺省
void Print1(int a = 1, int b = 2) {
cout<<"全缺省"<< endl;
cout <<a<<endl;
cout <<b<< endl;
}
//半缺省
void Print2(int a , int b ,int c=3) {
cout << "半缺省" << endl;
cout <<a<< endl;
cout <<b<< endl;
cout <<c<< endl;
}
int main() {
//全缺省——不给参数
Print1();
//全缺省——给参数
Print1(4, 5);
//半缺省——给必要参数
Print2(1,2);
//半缺省——给所有参数
Print2(1, 2, 4);
return 0;
}
• 函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省
值。
函数重载
C++⽀持在同⼀作⽤域中出现同名函数,但是要求这些同名函数的形参不同,可以是参数个数不同或者类型不同。
注意:返回值不同不能作为重载条件,因为调⽤时也⽆法区分。
#include <iostream>
using namespace std;
void func(int a) {
cout << a << endl;
}
void func(int a,int b) {
cout << a<<b << endl;
}
void func(double a) {
cout << a << endl;
}
int main() {
func(1);
func(1, 2);
func(1.1111);
return 0;
}
引用
引用的概念
引⽤不是新定义⼀个变量,⽽是给已存在变量取了⼀个别名,编译器不会为引⽤变量开辟内存空间,它和它引⽤的变量共⽤同⼀块内存空间。
引用的底层仍然是指针!
类型& 引⽤别名 = 引⽤对象;
#include<iostream>
using namespace std;
int main()
{
int a = 0;
// 引⽤:b和c是a的别名
int& b = a;
int& c = a;
// 也可以给别名b取别名,d相当于还是a的别名
int& d = b;
++d;
// 这⾥取地址我们看到是⼀样的
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
cout << &d << endl;
return 0;
}
引用的特性
• 引⽤在定义时必须 初始化• ⼀个变量可以有多个引⽤(土豆有很多个别名:洋芋、马铃薯)• 引⽤⼀旦引⽤⼀个实体,再不能引⽤其他实体(洋芋只能是土豆的别名,不能再成为其它蔬菜的别名)这一点注意与指针区分开!
引用的使用
• 引⽤在实践中主要是于引⽤传参和引⽤做返回值中减少拷⻉提⾼效率和改变引⽤对象时同时改变被 引⽤对象。
1. 引⽤传参
引⽤传参跟指针传参功能是类似的,引⽤传参相对更⽅便⼀些。
#include<iostream>
using namespace std;
//引用传参
void Swap(int& rx, int& ry)
{
int tmp = rx;
rx = ry;
ry = tmp;
}
int main()
{
int x = 0, y = 1;
cout << x << " " << y << endl;
Swap(x, y);
cout << x << " " << y << endl;
return 0;
}
2.做返回值
int arr[5] = { 0 };//全局变量
int& func(int n) {
return arr[n];
}
int main() {
cout << arr[4] << endl;
func(4)=1;//改变了arr[4]的值为1
cout << arr[4] << endl;
return 0;
}
const引用
可以引⽤⼀个const对象,但是必须⽤const引⽤。const引⽤也可以引⽤普通对象原因:因为对象的访问权限在引⽤过程中 可以缩⼩ ,但是 不能放⼤ 。
注意 :类似 int& rb = a*3; double d = 12.34; int& rd = d; 这样⼀些场景下a*3的和结果保存在⼀个临时对象中, int& rd = d 也是类似,在类型转换中会产⽣临时对象存储中间值,也就是时,rb和rd引⽤的都是临时对象,⽽C++规定临时对象具有常性,所以这⾥ 就触发了权限放⼤,必须要⽤const引⽤才可以。(临时对象:编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象)
指针和引用的关系
C++中指针和引⽤就像两个性格迥异的亲兄弟,指针是哥哥,引⽤是弟弟,在实践中他们相辅相成,功 能有重叠性,但是各有⾃⼰的特点,互相不可替代。
引用 | 指针 | |
开不开空间? | 不开 | 开 |
初始化? | 必须初始化 | 建议初始化,但不必须 |
改变对象? | 引⽤⼀个对象后,不能再引⽤其他对象 | 可以改变指向对象 |
访问对象? | 可以直接访问 | 需要解引⽤ |
sizeof中含义 | 引⽤类型的⼤⼩ |
地址空间所占字节个数(32位平台下 占4个字节,64位下是8byte)
|
安全性 | 更安全 | 空指针和野指针的问题 |
内联函数
⽤inline修饰的函数叫做内联函数
nline int Add(int x, int y)
{
return x + y;
}
• inline对于编译器⽽⾔只是⼀个建议。也就是说,你加了inline编译器也可以选择在调⽤的地⽅不展开,不同编译器关于inline什么情况展开各不相同,因为C++标准没有规定这个。inline适⽤于频繁调用的短小函数,对于递归函数,代码相对多⼀些的函数,加上inline也会被编译器忽略。
想象一下,假如一个内联函数内有100条语句:
没有展开:
call:相当于跳转,跳转到函数的地址
展开:
• C++设计了inline⽬的就是替代C的宏函数C语⾔实现宏函数也会在预处理时替换展开,但是宏函数实现很复杂很容易出错的,且不⽅便调试
#define ADD(x, y) ((x) + (y)) //通过宏函数实现ADD,复杂易错
• inline不建议声明和定义分离到两个⽂件,分离会导致链接错误。因为inline被展开,就没有函数地址,链接时会出现报错。
内联函数特点:
- 在
Debug
模式下,函数不会进行替换,可以进行调试- 在
Realse
模式下,函数会像宏函数一样展开,提高程序运行速度- 内联函数弥补了宏函数的不足,同时吸收了宏函数速度快的优点
补充:
vs编译器 debug版本下⾯默认是不展开inline的,这样⽅便调试,debug版本想展开需要设置⼀下
以下两个地⽅。
nullptr
NULL实际是⼀个宏,在传统的C头⽂件(stddef.h)中,可以看到如下代码:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
由此可见,C++中NULL被定义为字⾯常量0,而非void*。
为了修复这一漏洞, C++11中引⼊nullptr。
nullptr是⼀个特殊的关键字,nullptr是⼀种特殊类型的字⾯量,它可以转换 成任意其他类型的指针类型。使⽤nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被 隐式地转换为指针类型,⽽不能被转换为整数类型。