目录
一、C++参考文档
二、C++在工作领域的应用
三、C++学习书籍
四、C++的第一个程序
五、命名空间
(1)namespace的定义
(2)命名空间的使用
六、C++的输入和输出
七、缺省函数
八、函数重载
九、写在最后
一、C++参考文档
(1)虽然不是C++官方文档且标准只更新到C++11,但是以头文件的形式呈现,内容易看好懂;
https://legacy.cplusplus.com/reference/
(2)C++官方文档的中文版和英文版,信息很全,更新到了最新的C++标准,但是相比第一个不是那么易看。
https://zh.cppreference.com/w/cpp
https://en.cppreference.com/w/
二、C++在工作领域的应用
(1)大型软件开发。如:编译器、数据库、操作系统、浏览器等。
(2)音视频处理。
(3)PC客户端开发。⼀般是开发Windows上的桌面软件,比如WPS之类的;技术栈的话一般是C++和 QT,QT是⼀个跨平台的C++图形用户界面(Graphical User Interface,GUI)程序。
(4)服务端开发。各种大型应⽤网络连接的高并发后台服务。这块Java也比较多,C++主要用于⼀些对性能要求比较高的地方。如:游戏服务、流媒体服务、量化高频交易服务等。
(5)游戏引擎开发。很多游戏引擎就都是使⽤C++开发的,游戏开发要掌握C++基础和数据结构,学习图形学知识,掌握游戏引擎和框架,了解引擎实现,引擎源代码可以学习UE4、Cocos2d-x等开源引擎实现。
(6)嵌入式开发。嵌入式把具有计算能力的主控板嵌入到机器装置或者电子装置的内部,通过软件能够控制这些装置。比如:智能手环、摄像头、扫地机器人、智能音响、门禁系统、车载系统等等,粗略⼀点,嵌入式开发主要分为嵌⼊式应⽤和嵌⼊式驱动开发。
(7)测试开发/测试。
(8)机器学习引擎。机器学习引擎。机器学习底层的很多算法都是用C++实现的,上层用python封装起来。如果你只想准备数据训练模型,那么学会Python基本上就够了,如果你想做机器学习系统的开发,那么需要学会C++。
三、C++学习书籍
(1)C++ Primer:主要讲解语法,经典的语法书籍,前后中期都可以看,前期如果自学看可能会有点晦涩难懂,中后期作为语法字典,非常好用。
(2)STL源码剖析:主要从底层实现的角度结合STL源码,庖丁解牛式剖析STL的实现,是侯捷老师的经典之作。可以很好的帮助我们学习别人用语法是如何实现出高效简洁的数据结构和算法代码,如何使用泛型封装等。
(3)Effctive C++:本书也是侯捷老师翻译的,本书有的⼀句评价,把C++程序员分为看过此书的和没看过此书的。本书主要讲了55个如何正确高效使用C++的条款。
四、C++的第一个程序
C++兼容C语言绝大多数的语法,所以C语言实现的hello world依旧可以运行:
#include <stdio.h>int main(){ printf("Hello world\n"); return 0;}
当然C++有⼀套自己的输入输出,C++中需要把定义文件代码后缀改为.cpp,vs编译器看到是.cpp就会调用C++编译器编译,linux下要用g++编译,不再是gcc。
严格说C++版本的hello world应该是这样写的:
#include <iostream>using namespace std;int main(){ cout << "Hello world\n" << endl; return 0;}
五、命名空间
(1)namespace的定义
①定义命名空间,需要用到namespace关键字,后面跟着命名空间的名字,然后用一对{}连接,{}中为命名空间的成员。命名空间可以定义变量/函数/类型等。
与结构体的区别是:{}后面没有分号;
namespace zfy{ //变量 int rand = 10; //函数 int Add(int a, int b) { return a + b; } //类型 struct Node { struct Node* next; int val; }}
②namespace本质是定义出一个域,这个域跟全局域各自独立。
③不同的域可以定义同名变量,因此下面的两个rand不会冲突。
#include <stdio.h>#include <stdlib.h>namespace zfy{ int rand = 10; int Add(int a, int b) { return a + b; } struct Node { struct Node* next; int val; }}int main(){ printf("%p\n",rand);//这里默认访问的是全局的rand函数指针(即头文件stdlib.h中的) printf("%d\n",zfy::rand);//这里访问的是命名空间中的rand return 0;}
④C++中域有函数局部域、全局域、命名空间域、类域;
域影响的是编译时语法查找⼀个变量/函数/ 类型出处(声明或定义)的逻辑,所有有了域隔离,名字冲突就解决了。
局部域和全局域除了会影响编译查找逻辑,还会影响变量的生命周期,命名空间域和类域不影响变量生命周期。
//全局域int rand = 10;//命名空间域namespace zfy{ int rand = 10; int Add(int a, int b) { return a + b; } struct Node { struct Node* next; int val; }}//函数局部域int Add(int a, int b){ return a + b;}int main(){ //函数局部域 int rand = 10; return 0;}
⑤namespace只能定义在全局(即不能在main函数或者其他函数中定义),当然他还可以嵌套定义。
//命名空间的嵌套namespace zfy{ namespace z { int rand = 1; int Add(int left, int right) { return left + right; } } namespace f { int rand = 2; int Add(int left, int right) { return (left + right)*10; } }}//调用int main(){ printf("%d\n", zfy::z::rand); printf("%d\n", zfy::f::rand); printf("%d\n", zfy::z::Add(1, 2)); printf("%d\n", zfy::f::Add(1, 2)); return 0;}
⑥项目工程中多文件中定义的同名namespace会认为是⼀个namespace,不会冲突。
// Stack.h#pragma once#include<stdio.h>#include<stdlib.h>#include<stdbool.h>#include<assert.h>namespace zfy{ typedef int STDataType; typedef struct Stack { STDataType* a; int top; int capacity; }ST; void STInit(ST* ps, int n); void STDestroy(ST* ps); void STPush(ST* ps, STDataType x); void STPop(ST* ps); STDataType STTop(ST* ps); int STSize(ST* ps); bool STEmpty(ST* ps);}// Stack.cpp#include"Stack.h"namespace zfy{ void STInit(ST* ps, int n) { assert(ps); ps->a = (STDataType*)malloc(n * sizeof(STDataType)); ps->top = 0; ps->capacity = n; } // 栈顶 void STPush(ST* ps, STDataType x) { assert(ps); // 满了, 扩容 if (ps->top == ps->capacity) { printf("扩容\n"); int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2; STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType)); if (tmp == NULL) { perror("realloc fail"); return; } ps->a = tmp; ps->capacity = newcapacity; } ps->a[ps->top] = x; ps->top++; }}
比如说,我们在Stack.h和Stack.cpp中都定义了名为zfy的命名空间,但在使用时会被认为是一个命名空间,不会冲突:
// test.cpp#include"Queue.h"#include"Stack.h"//在全局定义⼀份单独的Stack typedef struct Stack{ int a[10]; int top;}ST;void STInit(ST* ps){}void STPush(ST* ps, int x){}int main(){ // 调⽤全局的 ST st1; STInit(&st1); STPush(&st1, 1); STPush(&st1, 2); printf("%d\n", sizeof(st1)); // 调⽤zfy namespace的 zfy::ST st2; bzfy::STInit(&st2); zfy::STPush(&st2, 1); zfy::STPush(&st2, 2); printf("%d\n", sizeof(st2)); return 0;}
⑦C++标准库都放在⼀个叫std(standard)的命名空间中。
(2)命名空间的使用
编译查找⼀个变量的声明/定义时,默认只会在局部或者全局查找,不会到命名空间里面去查找。所以下面程序会编译报错。
#include<stdio.h>namespace zfy{ int a = 0; int b = 1;}int main(){ // 编译报错:error C2065: “a”: 未声明的标识符 printf("%d\n", a); return 0;}
因此我们要想使用命名空间中定义的变量/函数,有三种方式:
①指定命名空间访问。项目中推荐这种方式。
// 指定命名空间访问 int main(){ printf("%d\n", zfy::a); return 0; }
②using将命名空间中某个成员展开。项目中经常访问的不存在冲突的成员推荐这种方式。
// using将命名空间中某个成员展开 using zfy::b;int main(){ printf("%d\n", zfy::a); printf("%d\n", b); return 0; }
③展开命名空间中全部成员。项目不推荐,冲突风险很大,因为可能会与全局中的变量冲突,导致编译报错,但在日常小练习时较方便,推荐使用。
// 展开命名空间中全部成员 using namespce zfy;int main(){ printf("%d\n", a); printf("%d\n", b); return 0; }
六、C++的输入和输出
①<iostream>是Input Output Stream的缩写,是标准的输入、输出流库,定义了标准的输入、输 出对象。
②std::cin是 istream类的对象,它主要面向窄字符(narrow characters (of type char))的标准输输入流。
③std::cout 是ostream类的对象,它主要面向窄字符的标准输出流。
④std::endl是⼀个函数,流插入输出时,相当于插入⼀个换行字符加刷新缓冲区。
⑤>是流提取运算符。(C语言还用这两个运算符做位运算左移/右移)。
⑥使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动指定格式,C++的输入输出可以自动识别变量类型(本质是通过函数重载实现的),其实最重要的是 C++的流能更好的支持自定义类型对象的输入输出。
#include <iostream>using namespace stdint main(){ int a = 0; double b = 0.1; char c = 'a'; printf("%d %lf %c\n",a, b, c); //输出整型 std::cout << a << std::endl; //输出浮点型 std::cout << b << std::endl; //输出字符 std::cout << c << std::endl; //还可以连接使用 std::cout << a << " " << b << " " << c << " " << std::endl; //输出:0 0.1 a //输出换行符 std::cout << "Hello\n"; std::cout << a << '\n'; std::cout << b << "\n"; std::cout << c << std::endl; //在前面可以使用using namespace std;那么std::cout、std::endl可以写成cout、endl //输入时可以自动识别类型 cin >> a; cin >> b >> c; //输出 std::cout << a << std::endl; std::cout << b << " " << c << std::endl; return 0;}
⑦ cout/cin/endl等都属于C++标准库,C++标准库都放在⼀个叫std(standard)的命名空间中,所以要通过命名空间的使用方式去用他们。
⑧⼀般日常练习中我们可以using namespace std,实际项目开发中不建议using namespace std。
这里我们没有包含<stdio.h>,仍可以使用printf和scanf,其实是在包含<stdio.h>时间接包含了。vs系列编译器是这样的,其他编译器可能会报错。
#include<iostream>using namespace std;int main(){ // 在io需求⽐较⾼的地⽅,如部分⼤量输⼊的竞赛题中,加上以下3⾏代码,可以提⾼C++IO效率 ios_base::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); return 0;}
七、缺省函数
①缺省参数是声明或定义函数时为函数的参数指定⼀个缺省值。在调用该函数时,如果没有指定实参,则采用该形参的缺省值,否则使用指定的实参,缺省参数分为全缺省和半缺省参数。(有些地方把缺省参数也叫默认参数);
#include <iostream>#include <assert.h>using namespace std;//全缺省void Func(int a = 0){ cout << a << endl;}int main(){ Func(); // 没有传参时,使⽤参数的默认值 Func(10); // 传参时,使⽤指定的实参 return 0;}
②全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值。
③C++规定半缺省参数必须从右往左依次连续缺省,不能间隔跳跃给缺省值。
④带缺省参数的函数调用,C++规定必须从左到右依次给实参,不能跳跃给实参。
#include <iostream>using namespace std;// 全缺省:全部形参给缺省值void Func1(int a = 10, int b = 20, int c = 30){ cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl << endl;}// 半缺省:部分形参给缺省值//半缺省函数必须从右往左依次连续缺省,不能跳跃给缺省值//错误写法:void Fun2(int a = 10, int b , int c = 20)void Func2(int a, int b = 10, int c = 20){ cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl << endl;}int main(){ Func1(); Func1(1); Func1(1,2); Func1(1,2,3); //传参必须从左到右,不能跳跃给实参 //错误写法:Fun1(,2,3); Func2(100); Func2(100, 200); Func2(100, 200, 300); return 0;}
⑤函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省 值。
// 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;}// test.cpp#include"Stack.h"int main(){ ST s1; STInit(&s1); //如果不确定大小,默认为4 //如果确定知道要插⼊1000个数据,初始化时⼀把开好,避免扩容 ST s2; STInit(&s2, 1000); return 0;}
八、函数重载
①C++支持在同⼀作用域中出现同名函数,但是要求这些同名函数的形参不同,可以是参数个数不同或者类型不同。(返回值不同不能作为重载条件)
#include<iostream>using namespace std;// 1、参数类型不同 int Add(int left, int right){ cout << "int Add(int left, int right)" << endl; return left + right;}double Add(double left, double right){ cout << "double Add(double left, double right)" << endl; return left + right;}// 2、参数个数不同 void f(){ cout << "f()" << endl;}void f(int a){ cout << "f(int a)" << endl;}// 3、参数类型顺序不同(本质上还是类型不同)void f(int a, char b){ cout << "f(int a,char b)" << endl;}void f(char b, int a){ cout << "f(char b, int a)" << endl;}//!返回值不同不能作为重载条件,因为调⽤时也⽆法区分 //void fxx()//{}//int fxx()//{// return 0;//}
②这样C++函数调用就表现出了多态行为,使用更灵活。而C语言是不支持同⼀作用域中出现同名函数的。
// 下⾯两个函数构成重载 // 但是调⽤f()时,会报错,存在歧义,因为编译器不知道调⽤谁 void f1(){ cout << "f()" << endl;}void f1(int a = 10){ cout << "f(int a)" << endl;}int main(){ Add(10, 20); Add(10.1, 20.2); f(); f(10); f(10, 'a'); f('a', 10); //到底是第一个函数,还是第二个函数,使用缺省值?(有歧义) return 0;}
九、写在最后
我们刚接触C++,需要一定的知识储备才能进行学习。
敬请期待“入门基础(下)”~