C++学习之路 - C++相对于C的扩展
总概述
书籍
当字典
《C++ Primer》《C++程序设计原理与实践》
跨平台
路程
常用语法
知识点
案例
面向对象
兼容C语言代码效率
兼顾Java数据抽象
标准C++一般基于C++98
C++11也在逐渐成为大众标准
C++概述
历史背景
江湖地位
-
编程语言现在大约100种
-
热度排名
-
tiobe编程语言排行
-
JAVA范围广、可移植、门槛低
-
C++门槛高、薪资高
-
面向对象
-
介于C和Java
-
代码效率高
- 具备C语言和Java的优点
-
-
-
-
C语言市场份额大但相比于Java场景范围不一样
- 相比于Java(封装高)代码执行效率高但代码庞大
- 底层
-
Python是脚本语言,特点是人工智能的应用,语法特性和C很像,但是更简单
-
C++之父
姓名
- Bjarne Stoustrup(1950--)
贡献(1979)
-
预处理程序
-
Cpre
- 为C语言增加了类的机制
-
贡献(1983)
-
C with Class
- 后来称为C++
贡献(1985)
-
第一个C++编译器
- CFront 1.0
-
第一本C++语法书
- 《The C++ Programming Language》
C++ 发展过程
1987,GNU C++
1990,Borland C++
1992,Microsoft C++(VC,VS)
1998,ISO C++98(国际标准)(主要)
2003,ISO C++03
- 优化完善,实际上微乎其微
2011,,ISO C++11
2014,ISO C++14
2017,ISO C++17
*2020,ISO C++20
应用领域
游戏开发
- 主流开发方向
- 性能和效率高
- 适合大型开发
科学计算
网络和分布式应用
操作系统和设备驱动
其它...
C和C++
相同点
- 1.编译型语言,都同样采用静态编译
- 2.强类型语言,但C++更强
不同点
-
1.C++去除了C中不好的特性
-
2.增加了C语言中没有的好的语法特性
- 全面支持面向对象
- 比C语言更适合大型软件开发
第一个C++程序
#include<iostream>
int main(void){
std::cout << "hello world!" << std::endl;
return 0;
编译方式
-
gcc xx.cpp
- -lstdc++
-
g++ xx.cpp
- 更方便,推荐!
文件扩展名
- .cpp(推荐)
- .cc
- .C(大写)
- .cxx
头文件
-
include
- 在C++中,和I/O相关的类型、对象、函数都在头文件中
-
C++绝大多数头文件没有".h"后缀
-
在C++开发中依然可以使用标准C库的头文件,另外,在C++中还提供一套不带".h"替换版本
-
include<stdio.h> ==> #include
-
include<stdlib.h> ==> #include
-
include<string.h> ==> #include
-
-
c++头文件位置
- /usr/include/c++/...
C++标准输入和输出
-
cin对象表示标准输入//类似scanf
-
从键盘读取一个int数据
-
C语言
- int num = 0;
scanf("%d",&num);
- int num = 0;
-
C++
- cin >> num;
/和C语言">>"按位右移无关,被称为输入(提取)操作符/
- cin >> num;
-
-
从键盘同时读取一个int和一个double数据
-
C语言
- int i =0,double d = 0.0;
scanf("%d%lf",&i,&d);
- int i =0,double d = 0.0;
-
C++
- cin >> i >>d;
-
-
C++标准输出
-
类似于C语言的printf
-
打印输出int类型数据
-
C语言
- int num = 123;
printf("%d\n",num);
- int num = 123;
-
C++
- cout << num << endl;
-
-
同时打印输出int和一个double
int i=100,double d = 1.23;-
C语言
- printf("%d,%lf\n",i,d);
-
C++
- cout << i << ',' << d <<endl;
-
-
面向对象
C
-
逐一刻画细节
- 一个步骤出现问题会牵一发动全身
C++
- 先对整体描述,后刻画细节
C++的"<<"">>"
<<
- 被称为输出(插入)操作符
>>
- 输入操作符
编译步骤
1.预处理
2.编译,检查语法
3.汇编
4.链接
名字空间(命名空间)
背景
大项目变量重名
分配变量作用范围
意义(作用)
在不同空间,相同的名字在连接时相互不可见,避免冲突
划分逻辑单元
语法和定义
namespace 名字空间{
名字空间成员1;
名字空间成员2;
...
}
- 注意:
名字空间的成员可以是全局函数、可以是全局变量、自定义类型、名字空间
表达大的作用域范围
成员的使用
通过作用域限定操作符
-
::(类似于中文“的”)
- 名字空间名 :: 要访问的成员;
示例
-
1 #include
2
3 namespace ns1{
4 void func(void){
5 std::cout << "ns1的func" << std::endl;
6 }
7 }
8 namespace ns2{
9 void func(void){
10 std::cout << "ns2的func" << std::endl;
11 }
12 }
13
14 int main(void){
15 //func();//直接访问报错
16 ns2::func();//可以通过作用域限定访问
17 return 0;
18 } -
简化方法
-
名字空间指令
-
using namespace 名字空间名;
-
注:在该条指令以后的代码中,指定名字空间的成员都可见,可以直接访问,省略"名字空间名::"
- eg:
using namespace std;
cout << a;//OK
- eg:
-
-
-
名字空间声明
-
using 名字空间名::名字空间成员;
- 注:
将名字空间中特定的一个成员引入到当前作用域,在该作用域访问这个成员就如同访问自己的局部成员一样,可以直接访问,省略"名字空间名::"
- 注:
-
-
全局作用域和匿名名字空间
没有放在任何名字空间的成员属于全局作用域,可以直接访问,但如果和局部作用域的成员名字一样,局部优先;这时如果还希望访问全局作用域的成员,可以通过"::xx"显式访问
定义名字空间时可以没有名字,即为匿名名字空间,匿名名字空间中的成员和全局作用域类似,可以直接访问,也可以通过":::xx:"显式的访问,唯一的区别是匿名名字空间的成员被局限在当前文件中使用
*名字空间的嵌套和合并
名字空间嵌套
-
eg:
namespace ns1{
int num = 100;
namespace ns2{
int num = 200;
namespace ns3{
int num = 300;
}
}
}- cout << ns1::num <<endl;//100
- cout <<ns1::ns2::num << endl;//200
- cout << ns1::ns2::ns3::num << endl;//300
名字空间合并
-
两个同名名字空间会自动合并成一个
第三章~第十一章为C++相对于C的扩展
C++的结构体、联合体、枚举
和C语言完全兼容
介绍和C语言相比一点点 的语法扩展
结构体
当定义结构体变量时可以
直接省略struct 关键字
-
C语言
-
struct HttpRequest{...};
- struct HttpRequest req1;
-
-
C++
-
struct HttpRequest{...};
- HttpRequest req2;
-
-
相比于C语言略微精炼
在C++结构体内部可以直接定义函数,称为成员函数(方法),在成员函数内部可以直接访问结构体的其它成员.
枚举
用的多,一般大项目使用广泛
常量集合或者列表
意义:提高代码的可读性
- enum STATE{SLEEP=0,RUN=1,STOP=2};
C++枚举和C语言枚举的区别
-
定义枚举变量时可以省略enum关键字
-
C++中的枚举被看做是独立数据类型,不能把枚举直接当做整形数来使用
- enum STATE{SLEEP=0,RUN=1,STOP=2};
/enum/ STATE s;
s = RUN;//OK 和C语言一样
s = 1;//C语言OK //C++报错
- enum STATE{SLEEP=0,RUN=1,STOP=2};
*联合体
存储于相同的地址
当定义联合体变量时可以省略union关键字
C++中支持匿名联合
C++的字符串(重点)
回顾C语言字符串
字面值常量字符串:"..."
字符指针:char*
字符数组:char[]
C++兼容C风格字符串,同时增加了string类型,专门表示字符串
更安全更方便
定义
string s1;//定义空字符串
暂时可以将string 理解为结构体类型(不准确)
string不是关键字
string s2 = "xx";//定义同时初始化
字符串拷贝
string s1 = "youchengwei";
string s2 = "mindasheng";
s1 = s2;//将s2拷贝给s1
cout << s1 << endl;//“mingdasheng”
- 简单、安全
无越界,在堆区完成
字符串连接
string s1 = "hello";
string s2 = " world";
string s3 = s1 + s2;
cout << s3 << endl;//"hello world"
- 无需担心内存问题,在堆区完成
string 核心
struct string{
char *m_str;//指向堆区
}
字符串比较
if(s1==s2)
if(s1 != s2)
比较大小是比较首字母ASCII码值,而非长短
随机访问
字符串数组下标访问
-
string s = "minwei";
-
将"m""w"改成大写
- s[0] = 'M';
s[3] = 'W';
cout << s << endl;MinWei
- s[0] = 'M';
-
其它函数
size() / length();//获取字符串长度
- eg:
string str = "youchengwei";
str.size();//求字符串长度 11
str.length();//11 和size等价
c_str();//返回字符串的起始地址(const char*)
- string s1 = "hello";//C++风格
const char* s2 = "world";//C风格
s1 = s2;//OK C++兼容C
s2 = s1;//error C不兼容C++
s2 = s1.c_str(); - 作用:类型转换,兼容C风格
段错误
一般是SIGSEGV
一般是对内存的某些数据区域的访问出现了问题,一般为非法访问内存
内存泄露
堆区内存分配了,没有及时释放,浪费了内存资源
进程运行起来后
内存的情况
栈区(高地址)
自动管理自动回收
- 非静态局部变量
- 函数形参
堆区,分配了要及时释放
-
动态内存
- malloc
- ...
全局区(整个进程的生命周期)
全局变量在这个区
- 数据区(初始化)
- BSS区(未初始化)
代码段(只读)
- 字面值常量
- 代码指令本身
- ...
C语言字符串比较
strcmp()
if(strcmp(s1,s2))
- s1,s2相等,返回0
类是结构体的扩展
里面包含了很多成员函数
语义差异
string
-
字符容器
- size和length互相调用
练习1
题目:使用string表示字符串,从键盘读取一个字符串并且统计里面包含字符"Y/y"个数
提示:
string str;
cin >> str;//从键盘读取一个字符串
练习2
题目:从键盘读取字符串,实现字符串的反转(逆置)
布尔类型(bool)
算C++基本数据类型
专门表示逻辑值,逻辑真用true表示,逻辑假用false表示.
大小
布尔类型在内存占一个字节
- 1表示true
- 0表示false
接受表达式结果
bool类型变量可以接受任意表达式的结果,其值非0则为true,为0则为false
空指针为false,非空指针为true
操作符别名
背景
某些西方欧洲国家,键盘上的字符比较多但是没有 && || 等等
意义
别名替换
代换
&& <==> and
|| <==> or
{ <==> <%
} <==> %>
....
函数
函数重载
(overload)
背景
-
函数功能类似
- eg:
strcpy(s1,s2);
strncpy(s1,s2,n);
- eg:
eg:
实现图形库的绘图函数
-
C语言方式
- //画矩形
void drawRect(int x,int y, int w,int h){
...
}
- //画矩形
-
C++方式
- //画图形
void draw(int x,int y,int w,int h){
...
}
- //画图形
定义
-
在相同的作用域定义同名的函数,但是参数必须有所区分,这样函数构成重载关系
- 注:
函数重载和返回类型无关
- 注:
条件
- 相同作用域
- 同名函数
- 参数有所区分(参数个数和类型)
调用重载关系函数时,一般是由编译器根据实参和形参的匹配程度,自动选择最优的重载版本.
- 当前g++匹配的一般规则:
完全匹配->常量转换->升级转换->降级转换->省略号匹配
原理
-
C++的编译器在编译函数时会进行换名,即将参数表的类型信息整合到新的函数名中,因为重载关系的函数参数表有所区分,换出的新的函数名也一定有所区分,解决了函数重载和名字冲突的矛盾.
-
笔试题例子
- Q:C++中extern “C”作用?
- A:显式告诉编译器按照C语言方式编译该函数(不换名,方便C程序直接调用,但和重载矛盾,声明的函数无法重载).
-
函数的缺省参数
(默认实参[重点])
可以为函数参数指定缺省值,调用该函数时,如果不给实参,就取缺省值作为默认实参
- eg:
void func(int i,int j=0/缺省参数/){}
靠右原则
- 如果函数的某个参数带有缺省值,那么该参数右侧的所有参数都必须带有缺省值
缺省参数只能在定义和声明之一写一次
- 但是绝大多数缺省参数是只写在声明部分,定义部分不写
- 定义部分写法:
void func(int a,int b,int c/=10/){}
函数的哑元参数
定义
- 在定义函数时,只有类型而没有变量名的形参被称为哑元
- void func(int /哑元/)
使用场景
-
eg:
在操作符重载函数中,区分前后++、-- -
eg:
为了兼容旧的代码- 标识该参数是无用的
内联函数(inline)
定义
- 使用inline关键字修饰的函数,即为内联函数,编译器将会尝试进行内联优化,可以避免函数调用开销,提高代码执行效率.
使用例子
- inline void func(void)
特点
- 空间换时间
适合场景
- 多次调用小而简单的函数
不适场景
-
调用次数极少或大而复杂的函数
-
递归函数不能内联优化
- 递归函数的递归次数在编译器的编译期间无法确定
-
虚函数
注
- 内联只是一种建议,而不是一种强制的语法要求,一个函数能否内联优化主要取决于编译器,有些函数不加inline修饰也会默认处理为内联优化,有些函数即便加了inline修饰也会被编译器忽略.
动态内存管理
回顾C语言的
动态内存管理
分配
- malloc(使用居多)
- calloc
- realloc
- brk
- sbrk
- mmap
释放
- free
C++动态内存管理
分配
- new
- new[]
释放
-
delete
1 #include
2 using namespace std;
3
4 int main(void){
5 int *p1;
6 //delete p1;//delete野指针,危险!!!
7
8 int *p2 = NULL;
9 delete p2;//delete空指针,安全,但是无意义
10
11 int *p3 = new int;
12 delete p3;
13 delete p3;//不能多次delete同一个地址,结果不确定
14
15 return 0;
16 } -
delete[]
只分配不释放
内存泄露
区别和联系
联系
- 功能差不多
区别
-
语法层面
-
malloc是函数
- 无初始化
-
new和delete是操作符
- 有初始化
-
-
对对象的操作
引用
(Reference)
定义
引用即别名,引用就是某个变量别名,对引用操作和对变量本身完全相同
语法
类型 & 引用名 = 变量名
eg:
int a = 10;
int & b = a;//b就是a的别名,等号是初始化
注
- 引用必须在定义同时初始化,而且在初始化以后所绑定的目标变量不能再做修改
- 引用的类型和绑定目标变量类型要一致
常引用
定义
- 定义引用时,可以多加const修饰,即为常引用,不能通过常引用修改目标变量(只读).
const 类型 & 引用名 = 变量名;
类型 const & 引用名 = 变量名;
- 两个方法等价
- int a =10;
const int &b = a;//b就是a的常引用
cout << b << endl;//10
b++;//error
普通引用也可称为左值引用,只能引用左值;而常引用也称为万能引用,既可以引用左值也可以引用右值.
引用型函数参数
可以通过形参直接修改实参的值
避免函数调用时传参的开销,提高代码效率
引用型参数有可能意外修改实参的值,如果不希望修改实参,可以将形参声明为常引用,提高效率的同时还可以接受常量型的实参.
引用型函数返回值
背景
- 函数的返回是返回的和return值相同的临时变量,内存会有开销
可以将函数的返回类型声明为引用,这时函数的返回结果就是return后面数据的别名,可以避免返回值带来的开销,提高代码执行效率.
如果函数的返回类型是左值引用,那么函数调用表达式的结果也将是左值
- int &func(void){
...
return num;
}
func() = 100;//OK
注:
不要从函数中返回局部变量的引用,因为所引用的内存会在函数返回后被释放,使用非常危险!可以在函数中返回成员变量、静态变量、全局变量的引用.
重难点
关于左值和右值
左值
-
可以放在赋值表达式左侧,可以被修改
- 如普通变量
-
也可以放右边
右值
-
只能放在赋值表达式右侧,不可以被修改
- 如字面值常量
左值和右值是对立的
练习
测试下面表达式结果,哪些是左值哪些是右值
- int a,b;
a+b;//右值
a+=b;//左值
++a;//左值
a++;//右值
总结关于引用和指针的区别和联系
-
从C语言的角度看待引用的本质,可以认为引用就是通过指针实现的,但是在C++开发中推荐使用引用,而不推荐指针.
-
int i = 100;
int* const pi = &i;
int& ri = i;- *pi <=等价=> ri
-
-
区别
-
指针可以不做初始化,其目标可以在初始化后随意改变(指针常量除外).
- int a = 10,b = 20;
int *p;//ok
p = &a;
p = &b;
- int a = 10,b = 20;
-
引用必须初始化,且一旦初始化,所引用的目标不能再改变
- int& r;//error
int& r = a;
r = b;//ok,但不是修改引用目标,仅是赋值运算.
- int& r;//error
-
可以定义指针的指针(二级指针),但不能定义引用的指针
-
int a =10;
int p = &a;
int *pp = &p;//二级指针 -
int& r =a;
int& *pr = &r;//error,引用的指针- 引用在C++没有自己独立的地址
-
int* pr = &r;//ok,仅是普通指针和int *p = &a;等价
-
-
可以定义指针的引用(指针变量别名),但是不能定义引用的引用
- int a = 100;
int* p = &a;
int* & rp = p;//ok,指针的引用 - int& r = a;
int& & rr = r;//error,引用用的引用
int& rr = r;//ok,但仅是一个普通引用
- int a = 100;
-
可以定义指针数组,但是不能定义引用数组
- int i = 10,j = 20,k = 30;
int* parr[3] = {i,j,k};//OK,指针数组
int& rarr[3] = {i,j,k};//error,不存在此写法
- int i = 10,j = 20,k = 30;
-
可以定义数组引用(数组别名)
- int i = 10,j = 20,k = 30;
int arr[3] = {i,j,k};
int (&rarr)[3] = arr;//ok
- int i = 10,j = 20,k = 30;
-
和函数指针类似,也可以定义函数引用(函数别名)
- void func(int i){...}
int main(void){
void (*pf)(int) = func;//函数指针
void (&rf)(int) = func;//函数引用
pf(100);
rf(100);
}
- void func(int i){...}
-
类型转换
背景
a = b;
- 保证a和b数据类型相同
隐式类型转换
场景
- char c = 'A';
int i = c;//隐式 - void func(int i){}
func(c);//隐式 - int func(void){
char c = 'A';
return c;//隐式
}
显式类型转换
背景
- cout << &data << endl;//1 ...
关于强制类型转换,兼容C强制类型转换
- char c = 'A';
int i = (int)c;//C风格
int i = int(c);//C++风格
C++扩展了四种操作符形式显式转换
-
静态类型转换
-
static_cast
-
语法
- 目标变量 = static_cast<目标类型>(源类型变量)
-
场景
- 将void*转换为其它类型的指针
-
-
动态类型转换
-
dynamic_cast//后面提
-
语法
- 目标变量 = dynamic_cast<目标类型>(源类型变量)
-
-
去常类型转换
-
const_cast
-
语法
- 目标变量 = const_cast<目标类型>(源类型变量)
-
场景
- 去掉指针或引用const属性
- int i = 0;//栈区可读可写
const int i = 0;//栈区可读可写
-
-
重解释类型转换
-
reinterpret_cast
-
语法
- 目标变量 = reinterpret_cast<目标类型>(源类型变量)
-
场景
-
指针和整型数进行显式转换.
-
eg:
已知物理内存地址0x12345678,向该地址存放一个整型数100?- int *paddr = 0x12345678;
*paddr = 100;//error - int paddr = reinterprect_cast<int>(0x12345678);
*paddr = 100;
- int *paddr = 0x12345678;
-
-
-
volatile
是标准C语言的关键字
被此修饰的变量是易变的,告诉编译器每次使用该变量时,都要小心从内存中读取,而不是取寄存器的副本,防止编译器优化引发的错误结果.
来自C++
社区建议
慎用宏
可以使用const、enum、inline替换
弊端
-
都是全局的
-
替换例子
-
define PAI 3.14
-
==>
- const double PAI = 3.14;
-
-
define SLEEP 0
define RUN 1
define STOP 2
-
==>
- enum STATE{SLEEP,RUN,STOP};
-
-
define Max(a,b) ((a)>(b)?(a):(b))
- inline int Max(int a,int b){
return a>b?a:b;
}
- inline int Max(int a,int b){
-
-