目录
前言:今天我们将进入C++的大门,希望我的文章对大家的C++之旅有些帮助。
一、C++的发展历史
C++的起源可以追溯到1979年,当时Bjarne Stroustrup(本贾尼·斯特劳斯特卢普,这个翻译的名字不 同的地⽅可能有差异)在⻉尔实验室从事计算机科学和软件⼯程的研究⼯作。⾯对项⽬中复杂的软件开 发任务,特别是模拟和操作系统的开发⼯作,他感受到了现有语⾔(如C语⾔)在表达能⼒、可维护性 和可扩展性⽅⾯的不⾜。
1983年,Bjarne Stroustrup在C语⾔的基础上添加了⾯向对象编程的特性,设计出了C++语⾔的雏形, 此时的C++已经有了类、封装、继承等核⼼概念,为后来的⾯向对象编程奠定了基础。这⼀年该语⾔被 正式命名为C++。
在随后的⼏年中,C++在学术界和⼯业界的应⽤逐渐增多。⼀些⼤学和研究所开始将C++作为教学和研 究的⾸选语⾔,⽽⼀些公司也开始在产品开发中尝试使⽤C++。这⼀时期,C++的标准库和模板等特性 也得到了进⼀步的完善和发展。
C++的标准化⼯作于1989年开始,并成⽴了⼀个ANSI和ISO(International Standards Organization)国际标准化组织的联合标准化委员会。1994年标准化委员会提出了第⼀个标准化草 案。在该草案中,委员会在保持斯特劳斯特卢普最初定义的所有特征的同时,还增加了部分新特征。
在完成C++标准化的第⼀个草案后不久,STL(Standard Template Library)是惠普实验室开发的⼀系 列软件的统称。它是由Alexander Stepanov、Meng Lee和David R Musser在惠普实验室⼯作时所开发 出来的。在通过了标准化第⼀个草案之后,联合标准化委员会投票并通过了将STL包含到C++标准中的 提议。STL对C++的扩展超出C++的最初定义范围。虽然在标准中增加STL是个很重要的决定,但也因 此延缓了C++标准化的进程。
1997年11⽉14⽇,联合标准化委员会通过了该标准的最终草案。1998年,C++的ANSI/IS0标准被投⼊ 使⽤。
二、C++的第一个程序
C++兼容C语言中的绝大多数语法,其中典型的包括C语言中的“Hello world"。在C++时需要定义文件后缀改为.cpp
在C++条件下用C语言的语法:
#include<stdio.h> int main() { printf("Hellow world!"); return 0; }
关于“Hello world",C++有自己的一套写法:
#include<iostream> using namespace std; int main() { cout << "Hello world!" << endl; return 0; }
三、命名空间
3.1namespace的价值
在我们之前C语言的学习中,可知C语言有32个关键字,而C++中有62个关键字,这些变量、函数和类的名称将都存在于全 局作⽤域中,可能会导致很多冲突。
C语言项目中下面的命名冲突可能发生:
而C++的关键字更多,为了避免命名冲突,C++引用了namespace关键字,使⽤命名空间的⽬的是对标识符的名称进⾏本地化,以避免命名 冲突或名字污染,namespace关键字的出现就是针对这种问题的 。
3.2namespace的定义
①定义命名空间,需要使用namespace关键字,后面跟空间名,在加一个{},{}中是命名空间的成员,命名空间内可以定义变量、函数、类型等。“namespace+空间名+{}”
②namespace的本质是定义出一个新的域,与全局域相互独立,这两个不同域可以定义同名变量,所以下面的rand就不在冲突了。
③C++中域有函数局部域,全局域,命名空间域,类域;域影响的是编译时语法查找⼀个变量/函数/ 类型出处(声明或定义)的逻辑,所有有了域隔离,名字冲突就解决了。局部域和全局域除了会影响 编译查找逻辑,还会影响变量的⽣命周期,命名空间域和类域不影响变量⽣命周期。
④namespace定义的域只能在全局,还可以嵌套定义。
⑤一个项目中定义了多个同名的namespace会认为是同一个
⑥C++标准库都放在⼀个叫std(standard)的命名空间中。
namespace wei { //命名空间中可以定义变量、函数、类型 int rand = 10; int add(int x, int y) { return x + y; } struct Node { struct Node* next; int val; }; //命名空间的嵌套使用 namespace wei02 { int Sub(int x, int y) { return x - y; } } } int main() { printf("%d\n", wei::rand); printf("%d\n",wei::add(10,65)); printf("%d\n", wei::wei02::Sub(10,65)); return 0; }
namespace wei { //命名空间中可以定义变量、函数、类型 int rand = 10; int add(int x, int y) { return x + y; } struct Node { struct Node* next; int val; }; //命名空间的嵌套使用 namespace wei02 { int Sub(int x, int y) { return x - y; } } } //一个项目中定义了多个同名的namespace会认为是同一个 namespace wei { int a = 50; int chu(int x, int y) { return x / y; } } int main() { printf("%d\n", wei::rand); printf("%d\n",wei::add(10,65)); printf("%d\n", wei::wei02::Sub(10,65)); printf("%d\n", wei::chu(10, 2)); return 0; }
3.3命名空间的使用
编译查找定义或声明的变量时只会从全局或局部变量里找,不会到命名空间里去找。我们可以通过一下三种方法找到命名空间里的变量等:
①指定命名空间展开:如上面wei::rand(空间名+::+成员)
②using将命名空间中某个成员展开(将这个成员公开),项⽬中经常访问的不存在冲突的成员推荐这种⽅式。
(using+空间名+::+要展开的成员)
③usiing将整个命名空间展开,冲突⻛险很⼤,⽇常⼩练习程序为了⽅便推荐使⽤。(using+命名空间+空间名)
namespace dong { int a = 10; int b=0; int c=800; } int main() { printf("%d", a);//未声明表示符a return 0; } //指定命名空间访问 namespace dong { int a = 10; int b = 0; int c = 800; } int main() { printf("%d",dong::a); return 0; } //展开部分成员 namespace dong { int a = 10; int b = 0; int c = 800; } using dong::a; int main() { printf("%d", a); return 0; } //展开全部成员访问 namespace dong { int a = 10; int b = 0; int c = 800; } using namespace dong; int main() { printf("%d", a); return 0; }
四、C++的输入与输出
在C语言中我们经常用printf和scanf等输入输出函数,来对数据进行输入输出,在C++中C语言的这一套仍能使用,但是C++自己也提出了一套:
<iostream>是 Input Output Stream 的缩写,是标准的输⼊、输出流库,定义了标准的输⼊、输 出对象。iostream 库包含两个基础类型 istream 和 ostream,分别表示输入流和输出流。
①cout是输出时使用的,与C语言中的 printf 作用一致,还有一个是 cin,与 scanf 作用一致。
②在使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件以及按命名空间使用方法使用std。
⼀般⽇常练习中我们可以using namespace std,实际项⽬开发中不建议using namespace std。
③std::endl 是⼀个函数,流插⼊输出时,相当于插⼊⼀个换⾏字符加刷新缓冲区。
④<<是流插入运算符,>>是流提取运算符。
⑤C++不用向C语言一样要手动输入格式,C++可以自己识别类型。
#include<iostream> using namespace std; int main() { int a = 0; char b = 'a'; double c = 2.22; cout << a<<' '<< b <<' '<< c << endl; std::cout << a << ' ' << b << ' ' << c << std::endl; cin >> a >> b >> c ; cout << a << ' ' << b << ' ' << c << endl; std::cin >> a >> b >> c; cout << a << ' ' << b << ' ' << c << endl; return 0; }
五、缺省参数
缺省参数是在定义函数时给某个参数指定一个默认值,当调用函数时如果没有传入该实参数的值,则会使用默认的实参的值来代替。这样可以简化函数的调用,增加函数的灵活性。
缺省参数分为:全缺省和半缺省
全缺省参数: 全部形参给缺省值。
半缺省参数:部分参数给缺省值。
注意:
①C++规定半缺省参数必须从右往左 依次连续缺省,不能间隔跳跃给缺省值。
②C++规定半缺省函数调用时,给实参时必须从左到右,不能间隔跳跃给实参。
③规定当函数声明和定义分离时,缺省函数不能在声明和定义里同时出现,规定在声明中出现
#include<iostream> using namespace std; //全缺省 void func1(int a=5, int b = 10, int c = 15) { cout << "a=" << a << endl; cout << "b=" << b << endl; cout << "c=" << c << endl; } //半缺省,从右往左缺省 void func2(int a, int b = 10, int c = 15) { cout << "a=" << a << endl; cout << "b=" << b << endl; cout << "c=" << c << endl; } int main() { //实参从左往右给 func1(); func1(1); func1(1, 2); func1(1, 2, 3); func2(1); func2(1, 2); func2(1, 2, 3); }
// Stack.h #include <iostream> #include <assert.h> using namespace std; typedef int STDataType; typedef struct Stack { STDataType* a; int top; int capacity; }ST; void STInit(ST* ps, int n = 4); // Stack.cpp #include"Stack.h" // 缺省参数不能声明和定义同时给 void STInit(ST* ps, int n) { assert(ps && n > 0); ps->a = (STDataType*)malloc(n * sizeof(STDataType)); ps->top = 0; ps->capacity = n; }
六、函数重载
在上面讲命名空间时我们说,为了防止同一域内命名冲突我们建立命名空间(新的域),而C++⽀持在同⼀作⽤域中出现同名函数,但是要求这些同名函数的形参不同,可以是参数个数不同或者 类型不同。这样C++函数调⽤就表现出了多态⾏为,使⽤更灵活。C语⾔是不⽀持同⼀作⽤域中出现同 名函数的。
#include<iostream> using namespace std; //函数类型不同 int add(int a, int b) { return a + b; } double add(double a, double b) { return a + b; } //函数形参数量不同 void add() { cout << "add()" << endl; } void add(int a = 10) { cout << "add()" << endl; } //函数形参顺序不同 void add(int a, char b) { cout << "add()" << endl; } void add(char b, int a) { cout << "add()" << endl; } //函数重载会遇到的问题 void add() { cout << "add()" << endl; } void add(int a = 10) { cout << "add()" << endl; } int main() { add();//此时编译器不知道调用哪一个 return 0; }
七、引用
7.1引用的定义与概念
引用并不是重新定义一个变量,他是给已经存在的变量取一个别名,编译器不会创建空间,而是与大名公用一块空间 。人的外号就类似于引用,举例如:“及时雨”宋江。
类型+&+别名==引用对象
int main() { int a = 0; int& b = a; int& c = a; cout << &a << endl;//00000034E38FF6A4 cout << &b << endl;//00000034E38FF6A4 cout << &c << endl;//00000034E38FF6A4 }
7.2引用的特性
①引用必须初始化。
②一个变量可以有多个引用。(一个人可以有多个别名)
③一个引用只能指向一个变量。(一个别名只能代表一个人)
int main() { int a = 0; int& d;//未初始化 //一个变量有多个引用 int& b = a; int& c = a; //这里只改变b的值,并不改变b的指向 int h = 2; b = h; }
7.3引⽤的使⽤
C++中引用的目的:
①引⽤在实践中主要是于引⽤传参和引⽤做返回值中减少拷⻉提⾼效率和改变引⽤对象时同时改变被 引⽤对象。
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; }
7.4const引⽤
①当我们引用一个被const限制的变量时,我们必须也用const限制引用。如果说const限制了一个变量,这个变量就“只能读不能写”,而当引用不加const,他就“能读能写了”,这是不被允许的。
即对象的访问权限在引⽤过程中可以缩⼩,但是不能放⼤。所以const引⽤也可以引⽤普通对象。
②当我们引用表达式相加、调用函数、函数传值反回和类型转换,会产⽣临时对象存储中间值,也就是时,引⽤的都是临时对象,而临时对象具有常性,所以这时我们要加const。
③所谓临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象, C++中把这个未命名对象叫做临时对象。
int main() { const int a = 10; // 编译报错:error C2440: “初始化”: ⽆法从“const int”转换为“int &” // 这⾥的引⽤是对a访问权限的放⼤ //int& ra = a; // 这样才可以 const int& ra = a; // 编译报错:error C3892: “ra”: 不能给常量赋值 //ra++; // 这⾥的引⽤是对b访问权限的缩⼩ int b = 20; const int& rb = b; // 编译报错:error C3892: “rb”: 不能给常量赋值 //rb++; return 0; }
#include<iostream> using namespace std; int main() { int a = 10; const int& ra = 30; // 编译报错: “初始化”: ⽆法从“int”转换为“int &” // int& rb = a * 3; const int& rb = a*3; double d = 12.34; // 编译报错:“初始化”: ⽆法从“double”转换为“int &” // int& rd = d; const int& rd = d; return 0; }
7.5引用和指针的区别
引用和指针的区别:
①语法概念上引⽤是⼀个变量的取别名不开空间,指针是存储⼀个变量地址,要开空间。
②引⽤在定义时必须初始化,指针建议初始化,但是语法上不是必须的。
③引⽤在初始化时引⽤⼀个对象后,就不能再引⽤其他对象;⽽指针可以在不断地改变指向对象。
④引⽤可以直接访问指向对象,指针需要解引⽤才是访问指向对象。
⑤sizeof中含义不同,引⽤结果为引⽤类型的⼤⼩,但指针始终是地址空间所占字节个数(32位平台下 占4个字节,64位下是8byte)
⑥指针很容易出现空指针和野指针的问题,引⽤很少出现,引⽤使⽤起来相对更安全⼀些。
八、inline
C++设计了inline⽬的就是替代C的宏函数。
C的宏函数弊端:
C语⾔实现宏函数也会在预处理时替换展开,但是宏函数实现很复杂很容易出错的。
inline的作用:
⽤inline修饰的函数叫做内联函数,编译时C++编译器会在调⽤的地⽅展开内联函数,这样调⽤内联 函数就需要建⽴栈帧了,就可以提⾼效率。
注意:
①并不是用了inline就会展开,这个对编译器只是建议,当函数指令太多时会被直接忽略。
②inline不建议声明和定义分离到两个⽂件,分离会导致链接错误。因为inline被展开,就没有函数地 址,链接时会出现报错。
#include<iostream> using namespace std; inline int Add(int x, int y) { int ret = x + y; ret += 1; ret += 1; ret += 1; return ret; } int main() { // 可以通过汇编观察程序是否展开 int ret = Add(1, 2); cout << Add(1, 2) * 5 << endl; return 0;
九、nullptr
NULL实际是⼀个宏,在传统的C头⽂件(stddef.h)中,可以看到如下代码:
NULL在C++中被替换成为0,而在C语言中被定义为⽆类型指针(void*)的常量。为了避免在C++错误使用。
C++11中引⼊nullptr,nullptr是⼀个特殊的关键字,nullptr是⼀种特殊类型的字⾯量,它可以转换 成任意其他类型的指针类型。使⽤nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被 隐式地转换为指针类型,⽽不能被转换为整数类型。
void add(int*a) { cout << "add(a)" << endl; } void add(int a = 10) { cout << "add()" << endl; } int main() { add(); add(NULL); add(nullptr); return 0; }