前言
《C++ Primer Pluse(第6版)中文版》(后文简称CPPPP)是一部经典的C++入门书籍,作为入门书籍给我的感觉却是劝退,所以我也建议读者在读CPPPP前了解C语言或C++,他的优点也是他的缺点——讲解过细过深,有写地方深入但没有讲透彻让读者晕头转向,在加上翻译问题更是让很多人读不下去,这也是我写这篇关键汇总的原因,通过提取每节的关键句,再加上我自己对C++的理解,帮助读者搞明白它在讲什么,但由于我自身能力有限仅利用周末等空闲时间编写,质量并不高但构建出了大体框架,文中或许会存在一定问题,读者可以向我反馈,以及不懂的地方也可以来询问我,在之后的改版中我也知道哪里需要进一步细化。请读者注意学完此书也只代表读者称为一名C++入门程序员,因为此书主要包含C++11前(包含一些C++11)的内容,然而C++在C++11后又更新了三版现在最新的大版本是C++20,此文中代码实现为Windows 11、Visual Studio 2022 。
第一章 预备知识
1.1 C++简介
C++融合三种不同编程方式:过程性语言(c语言为代表),面向对象语言(OOP),泛型编程。
1.2 C++简史
1.2.1 C语言
汇编语言是依赖计算机的内部机器语言,是低级语言即直接操作硬件,因此汇编语言无法通用。需要高级语言能够不针对特定硬件,翻译器将高级语言转化为特定计算机的内部语言,在旧语言的基础上,将低级语言的效率,硬件访问能力和高级语言的通用性,可移植性融合在一起创造出C语言。
1.2.2 C语言编程原理
c语言过程性语言,结构化编程,自上而下原则。
1.2.3 面向对象编程
OOP强调数据,特征包括封装继承多态,设计与问题的本质相对应的数据格式,是一个管理大型项目的工具。
1.2.4 C++与泛型编程
泛型编程强调独立于特定数据结构,提供执行任务常见的工具。
1.3 可移植性和标准
1990年美国国家标准局(ANSI)与国际标准化组织 (ISO)创立联合组织ANSI/ISO,致力于制定C++标准。
1998年制定出(ISO/IEC14882:1998)C++98,不仅描述了已有C++特性,还添加了异常、运行阶段类型识别(RTTI)、模板和标准模板库 (STL)。
2003年制定出(ISO/IEC 14882:2003)C++03,是对C++98的一次修订,没有改变语言特性。因此C++98表示C++98/C++2003。
2011年指定出(ISO/IEC 14882:2011)C++11,新增众多特性,消除不一致性。
第二章 初始C++
2.1 进入C++
#include<iostream>
using namespace std;
int main()
{
cout<<"Hello World!"<<endl;
cin.get();
return 0;
}
2.1.1 main()函数
main函数是程序入口,通常从main函数开始执行(在一些情况下可以不需要main函数)
C++语法要求main()函数的定义以int main()开始,该函数头描述的是main()和操作系统之间的接口。
main函数可以省略返回语句,默认添加return 0(只适用与main函数)。
2.1.2 C++注释
C++注释风格: // (尽量使用此风格)
C语言注释风格: /* */
2.1.3 C++预处理器和iostream文件
预处理器:在进行主编译前对源文件进行处理(#预处理器指令)
**#**include:将头文件的内容添加到程序中。
iostream: 文件中包含输入输出流的基本定义。
事实上,iostream文件内容将取代#include。原始文件没有改变,而是组合成复合文件,在下一阶段使用
2.1.4 头文件名
像iostream这样文件叫包含文件——它们包含在其他文件中;也叫头文件——它们被包含在文件头。
C语言头文件使用扩展名h。
C++对于老式C的头文件做了保留,C++头文件没有扩展名,C转换为C++时会将h去掉并添加前缀c,对于纯粹的C++头文件,没有h不 只是形式上的变化,没有h也可以包含名称空间。
[外链图片转存中…(img-rcE8Jdj8-1734614181740)]
2.1.5 名称空间
若没有后缀h,应使用名称空间编译指令使头文件中的定义对程序可用
using namespace std //using编译指令
名称空间支持是C++的特性,编写大型程序以及将多个厂商现有代码组合时更容易,有助于组织程序。(使用名称空间区分重命名)
更好的方法是,只使所需的名称。
using std::cout;
using std::endl;
2.1.6 使用cout进行C++输出
输出是一个流,即从程序流出一系列字符。cout对象表示这个流,其属性是在iostream中定义的。
cout属性包含插入运算符(<<)将右侧信息插入流中。
1. 控制符endl
endl是特殊的C++的符号,表示:重起一行。(对于cout有特殊意义的符号被称为控制符)
2.换行符
C++提供旧式方法:C语言的\n。
\n是一个字符,名为换行符。
显示用引号括起来的字符串时,通常使用\n,其他情况使用控制符endl。
endl与\n差别:endl确保程序继续运行前刷新输出(将其立即显示在屏幕上);\n不能提供这样的保障。
2.1.7 C++源代码的格式化
1. 源代码中的标记和空白
标记(token):不可分割的元素。必须用空白将两个标记分开
空白(white space):空格、制表符、回车。
2. C++源代码风格
每行语句占一行
每个函数都有一个开始和结束花括号,花括号各占一行
函数中的语句都相对于花括号缩进
与函数名称相关的圆括号没有空白(帮助区分函数和一些也使用圆括号的C++内置结构)
2.2 C++语句
C++程序是一组函数,每个函数是一组语句。
#include<iostream>
using namespace std;
int main()
{
int a=10;
cout<<"a="<<a<<endl;
a=a-1;
cout<<"a="<<a<<endl;
}
2.2.1 声明语句和变量
声明语句:提供两个信息:需要的内存空间以及该内存单元的名称。
声明语句包含:定义声明(定义):编译器为变量分配空间;
引用声明(引用):命令计算机使用其他地方定义的变量。
变量:给一段内存空间起名方便操作这段内存,C++中变量必须声明(避免创造不需要的变量,尽量在首次使用前声明)。
初始化:再声明变量时便给变量赋值。
2.2.2 赋值语句
"="称为赋值运算符。C++可连续使用赋值运算符。
a=b=c=10;
赋值将从右向左进行。
2.2.3 cout的新花样
cout可以输出变量,cout在输出整数时先将其转化为字符再进行输出。
2.3 其他C++语句
#include<iostream>
using namespace std;
int main()
{
int a;
cin>>a;
cout<<"a="<<a<<endl;
}
2.3.1 使用cin
输入看作流入程序的字符串,>>运算符从输入流中抽取字符。
2.3.2 使用cout拼接
<<可以连续使用
2.3.3 类介绍
类是用户定义的一种数据类型
2.4 函数
C++函数分为:有返回值和没有返回值(无返回值用void)。
函数调用:函数名(参数列表);。被调用的函数称为被调用函数,包含函数调用的函数称为调用函数
函数原型:返回值类型 函数名(参数列表); 应为程序中使用的每个函数提供原型
函数格式:C++不允许将函数定义嵌套在另一个函数定义中,不对函数内部变量初始化,变量值将是被创建之前,相应内存单元保留 的值
返回值类型 函数名(参数列表)
{
函数体
return 表达式
}
第三章 处理数据
3.1 简单变量
3.1.1 变量名
C++提倡使用有一定含义的变量名。
命名规则:
—只能使用字母、数字、下划线;
—名称的第一个字符不能是数字;
—区分大小写;
—不能使用关键字;
—以一个、两个下划线或下划线和大写字母打头的名称保留给实现(编译器及其使用的资源)使用;
—C++对于名称长度没有限制,但有平台会对长度进行限制。
3.1.2 整型
整数就是没有小数点的数字。
不同C++整型使用不同的内存量存储整数(术语宽度,用于描述存储整数时使用的内存量)
3.1.3 整型 short、int、long、long long
计算机内存由位(bit)的单元组成
—short至少16位;
—int至少与short一样长;
—long至少32位,且至少与int一样长;
—long long至少64位,且至少与long一样长。
位与字节
字节(byte)通常是8位的内存单元。字节就是描述计算机内存量的度量单位。
C++中字节由至少能够容纳实现的基本字符集的相邻位组成,即可能取值的数目必须等于或超过字符数目。
例如:ASCII和EBCDIC字符集,可用8位存储,所以使用这两种字符集的系统中,C++字节通常为8位
国际编程需要使用更大的字符集,如Unicode,因此有些实现会使用16位,32位
计算数据类型长度
sizeof运算符
语法:sizeof(数据类型/变量名);
返回类型或变量长度,单位是字节。
头文件climits
文件中定义了符号常量
初始化方式
大括号初始化器(有助于更好的防范类型转换错误)
int a{7};
int b={7}; //可加=
int c={}; //初始为0
3.1.4 无符号类型
不能存储负数值,增大变量能够存储的最大值。
在整型前加unsigned关键字修饰声明。
注:C++确保了无符号类型的这种行为,但C++不保证符号整型超越限制(上溢或下溢)时不出错
3.1.5 选择整型类型
自然长度int:计算机处理起来效率最高。
大于16位整数时使用long,存储值超过20亿时使用long long。
通常仅在有大型整型数组时使用short
若只需1字节则使用char
3.1.6 整型字面值
整型字面值(常量)是显式地书写的常量。
三种计数方式书写整型:基数为10,基数为8,基数为16。
若第一位为1-9,则基数为10;
若第一位为0,第二位为1-7,则基数为8;
若前两位为0x或0X,则基数为16。
默认情况下cout以十进制显示整数,可使用控制符dex、hex、oct指示cout输出十进制,十六进制和八进制。
3.1.7 C++如何确定常量字面值的类型
默认存储为int(除非有理由存储其他类型)
添加后缀(大小写都可):
— L表示 long
— U表示 unsigned int
— UL 表示unsigned long
— LL 表示 long long
— ULL 表示 unsigned long long
3.1.8 char类型:字符和小整数
专为存储字符(字母和数字)而设计。编程语言通过使用字母的数值编码(字符集)解决存储字符问题,C++实现使用的是其主机系统 的编码,C++支持的宽体字符类型可以存储更多的值。
C++对字符用单引号,对字符串用双引号。
**输入输出:**输入时将字符转换为数值编码,输出时将字符编码转换为字符。
cout.put()成员函数
用于显示一个字符。
"."句点被称为成员运算符。C++Release2.0前cout将字符变量显示为字符,将字符常量(‘M’,‘N’)显示为数字(存储 在int中),所以使用cout.put(‘M’)打印单个字符。
char字面值
书写字符常量的方式有很多,最简单的方法时用单引号括起例如 ‘ A ’。
转义序列:
不能直接通过键盘输入到程序中。
可以基于字符的八进制和十六进制编码使用转义序列。
通用字符名
C++支持一个基本的源字符集,即可用来编写源代码的字符集,C++标准还允许实现提供扩展源字符集和扩展执行字符集。
那些被作为字母的额外字符也可用于标识符名称中,使用通用字符名表示这种特殊字符机制。
用法:类似与转义序列,可以以\u或\U打头。\u后面是4个十六进制位,\U后面则是8个十六进制位。
Unicode和ISO 10646
Unicode提供了一种表示各种字符集的解决方案——为大量字符和符号提供标准数值编码,并根据类型进行分组ASCII码为 Unicode的子集。
Unicode给每个字符指定一个编号——码点。
ISO 10646——对多种语言文本进行编码的标准。IOS 10646与Unicode进行合作确保标准同步。
signed char和unsigned char
unsigned char表示范围通常为0~255;
signed char表示范围通常为-128~127;
wchar_t
wchar_t(宽字符类型)可以表示扩展字符集,属于整数类型,前缀L
cin与cout可看作char流,类似的用wcin和wcout,处理wchar_t流。
char16_t和char32_t
char16_t:无符号,长16位,前缀u
char32_t:无符号,长32位,前缀U
3.1.9 bool类型
布尔变量值可以是true和false。
字面值true和false可以通过提升转换为int类型,true为1,false为0。
任何数字值和指针值都可以被隐式转换(即不用显示强制转换)为bool值。任何非零都被转为true,而零转为false。
3.2 const限定符
语法:const+变量;
const与#define的比较:const能够明确指定类型,可以使用C++的作用域规则将定义限制在特定函数或文件中。
3.3 浮点数
第二组基本类型,表示带小数部分的数字。
分为两部分存储:一部分基准值,另一部分表示缩放因子
例:34.1245 保存为0.341245(基准值)与100(基准值)。
3.3.1书写浮点数
两种书写方式:
第一种:标准小数点表示法 例:12.34;
第二种:E表示法 例:3.45E6
E表示法最适合与非常大或非常小的数。确保了数字以浮点格式存储,即使没有小数点
3.3.2 浮点类型
三个类型:float,double,long double
区别:有效位与允许的指数最小范围
占用内存:float至少32位;
double至少48位,且不少于float;
long double 至少和double一样多。
通常float为32位,double为64位,long double位80、96或128位
指数范围:至少位-37~37
可以从头文件cfloat中查看
cout.setf(ios_base::fixed,ios_base::floatfield)输出使用定点表示法,防止程序把较大值转为E表示法
3.3.3 浮点常量
默认情况下浮点常量属于double
后缀: F为float L为long double
3.3.4浮点数的优缺点
- 优点:
- 可以表示整数间的值
- 表示的范围很大
- 缺点:
- 运算速度比整数运算慢
- 精度降低
类型分类
- 算数类型
- 整型
- 符号整型
- 无符号整型
- 浮点型
- 整型
3.4 C++算数运算符
- +,加法运算 例:4+20 结果为 24
- -, 减法运算 例:12-3 结果为 9
- ,乘法运算 例:420 结果为 80
- /,除法运算 例:1000/5 结果为 200
- 若操作数都为整数,结果为商的整数部分
- %,求模运算 例:19%6 结果为 1
- 两个操作数必须为整型
- 结果符号与被除数相同
3.4.1 运算符优先级和结合性
通常遵循代数优先级,C++实现决定两个同优先度的谁先运算
3.4.2 除法分支
除法运算符的行为取决于操作数的类型。都为整数则执行整数除法,若有一个为浮点型,则执行浮点除法
3.4.3 求模运算符
返回整数除法的余数
3.4.4 类型转换
- 将一种算术类型的值赋给另一种算数类型的变量时,C++将对值进行转换
- 表达式种不包含不同的类型时,C++将对值进行转换
- 将参数传递给函数时,C++将对值进行转换
1、初始化和赋值进行的转化
C++允许将一种类型的值赋给另一种类型的变量
将一个值赋给取值范围更大的类型通常没有问题
2、以{}方式初始化时进行转换
C++11将使用大括号的初始化称为列表初始化,列表初始化不允许缩窄,即变量的类型可能无法表示赋给它的值。
3、表达式中的转换
C++执行两种自动类型转换:首先一些类型出现时就会发生自动转换;其次,有些类型和其他类型同时出现在表达式中将被自动转换
C++将bool、char、unsigned char、signed char、short值转换为int(整型提升)
较小类型会转为较大类型
4、传递参数时的转换
由函数原型控制
5、强制类型转换
强制类型转换并不是修改本身,而是创造出一个新的,指定类型的值
格式:
(数据类型)变量/值; C语言风格
数据类型(变量/值); C++风格
C++引入了4个强制类型转换运算符,例:static_cast<数据类型>(变量/值)将值从一种数值类型转换为另一种数值类型
3.3.5 C++中auto声明
让编译器根据初始值的类型推断变量的类型。
第四章 复合类型
4.1 数组
数组是一种数据结构,能够储存多个同类型的值
声明:数据类型 数组名[数组长度]。
数组中每个元素都可看成一个简单变量
数组长度必须明确,可以是整型常量或const值,也可以时常量表达式,即其中所有值在编译时都是已知的。变量值是在程序运行时设 置的。
C++数组从0开始编号。
编译器不会检查使用的下标是否有效。若使用无效下标会引发很多问题。
4.1.1 数组的初始化
只有定义数组时才能使用初始化,不能将一个数组赋值给另一个数组
数据类型 数组名[数组长度]={值1,值2…};
可以使用下标给数组元素赋值
初始化数组时提供的值可以小于数组长度,剩余的会设置为0
4.1.2 C++11数组初始化方式
大括号的初始化
数据类型 数组名 [数组长度]{值1,值2…};
括号中不包含任何值将把所有元素置为0;
禁止缩窄转换
4.2 字符串
字符串是储存在内存的连续字节中的一系列字符。
C风格字符串以空字符结尾,\0,其ASCII码为0。(若没有\0,cout时将继续打印下一个字节中的内容)
声明: char 字符串名[]={‘X’,‘X’,‘X’,‘X’,‘\0’};
字符串常量(字符串字面值)
char 字符串名[]=“字符串值”;(隐式地包含了空字符)
4.2.1 拼接字符串常量
C++允许拼接字符串字面值
cout<<"xxxxxx""xxxxxx"<<endl;
4.2.2 在数组中使用字符串
数组初始化为字符串字面值、将键盘或文件输入读入数组中。
使用 cstring 中的 strlen() 确定字符串的长度
4.2.3 字符串输入
cin使用空白来确定字符串的结束位置(读取一个单词)。
cin将字符串放到数组中,自动在结尾添加空字符。
4.2.4 每次读取一行字符串输入
istream中提供: getline() 和 get() 函数,读取一行输入,直到遇到换行符。
区别:getline()会丢弃换行符,get() 将保留换行符在输入序列中;
1、getline()
提供两个参数,第一个用来存储输入行的数组名,第二个要读取的字符数(空字符占一个字符数)。
2、get()
与getline差不多,但输入会更仔细。
可以通过get()检测停止读取的原因。
3、空行和其他问题
get()读取空行后设置失效位。接下来的输入被阻断,可以用cin.clear()恢复。
输入字符串比分配内存长,会将余下的字符保留在输入队列中,getline()还会设置失效位,关闭后面的输入。
4.2.5 混合输入字符串和数字
int year;
cin>>year;
char address[80];
cin.getline(address,80);
cout<<year;
cout<<getline;
输入时会将回车当成换行符保留在输入流中,cin.getline()看到换行符会认为输入完毕。
增加一个cin.get()来丢弃换行符。
4.3 string类简介
加头文件string
string使用对象方式与使用字符数组相同
- 可以使用C风格字符串来初始化string对象
- 可以使用cin将键盘输入存储到string对象
- 可以使用cout来显示string对象
- 可以使用数组表示法来访问存储在string中的字符
4.3.1 C++11字符串初始化
可以使用列表初始化
string name={“TOM”};
string name{“TOM”};
4.3.2 赋值、拼接和附加
可以将string对象赋值给另一个对象。
使用+将两个string对象合并。
使用+=将字符串附加到string对象的末尾;
4.3.3 C风格字符串的其他操作
头文件cstring中,strcpy()函数,将字符串复制到字符数组中,strcat()函数,将字符串附加到字符数组末尾。
为了防止超出数组长度,提供strncpy()函数,strncat()函数,他们接受指出目标数组最大允许长度的第三个参数
使用 strlen() 函数获取长度。
4.3.4 string类I/O
提供getline ()函数解决输入问题。
4.3.5其他形式的字符串字面值
支持Unicode字符编码方案UTF-8,前缀u8来表示。
原始字符串raw,用“(和)”用作界定符,并使用前缀R来表示原始字符串,输入原始字符串时,按回车不仅会移到下一行,还会在字符串中添加回车字符。
自定义定界符,在默认定界符间添加任意数量的基本字符,但空格,括号,斜杠,控制字符(制表符,换行符)除外。
可以前缀R和其他字符串前缀一同使用。
4.4 结构简介
C++允许在声明结构变量时省略struct关键字
4.4.1 程序中使用结构体
将结构体声明放到main()函数前(外部声明),也可放到main()函数中。
外部声明可以被其他函数使用,内部声明只能被该声明所属的函数使用。
C++不提倡使用外部变量,但提倡使用外部结构声明,在外部声明符号常量通常更合理。
4.4.2 C++11结构初始化
列表初始化,大括号内未包含任何东西,各个成员都将设置为零。
不允许窄缩转换。
4.4.3 其他结构属性
C++使用户定义的类型与内置类型尽可能相似。
可以将两个相同结构使用赋值符,尽管成员为数组(成员赋值)。
C++除了成员变量外也可以有成员函数。
4.4.5 结构数组
可以创建元素结构的数组。
4.4.6 结构中的位字段
C++允许指定占用特定位数的结构成员(位字段),使得创建于某个硬件设备上的寄存器对应的数据结构非常方便。
字段类型应为整型或枚举,接下来时冒号,冒号后面是一个数字,它指定了使用的位数。
可以使用没有名称的字段来提供间距。
struct torgle_register // torgle_register称为结构
{
unsigned int SN: 4;
unsigned int : 4;
bool goodIn : 1;
bool goodTorgle : 1;
};
一般使用按位运算符来替代
4.5 公用体
共用体(union)是一种数据格式,它能够储存不同的数据类型,但只能同时存储其中一个类型。
共用体长度为其最大成员的长度。
用途:当数据项使用两种或更多的格式(不会同时使用),可以节省空间。
匿名共用体没有名称直接使用成员变量名。
union one4all
{
int int_val;
long long_val;
double double_val;
};
4.6 枚举
enum(enumeration)创建符号常量,可以替代const。可以定义新类型
//spectrum称为枚举也是新类型的名称,red等称为符号常量,对应整数值为1~7。这些常量称为枚举量
enum spectrum{red,orange,yellow,green,blue,violet,indigo,ultraviolet};
在默认情况下,将整数值赋给枚举量(从零开始,每一项比前一项大1),可以通过显示地指定整数值来覆盖默认值
创建变量:枚举名 变量名;
不进行强制类型转换的话,只能将定义枚举时使用的枚举量赋值给枚举的变量
spectrum band;//创建枚举变量
band=blue; //正确 枚举只有赋值运算
band=200; //错误,200不是枚举量
枚举量是整型可以被提升为int类型,但int不能自动转换为枚举类型
int color=blue;
band=3; //错误
color=3+band;
枚举通常被用来定义相关的符号常量。
4.6.1 设置枚举量的值
可以使用赋值运算符来显示地设置枚举量。
可以创建多个值相同的枚举量。
enum bigstep{first,second=100,third}; //third为101
enum {zero,null=0,one,numero_uno=1}; //0,0,1,1
4.6.2 枚举的取值范围
每个枚举都有取值范围,通过强制类型转化,将取值范围中的任何整数值赋给枚举变量,即使它不是枚举值。
取值上限:找到大于最大枚举量的最小2的幂,减去1。
取值下限:若最小枚举量大于零,则下线为0;若小于零,采用寻找上限的方式,加上负号。
4.7 指针和自由存储空间
指针是一个变量,存储的是值的地址(显示时通常为十六进制,通常占用8字节)。
对变量应用地址运算符(&),就可以获得他的位置。
使用常规变量时,值是指定量,而地址时派生量。
处理存储数据时将地址视为指定的量,而值是为派生量。
*运算符称为间接值或解除引用运算符,将其运用在指针会得当该地址的存储值。
指针与C++基本原理
OOP强调在运行阶段(而不是编译阶段)进行决策。
运行阶段:程序运行时 编译阶段:编译器将程序组合起来
运行阶段决策更加灵活,节省空间。
4.7.1 声明和初始化指针
计算机会跟踪指针指向的值的类型,不同类型存储时使用的内部结构也不同。
地址本身只指出对象存储地址的开始,并没有指出使用的字节数(数据类型)
声明指针:
int* p;//C++风格 强调int*是一种类型——指向int的指针
int *p;//C风格 强调*p是int类型
int* p1,p2; //表示创建一个指针(p1),和一个变量(p2)
地址的长度或值既不能指示关于变量的长度或类型的任何信息,也不能指示该地址上的内容。
4.7.2 指针的危险
创建指针时,计算机将分配用来存储地址的内存,但不会分配用来存储指针所指向的数据的内存。
一定要在解引用前将指针初始化为一个确定的,适当的地址。
4.7.3 指针和数字
指针不是整型,将指针相乘没有意义。应使用强制类型转换将整型转为适当的指针类型。
但通常不会这么操作很容易造成野指针(不是计算机分配的地址)
int* p;
p=(int*)0xB8000000;
4.7.4 使用new来分配内存
变量是在编译时分配的有名称的内存,而指针只是为可以通过名称直接访问的内存提供了一个别名。
指针用途:在运行阶段分配未命名的内存以存储值。
new运算符根据类型来确定需要多少字节的内存,然后找到这样的内存,并返回其地址。
int* pn=new int;//只能通过指针访问
int higgens;
int* pt=&higgens;//可以通过higgens来访问int
pn指向一个数据对象(数据对象比变量更通用,它指的是为数据项分配的内存块 )
内存耗尽问题:new通常会引起异常,C++中值为0的指针称为空指针(和野指针一样不可访问),空指针不会指向有效的数据,常 被用来表示运算符或函数失败(若成功返回一个有用指针时),C++提供了检测并处理内存分配失败的工具。
4.7.5 使用delete释放内存
需要内存时用new请求,归还内存时用delete释放。
- 不要使用delete释放不是new分配的内存
- 不要使用delete释放同一个内存块两次
- 如果使用new[]为数组分配内存,则使用delete[]释放
- 如果使用new为一个实体分配内存,则使用delete(没有中括号 )来释放
- 对空指针使用delete是安全的
int* p=new int;
delete p;
释放p的内存,不会删除p本身,p可以重新指向一个内存。
一定要配对使用new和delete,否则会发生内存泄露,既分配的内存无法使用。
4.7.6 使用new来创建动态数组
在编译阶段给数组分配内存称为静态联编,数组在编译时加入到程序中。
运行阶段选择数组长度称为动态联编,数组在程序运行时创建。
//数组实现
class Array
{
public:
//数组指针
int* m_pArr;
//数组大小
int m_Size;
//数组容量
int m_Capacity;
//构造函数
Array(int cap = 10):m_Size(0),m_Capacity(cap)
{
m_pArr = new int[cap]();
}
//析构函数
~Array()
{
delete[]m_pArr;
m_pArr = NULL;
}
//末尾插入
void push_back(int val)
{
if (m_Size == m_Capacity)
{
expand(2 * m_Capacity);
}
m_pArr[m_Size++] = val; //指针当作数组名使用
}
//末尾删除
void pop_back()
{
if (m_Size == 0)
{
return;
}
m_Size--;
}
//指定位置插入
void insert(int pos, int val)
{
if (pos<0 || pos>m_Size)
{
return;
}
if (m_Size == m_Capacity)
{
expand(2 * m_Capacity);
}
for (int i = m_Size - 1; i >= pos; i--)
{
m_pArr[i+1] = m_pArr[i];
}
m_pArr[pos] = val;
m_Size++;
}
//元素查询
int find(int val)
{
for (int i = 0; i < m_Size; i++)
{
if (m_pArr[i] == val)
{
return i;
}
}
return -1;
}
//按位置删除
void erase(int pos)
{
if (pos < 0 || pos >= m_Size)
{
return;
}
for (int i = pos; i <= m_Size-1; i++)
{
m_pArr[i] = m_pArr[i + 1];
}
m_Size--;
}
//打印
void show()const
{
for (int i = 0; i < m_Size; i++)
{
cout << m_pArr[i] << " ";
}
cout << endl;
}
private:
//扩容
void expand(int a)
{
int* p = new int[a]();
memcpy(p, m_pArr, sizeof(int) * m_Capacity);
delete[]m_pArr;
m_pArr = p;
m_Capacity = a;
}
};
指针当成数组名使用,C++内部都使用指针来处理数组。
注:上述实现了一个动态数组,使用的是面向对象技术,读者只要知道new和delete实现动态数组便可。
4.8 指针、数组和指针算术
指针和数组基本等价原因在于指针算术和C++内部处理数组的方式。
使用数组表达式: array[i] , C++解释为*(array+i)。
使用指针: pointer[i] ,C++解释为 *(pointer+i)。
对数组使用sizeof得到数组长度,对指针使用得到指针长度。
数组地址:
short tell[10];
cout<<tell<<endl;
cout<<&tell<<endl;
两者结果相同,但概念上&tell[0](即tell)是一个2字节的内存块地址,而&tell是一个20字节内存块的地址。
short(*pas)[10]=&tell; pas变量类型为short(*****)[10]
4.8.1 指针小结
-
声明指针
数据类型* 指针名;
-
给指针赋值
指针名=&变量名;
-
对指针解除引用
*数据类型;
-
区分指针和指针所指向的值
pt是指向int的指针,*pt完全等同于一个int类型的变量
int*pt=new int;
-
数组名
数组名视为数组的第一个元素的地址
-
指针算术
允许指针和整数相加,加1结果为原本的地址值加上指向的对象占用的总字节数。
-
数组的静态联编和动态联编
数组声明创建数组时是静态,用new[]是动态
-
数组表示法和指针表示法
使用中括号数组表示法等同于对指针解引用
4.8.2 指针和字符串
cout认为char的地址是字符串的地址,因此打印改地址处的字符,然后继续打印后面的字符,直到遇到空字符。
用引号括起来的字符串像数组名一样,也是第一个元素的地址。
头文件cstring中函数**strcpy()**将字符串从一个位置复制到另一个位置,接受两个参数,一个是目标地址,另一个是要复制的地址。
为了防止字符串复制时目标空间不足,可以使用strncpy()
C++不保证字符串字面值被唯一储存。
给cout一个地址它将打印这段地址,如果要显示字符串地址要使用强转。
4.8.3 使用new创建动态结构
使用箭头成员运算符(->)来访问对象。
new和delete的妙用
可以节省内存
char* getname()
{
char temp[80];
cint>>temp;
char*p=new char[strlen(temp)+1];
strcpy(p,temp);
return p //在main函数中释放p
}
4.8.5 自动存储、静态存储和动态存储
C++有四种内存管理方式:自动存储、静态存储、动态存储和线程存储(C++11新增,在9章介绍)
1、自动存储
在函数内部定义的常规变量使用自动内存空间,被称为自动变量,他们在所属函数被调用时存在,在函数结束时释放。
自动变量是一个局部变量,其作用域为包括它的代码块,仅在程序执行该代码块时存在。
通常存储在栈上,变量依次存入栈中,释放时按相反顺序释放这些变量(先进后出)。
2、静态存储
整个程序执行期间都存在。
定义方式:一种在函数外面定义一个变量;另一种在声明变量时加static。
3、动态存储
new和delete实现了动态存储。他们管理一个内存池(自由存储空间或堆)。
内存池同用于静态变量和自动变量的内存是分开的。
可以在一个函数中分配内存,在另一个函数中释放。
4、栈、堆和内存泄漏
没有调用delete,则即使包含指针的内存由于作用域规则和对象生命周期的原因而被释放,在自由存储空间上动态分配的变量或结构将继续存在,将无法访问自由存储空间中的变量或结构,因为指向这些内存的指针无效,导致内存泄漏。
4.9 类型组合
struct Year
{
int year;
}
//可以这么创建类型变量
Year y01,y02,y03;
//使用成员运算符访问成员
y01.year=1998;
//创建结构指针
Year* py=&y02;
//将地址设置为有效地址后,可以通过箭头成员运算符访问成员
py->year=1999;
//可以创建结构数组
Year trio[3];
trio[0].year=2003; //使用成员运算符访问成员
//由于数组名是一个指针所以可以这么访问成员
(trio+1)->year=2004;
//可以创建指针数组
const Year* arp[3]={&y01,&y02,&y03};
//arp是一个指针数组,arp[0]就是一个指针
cout<<arp[0]->year<<endl;
//可以创建指向上述数组的指针
const Year** ppy=arp;
//可以使用auto正确判断ppy的类型
auto ppy=arp;
//ppy是一个只想结构指针的指针,所以*ppy是一个结构指针
cout<<(*ppy)->year<<endl;
cout<<(*ppy+1)->year<<endl;
4.10 数组替代品
4.10.1 模板类vector
vector属于泛型编程中的容器,它使用new和delete来管理内存,可以替代自己创建的动态数组,但效率较低。
注:建议读者构建自己的动态数组,了解动态数组构建的底层逻辑,从而更方便的使用 vector容器。
4.10.2 模板类array
如果使用的固定长度的数组,推荐array容器,对象长度固定,也使用栈,因此与数组效率相同,但比数组更方便和安全。
声明:array<数据类型,长度>对象名;
4.10.3 比较数组、vector对象 和array对象
-
相同点
- 都可以使用标准数组表示法访问各个元素。
- 都不会检查超越错误。
-
不同点
- array和数组存储在栈上,vector存储在自由存储区(堆)。
- 可以将array对象可以赋值给另一个array对象。
vector与aarray提供函数at(),在运行期间捕获非法引索,程序允许时间也将加长。
第五章 循环和关系表达式
5.1 for循环
5.1.1 for循环的组成部分
for循环组成部分完成下面这些操作:
1、设置初始值;
2、执行测试,判断循环是否继续进行;
3、执行循环操作;
4、更新用于测试的值。
初始化,测试和更新操作构成了控制部分,用小括号括起,每一个都是表达式,用分号隔开。
控制部分后面为循环体,只要测试结果为真便会执行。
for (起始表达式<初始化>;条件表达式<测试>;末尾循环体<更新操作>)
循环体;
C++将整个for循环看成一个语句——虽然循环体包含一条或多条语句(包含多条语句时,需要使用复合语句或代码块)
循环只会执行一次初始化,C++将会把条件结构强制转化为bool值
for循环是入口条件循环,每次循环前都将计算测试表达式的值,表达式为false时不执行循环体。
为了避免将for循环看成函数调用,习惯上会在for和()间加一个空格。
1、表达式和语句
任何值或任何有效的值和运算符组合都是表达式。
cout.setf(ios::boolalpha)函数调用设置了一个标记,该标记命令cout显示true和false
cout.setf(ios::boolalpha);
cout << (1 == 1) << endl; //输出true
表达式:a+100
语句:a+100;(尽管它没有任何效果但他的确是语句,有些编译器甚至不会执行它)
2、非表达式和语句
语句中删除分号并不一定时表达式(例如 int a; 是一个语句 但 int a不是一个表达式)。
3、修改规则
之前说for循环由三个表达式构成,C++增加了一个特性可与再for循环中声明变量。
起初这一非法操作通过定义一个新的表达式——声明语句表达式来合法化,声明语句表达式不带分号,只能在for循环中使用
现在将for循环语句的句法进行更改
for(for-init-statement condition;expression)
statement
for-init-statement被视为一个语句,语句有自己的分号。
for-init-statement声明变量,变量将只存在与for循环中,离开循环该变量将无法被使用。
5.1.2 递增(++)递减(–)运算符
前置递增:先计算表达式,再变量加1
后置递增:先变量加1,再计算表达式
注:同一个语句中对一个值进行多次递增递减,结果取决于实现
5.1.3 副作用和顺序点
副作用:在计算表达式时对某个东西(如储存在变量中的值)进行了修改
顺序点:程序执行过程中的一个点,在进行下一步之前确保对所有的副作用都进行了评估。
分号、任何完整表达式末尾都是一个顺序点。
完整表达式:不是另一个更大表达式的子表达式
(C++没有规定是在计算每个子表达式时还是计算完整表达式前或后进行变量改变)
5.1.4 前缀格式或后缀格式
在使用类时进行运算符重载前缀和后缀将会存在差异。
对于内置类型而言不会有差别,但对于自定义类型而言前缀格式效率更高
//重载前置递增运算符
MyInteger& operator++()
{
m_myint ++;
return *this;
}
//重载后置递增运算符
//int占位参数,可以用于区分前置和后置
MyInteger operator++(int)
{
MyInteger temp =*this;
m_myint++;
return temp;
}
5.1.5 递增/递减运算符和指针
优先度:前缀=解引用<后缀(相同情况下从右向左进行)
int arr[5]={1,2,3,4,5}
int* p=arr; //x=arr[0]
p++; //p=&arr[1]
x=*++p; //x=arr[2]
x=++*p; //x=++3=4
x=(*p)++; //x=3++=4
x=*p++; //x=4
5.1.6 组合赋值运算符
5.1.7 复合语句(语句块)
使用两个花括号来构造一条复合语句(代码块),被视为一条语句。
在语句块中定义的新变量,则仅当执行该语句块中的语句时存在,执行完后,该变量被释放。
若语句块中声明一个外部语句块的同名变量,在第二次声明位置到内部语句块结束的范围内,新变量将隐藏就变量。
int main()
{
int x=20;
{
cout<<x<<endl; //输出20
int x=100;
cout<<x<<endl; //输出100
}
cout<<x<<endl; //输出20
}
5.1.8 其他语法技巧——逗号运算符
逗号表达式允许将两个表达式放到C++句法值允许放一个表达式的地方。
声明语句将逗号用于分隔列表中的变量。
逗号运算符也是一个顺序。
i=20,j=2*20; //j=40
逗号表达式的值是第二部分的值(上述表达式的结果为40),逗号运算符的优先级最低
cats=17,240 //被解释为(cats=17),240
5.1.9 关系运算符
可以用于字符,但不可以是C风格字符串。
关系运算符优先度比算数运算符低。
5.1.10 C风格字符串比较
由于C风格字符串为一个地址,所以不能直接使用关系运算符进行比较
提供了**strcmp()**函数,它接受两个字符串地址作为参数。
若相同返回0;第一个字符串按字母顺序排在第二个字符串之前,则返回负值;若排在之后返回正值。
5.1.11 比较string字符串
通过重载关系运算符可以直接使用关系运算符进行比较。
5.2 while循环
只有测试条件和循环体
while (test-condition)
body
5.2.1 for和while
本质上相同
不同:for循环中省略了测试条件时,将默认为true;其次for循环中可以初始化语句声明一个变量,但while不可以
若包含continue情况稍有不同(第六章指出)
通常程序员使用for循环来为循环计数,在无法预知循环次数时,程序员常使用while
5.2.2 等待一段时间:编写延时循环
可以用while达成延时效果,但等待时间不可控。
long wait=0;
while(whit<10000)
whit++;
使用系统时钟
**clock()**函数,返回程序开始执行后所用的系统时间。但返回单位不一定是秒,返回类型不可控。
在头文件ctime中,定义了符号常量——CLOCKS_PER_SEC,该常量为每秒包含的系统时间单位数。
ctime还将clock_t作为clock()返回类型的别名。
类型别名
建立类型别名方式有两种
1、使用预处理器:#define BYTE char 用char替换所有BYTE
2、关键字typedef:typedef char byte 将byte作为char的别名
typedef能够处理更复杂的类型别名,建议使用typedef
5.3 do while循环
它是出口条件循环。先执行循环体,再判定测试表达式。
do
body
while (test-expression)
通常入口条件循环比出口条件循环好,因为入口循环再循环开始前对条件进行检测。
但有时使用do while更符合逻辑(例如:请求用户输入时,程序应先输入后判断)
5.4 基于范围的for循环
简化了常见的循环认为:对数组或容器的每个元素执行相同操作(主要用于容器)
int arr[5]={1,2,3,4,5};
for(int x:arr)
cout<<x<<endl;
5.5 循环和文本输入
5.5.1 使用原始的cin进行输入
选择某个特殊字符——也被称为哨兵字符,将其作为停止标记。
但cin将忽略空格和换行符,发送给cin的输入被缓冲,意味着只要用户按下回车键后,输入的内容才会被发给程序。
5.5.2 使用cin.get(char)补救
读取输入的下一个字符,即使他是空格。
5.5.3 使用哪种类型的cin.get()
C++使用函数重载使得get()存在多个形式,根据传入参数不同选择不同的get()
5.5.4 文件尾条件
检测文件尾(EOF)来告知程序输入结束(它来源于文件输入)
很多系统允许通过键盘来模拟文件尾操作
Windows中可以再任意位置按Ctrl+z和回车达成输入EOF。
检测到EOF后,cin将两位(eofbit和failbit)都设置为1。
检测到EOF,cin.eof()将返回bool值true,否则返回false。
如果eofbit或failbit被设置为1,则fail()函数返回true否则返回false
eof()和fail()报告最近读取的结果
1、EOF结束输入
设置EOF后cin将不会读取输入,再次条用cin也无法读取
可以用cin.clear ()清楚EOF标记(但一些系统中Ctrl+z将结束输入输出,该函数无法恢复输入输出)
2、常见字符输入法
cin.get(ch);
while(!cin.fail())
{
...
cin.get(ch);
}
cin.get(ch);返回值时cin对象,istream提供可以将istream对象转为bool的函数;当cin需要被转成bool时该函数被调用。
如果最后一次读取成功该函数被转换为ture否则为false
可以将上述while测试改为while(cin)这比!cin.fail()更通用,可以检测失败原因。
也可精简为
while(cin.get(ch);)
{
...
}
这样cin.get(ch)只被调用一次。
5.5.5 另一个cin.get()版本
当函数到达EOF时,将没有可返回的字符。但cin.get()将返回一个用符号EOF表示的特殊值。通常定义为-1。
[外链图片转存中…(img-H6h9puo3-1734614181740)]
5.6 嵌套循环和二维数组
C++没有提供二维数组类型,用户可以创建每个元素本身就是数组的数组。
5.6.1 初始化二维数组
nt arr[2][3] =
{
{1,2,3},
{4,5,6}
};
5.6.2 使用二维数组
int score[3][3] =
{
{100,100,100},
{90,50,100},
{60,70,80}
};
string name[3] = { "张三","李四","赵六" };
for (int i = 0; i < 3; i++)
{
int sum = 0;
for (int j = 0; j < 3; j++)
{
sum += score[i][j];
}
cout << name[i] << "总分为:" << sum << endl;
}
第六章 分支语句和逻辑运算符
6.1 if语句
语法:
if (test-condition<测试条件>)
statement(语句)
statement可以是一个语句也可以是一个语句块。
整个if语句被视为一条语句。
6.1.1 if else 语句
语法:
if (test-condition)
statement1
else
statement2
从语法上看整个if else语句被视为一个语句
6.1.2 格式化if else语句
if else中两种操作都必须是一条语句。
若使用多条语句,应用大括号括起来,组成一个语句块。
主要分为两种风格
第一种:强调语句的块结构(我喜欢这种)
if(...)
{
...
}
else
{
...
}
第二种:将语句块和关键字更紧密地结合在一起
if(...){
...
}
else{
...
}
6.1.3 if else if else结构
if(...)
{
...
}
else if(...)
{
...
}
else
{
...
}
6.2 逻辑表达式
6.2.1 逻辑OR运算符:||
||优先度比关系运算符低,运算符左边的子表达式先于右边的子表达式。
同假为假,其他为真。
6.2.2 逻辑AND运算符:&&
&&优先度比关系运算符低。
同真为真,其他为假
可以用&&设置取值范围
if(a>10&&a<20) //
if(10<a<20) //错误 不能这么表示编译器将理解为if((10<a)<20)因为10<a结果要么为1要么为0,但一定小于20,所以是true
6.2.4 逻辑NOT运算符:!
!优先度高于所有关系运算符和算数运算符。
假变真,真变假
6.2.5 逻辑运算符细节
推荐使用括号就行分组,不管是否需要括号,有助于代码的阅读。
6.2.6 其他表达方式
运算符 | 另一种表达方式 |
---|---|
&& | and |
|| | or |
! | not |
6.3 字符函数库 cctype
包含头文件cctype。
6.4 ? : 运算符
称为条件运算符(也称三目运算符),可以替代if else
语法:expression1 ? expression2 : expression3
若1为真,执行2并返回2的结果。
若1为假,执行3并返回3的结果。
条件运算符生成一个表达式,因此是一个值。
6.5 switch语句
语法:
switch (integer-expression)
{
case label1:statement(s)
case label2:statement(s)
...
default:statement(s)
}
最常见的标签是int或char常量,也可以是枚举量。
若integer-expression不与任何标签匹配,则程序跳到标签为default的那一行。
case标签只是行标签,而不是选项之间的界线,跳到特定代码行后,依次执行之后的所有语句(可以使用break跳出switch)。
6.5.1 将枚举量作为标签
6.5.2 switch和if else
switch缺点:判断只能为整型、字符或枚举,不能是区间。
switch优点:结构清晰,执行效率高。
6.6 break和continue语句
都使程序能够跳出部分代码。
break语句:
出现在switch语句中,终止case跳出switch;
出现在循环中,跳出当前循环。
continue语句:
跳过本次循环的余下未执行语句,继续执行下一次循环(但不会跳过循环更新表达式)
goto语句:
goto 标签;
标签定义方式: 标签名 :
goto不常用,它导致代码可读性变差。
6.7 读取数字循环
int n;
cin>>n;
如果输入字符将会发生
- n的值保持不变
- 不匹配的输入将被留在输入队列中
- cin对象中的一个错误标记被设置(必须重置该标记,程序才能继续输入)
- 对cin方法的调用将返回false(可以使用非数字输入结束读取数字的循环)
解决方式
while(!(cin>>golf[i])
cin.clear();
while (cin.get!='\n')
continue;
if(!(cin.get>>golf[i])
{
cin.clear();
cin.ignore(numeric_limits<streamsize>::max(),'\n');
}
6.8 简单文件的输入输出
6.8.1 文本I/O和文本文件
cin进行输入时,程序将输入视为一系列字节,每个字节都被解释为字符编码。
不论目标类型是什么,输入一开始都为字符数据——文本数据,然后cin再将文本转为其他类型。
Window文本文件的每行都以回车字符和换行字符结尾
6.8.2 写入到文本文件中
- 包含头文件fstream
- 输出类ofstream
- 声明一个或多个ofstream变量
- 使用open()将对象和文件关联
- 使用close()将文件关闭
- 结合ofstream对象和运算符<<来输出各种类型的数据
6.8.3 读取文本文件
- 包含头文件fstream
- 输入类ifstream
- 声明一个或多个ifstream变量
- 使用open()将对象和文件关联
- 使用close()将文件关闭
- 结合ifstream对象和>>来读取数据
- 使用get()读取一个字符,使用getline()读取一行
- 结合eof()和fail()判断输入是否成功
- ifstream对象用于测试条件时,如果最后一步读取成功,他将转为true
- 使用**is_open()**来检测是否打开成功,成功打开返回true
第七章 函数——C++的编程模块
7.1 函数基本知识
7.1.1 定义函数
函数分为两类:没有返回值的函数和有返回值的函数
-
没有返回值的函数
-
被称为void函数
-
void functionNam(parameterList) { statement(s) return; }
-
parameterList指定了传递给函数的参数类型和数量
-
返回语句标记了函数结尾,省略后函数将以右花括号结束。
-
-
有返回值的函数
-
函数将生成一个值,返回给调用函数
-
typeName functionNam(parameterList) { statement(s) return value; }
-
必须使用返回语句
-
结果类型必须为typeName类型或可以转为typeName类型(不能是数组,可以是任何类型<包含数组可以>)
-
返回值的底层逻辑:函数将返回值复制到指定CPU寄存器或内存单元中将其返回。调用程序将会查看该内存单元。
返回函数和调用函数必须就该内存单元中存储的数据类型达成一致。
函数原型将返回值类型告知调用程序,而函数定义命令被调用函数应返回什么类型的数据。
-
7.1.2 函数原型和函数调用
1、为什么需要原型
原型描述了函数到编译器的接口,他将返回值的类型(如果存在)以及参数的类型和数量告知编译器。
可以在使用前定义函数避免使用函数接口,但C++编程风格是将main函数放在最前面。
2、原型语法
函数头+分号
原型中不要求提供变量名,有类型列表就足够了。
3、原型的功能
编译器正确处理函数返回值
编译器检查使用的参数数目是否正确
编译器检查使用参数类型是否正确;若不正确,则转化为正确类型(如果可以转换)
仅当有意义时,原型化才会导致类型转换。在编译阶段进行的原型化被称为静态类型检查。
7.2 函数参数和按值传递
按值传递时,形参为实参的一个副本,形参为函数私有,仅当函数被调用时,计算机才为形参分配内存,在函数结束时释放内存。
函数使用多个参数时使用逗号隔开,避免使用占位参数
7.3 函数和数组
参数传入数组时使用, 但真是情况是arr不是数组而是指针
int Arr(int arr[],int n);
int Arr(int* arr,int n);
7.3.1 函数如何使用指针来处理数组
int arr[]和int* arr都代表接受一个地址,效果相同(当前仅当做形参)
得到首地址通过地址++访问到数组中的各个元素
7.3.2 将数组作为参数意味着什么
实际上并没有将数组全部内容传递给函数,而是将数组地址,元素种类,个数告诉给函数。
将数组地址作为参数可以节省复制真个数组所需的时间和内存;减少破坏数据的风险。
由于传递的是地址所以函数访问的是原始数组不是副本,所以函数中的修改会改变原始数组。
为了避免无意中修改数组内容,可在声明形参时使用const。
7.3.4 使用数组区间的函数
在上述使用的方法是传入数组首地址,和数组长度访问数组。
还可以提供两个指针,一个指向数组中首地址另一个指向数组尾部(指向数组最后一个元素后面的指针)
这种方法在STL容器中非常常见。
7.3.5 指针和const
指针常量: int* const p=&a; 指向不可改,指向值可以改
常量指针: const int* p=&a; 指向可以改,指向值不可改
const既修饰指针又修饰常量 const int* const p=&a; 指向和指向值均不可改
数据类型本身不是指针,可以将常量或变量地址赋给指向常量的指针,但非const指针只接受非const数据地址。
尽可能使用const,可以避免无意间修改数据
7.4 函数和二维数组
二维数组在函数中的使用
void printArray(int (*arr)[4], int len); //int (*arr)[4]<=>int arr[][4];
int main()
{
int arr[3][4] = { {1,2,3,4},{5,6,7,8},{2,4,6,8} };
printArray(arr,3);
}
void printArray(int (*arr)[4],int len)
{
for (int i = 0; i < len ; i++)
{
for (int j = 0; j < 4; j++)
{
cout << *((*arr+i)+j) <<" "; //arr[i][j];
}
cout << endl;
}
}
7.5 函数和C风格字符串
字符串本质虽与字符数组类似,但在充当函数参数时可以不需要传入字符串长度。
函数无法直接返回字符串,但可以返回字符串地址 。
char* printchar(char* a);
int main()
{
char a[]="abc";
printchar(a);
cout << a << endl;
}
char* printchar(char* a)
{
while (*a != '\0')
{
cout << *a;
(*a)++;
a++;
}
cout << endl;
return a;
}
7.6 函数和结构
结构变量接近于基本的简单变量,当结构较小时按值传递较为合理,较大时使用地址。
7.7 函数和string对象
string对象与结构类似。
7.8 递归
函数自身调用(C++不允许main函数调用自己)被称为递归。
常用于算法题
//使用递归函数求阶乘
int f(int n)
{
int ref=1;
if (n == 1)
{
ref = 1;
}
else
{
ref =n*f(n-1);
}
return ref;
}
int main()
{
int ref = f(5);
cout << "ref=" << ref << endl;
}
7.9 函数指针
函数地址:存储其机器语言代码的内存的开始地址 。
7.9.1 基础知识
函数地址:函数名
声明函数指针:要指定函数的返回类型以及函数的参数列表。 例:int (*p)(int);
使用指针调用函数:(*p)和函数名相同,C++也允许直接使用p。
int add(int num1, int num2);
int main()
{
int a = 10, b = 20;
int(*Add)(int num1, int num2) = add;
cout << add(a, b) << endl;
cout << (*Add)(a, b) << endl;
cout << Add(a, b) << endl;
}
int add(int num1, int num2)
{
return num1 + num2;
}
7.9.2 深入探讨函数指针
const double* f1(const double ar[],int n);
const double* f2(const double [],int);
const double* f3(const double *,int);
这三个函数特征相同,想用一个指针数组指向这三个函数应使用
const double*(*p[3])(const double *,int n)={f1,f2,f3};
因为p为包括三个元素的数组,所以先使用p[3],运算符[]高于*****,因此*****p[]表示p时一个包含三个指针的数组。
此时不能使用auto,自动类型推断只能用于单值初始化,不能用于初始化列表。
使用元素时
(*p)[i](av,3);
(*(*p)[i])(av,3);
7.9.3 使用typedef进行简化
可以创建类型别名
typedef const double* (*p_fun)(const double ar[],int n);
p_func pa={f1,f2,f3};
第八章 函数探幽
8.1 C++内联函数
语法:在函数声明/定义前加inline
作用:提高程序允许速度。
缺点:占用内存大
原理:使用函数代码替换函数调用。
使用:通常省略原型,将定义写到原型的的位置,函数执行时间短时,使用内联函数,执行时间长时,内联函数效果不明显。
内联与宏的区别:
宏:本质就是文本替换 例如:#define ADD(x) x+x x为c++时,c递增两次
内联:是一个函数可以按值传递参数 例如:inline int ADD (int x){return x+x}; x为c++时,c递增一次
8.2 引用变量
8.2.1 引用基础知识
语法: 数据类型 &别名=原名 (左值引用)
本质:类似于常量指针,将自动转化为 int* const b=&a
注意事项:1、引用必须初始化。
2、初始化后,引用不可以改变。
8.2.2 函数与引用
引用传递本质上为地址传递,可以修改实参
void swap(int &num1,int &num2)
{
int temp = num1;
num1 = num2;
num2 = temp;
}
返回值为引用
//不要返回局部变量的引用
int& test01()
{
int a = 10;//局部变量存放在栈区,函数结束后自动释放,再次访问为违法访问(虽然有些编译器会继续保留)
return a;
}
//函数调用可以做左值
int& test02()
{
static int a = 10;//加static静态变量,放在全局区,在程序结束后系统释放
return a;
}
int main()
{
int& ref2 = test02();
cout << ref2 << endl;
test02() = 1000;//如果返回值是引用,这个函数调用可以做左值
cout << ref2 << endl;
}
8.2.3 引用的属性和特别之处
临时变量、引用参数和const
若实参与引用参数不匹配,C++将生成临时变量。当前,仅当参数为常量引用时,C++才允许这样做。
临时变量创建时机:实参类型正确,但不是左值;实参类型不正确,但可以转换为正确的类型。
左值:可被引用的数据对象,变量、数组元素、结构成员、引用。
非左值:包括字面常量和包含多项的表达式。
//错误:int& ref = 10; 10为常量,引用必须引一块合法的内存空间
//加上const之后,编译器将代码修改 int temp = 10; const int& ref=temp;
const int& ref = 10;
C++新增了另一种引用右值引用
语法:数据类型 &&别名=原名
用途:让库设计人员能够提供一些操作的更有效实现。
8.2.4 将引用用于结构和类
引用规则与函数差不多,要结合结构和类自身的属性,综合使用
8.2.5 对象、继承和引用
继承为面向对象三大特征之一,等到介绍面向对象时将详细展开
setf()可以设置各种格式化状态。
setf(ios_base::fixed) 对象置于使用顶点表示法状态
setf(ios_base::showpoint) 对象置于显示小数点模式
precision() 指定显示多少位小数点(假定对象处于顶点状态下)
width() 设置下一次输出操作使用的字符宽度
8.2.6 何时使用引用参数
要修改调用函数中的数据对象
提高程序运行速
8.3 默认参数
//如果传入数据,就使用传入数据,没有传入,就使用默认值
//语法: 返回值类型 函数名(形参=默认值)
int func(int a, int b=20, int c=30)
{
return a + b + c;
}
int main()
{
//注:
// 1、如果某个位置已经有默认参数,那么从这个位置往后,从左到右都必须有默认值
/*
错误示例:
int func(int a=10,int b,int c)
{
return a+b+c;
}
*/
// 2、如果函数声明有默认参数,函数实现就不能有默认参数
// 声明和实现只能有一个默认参数
/* 错误示例:
int func(int a=10,int b=10);
int func(int a=10,int b=10)
{
return a+b;
}
*/
cout << func(10,30) << endl;
system("pause");
return 0;
}
8.4 函数重载
也称函数多态。
名称修饰,根据形参类型对每个函数名加密,从而跟踪每个重载函数
//作用:函数名可以相同,提高复用性
/*
满足条件:
1、同一作用域下
2、函数名相同
3、函数参数或个数或顺序不同
注:函数返回值不可作为函数重载条件
*/
void func()
{
cout << "this is func" << endl;
}
void func(int a)
{
cout << "this is func(int a)" << endl;
}
void func(double a, int b)
{
cout << "this is func(double a, int b)" << endl;
}
void func(int a, double b)
{
cout << "this is func(int a, double b)" << endl;
}
int main()
{
func();
func(10);
func(3.14, 10);
func(10, 3.14);
system("pasue");
return 0;
}
8.5 函数模板
使用泛型定义函数
语法:template
函数声明或定义
解释:template:声明创建模板
typename:表面后面的符号是一个种类型,可以用class替换
T:通用的数据类型,名称可替换,通常为大写字母
template<typename T> //typename可以替换为class
void myswap(T& num1, T& num2)
{
T temp = num1;
num1 = num2;
num2 = temp;
}
int main()
{
int a = 10;
int b = 20;
//自动类型推导 普通函数调用
myswap(a, b);
//显示指定类型 调用时在函数名后面加<数据类型>
myswap<int>(a, b);
std::cout << "a=" << a << std::endl;
std::cout << "b=" << b << std::endl;
system("pause");
return 0;
}
8.5.1 重载的模板
与重载函数类似
8.5.2 模板的局限性
编写的函数模板可能无法处理默写类型,有时候通用化是有意义的,但C++不允许这么做
8.5.3 显式具体化
利用具体化模板,可以解决自定义类型的通用化
template <> 返回值类型 函数名(自定义数据类型)
第三代具体化( ISO/ANSI C++标准)
-
对于给定的函数名,可以有非模板函数、模板函数和显示具体化模板函数以及它们的重载版本;
-
显示具体化的原型和定义应以template<>打头,并通过名称来指出类型;
-
优先度: 非模板函数>具体化>常规模板
void Swap(job&,job&); //非模板函数 void Swap(T&,T&); //模板函数 template <>void Swap<job>(job&,job&) //具体化 <job>是可选的
8.5.4 实例化和具体化
实例化:
定义:函数模板本身不是函数定义,他是生成函数定义的方案。使用函数模板为特定数据类型生成函数定义时称为实例化。
分类:隐式实例化和显式实例化
隐式实例化:使用自动类型推导
显示实例化:template void Swap(job&,job&);
具体化:实例化和显示具体化统称为具体化,注意使用template和template <>区分显式实例化和显示具体化
注意:在同一文件中使用同一种类型的显示实例化和显示具体化将出错。
8.5.5 编译器下选择使用哪种函数版本
对于函数重载、函数模板和函数模板重载,C++重载解析决定调用哪个函数。
重载解析:
1、创建候选函数列表。
2、使用候选函数列表创建可行的函数列表。
3、确定是否有最佳的可行函数。
最佳函数:
1、完全匹配,但常规函数优先于模板
2、提升转换
3、标准转换
4、用户定义的转换
完全匹配与最佳匹配:
C++允许无关紧要的转换
如过有多个匹配的原型,编译器将无法完成重载解析过程(二义性)。
最具体:编译器推断使用哪种类型时执行的转换最少。
部分排序规则:
用于找出最具体的模板的规则。
8.5.6 模板函数的发展
decltype用于声明数据类型未知的变量。
后置返回类型
double h(int x ,int y)可以改写为auto h(int x,int y)->double
第九章 内存模型和名称空间
9.1 单独编译
C++鼓励程序员将函数放到独立文件中,链接器会将他们整合为可执行程序。
分文件管理:
- 头文件:包含结构声明,使用这些结构的函数原型
- 源代码文件:包含与结构有关的函数的代码(函数定义)
- 源代码文件:包含调用与结构相关的函数的代码
不要将函数定义、变量声明放到头文件中。
include包含头文件<>编译器将在存储标准头文件的主机系统的文件系统中查找,""编译器将首先在工作目录、源代码目录中查找
头文件管理
同一个文件只能将同一个头文件包含一次。
下面代码意思为仅当以前没有使用预处理器编译指令定义COORDIN_H才执行ifndef~endif之间的指令
#ifndef COORDIN_H
#define COORDIN_H
#endif
这种方法不能防止编译器将文件重复包含两次,而只是让他忽略除第一次包含之外的所有内容。
现在有些编译器提供#pragma once防止重复包含(更常用)
多个库的链接
每个编译器都有自己的名称修饰,在连接编译模块时确保所有对象或库都由一个编译器生成。
9.2 存储持续性、作用域和链接性
存储方式:
自动存储持续性:
静态存储持续性:
线程存储持续性:
动态存储持续性:
9.2.1 作用域与链接
作用域:描述了名称在翻译单元(文件)的多大范围内可见。
分类:
局部:只能在定义它的代码块中可用。
全局:在定义位置到文件结尾之间都可用。
函数原型作用域:只在包含参数列表的括号内可用。
类中声明的成员作用域是整个类。
名称空间声明的变量作用域是整个名称空间。
链接性:描述了名称如何在不同单元间共享。
分类:
外部:可在文件间共享。
内部:只能由一个文件中的函数共享。
无:不可共享
9.2.2 自动存储持续性
默认下在函数中声明的函数参数和变量的存储持续性为自动,作用域是局部,没有链接性。
register用来显式的指出变量是自动的。
9.2.3 静态持续变量
有三种链接方式:外部链接性(可在其他文件中访问),内部链接性(仅当前文件可访问),无链接性(仅在当前代码块访问)
都在整个程序执行期间存在,若没有显式地初始化,将默认为0(零初始化)
链接性为外部的静态持续性变量:必须在代码块外声明
链接性为内部的静态持续性变量:在代码块外声明它,并使用static
没有链接性的静态持续性变量:在代码块内声明它,并使用static
静态变量初始化:零初始化、常量表达式(constexpr增加创建方式)初始化,编译器在处理文件时初始化变量。
动态初始化:变量在被编译时初始化
9.2.4 静态持续性、外部链接性
链接性为外部的静态持续性变量简称外部变量(全局变量),存储持续性为静态,作用域为整个文件。
单定义规则:
变量只能定义一次。C++提供两种声明,定义声明:将分配内存;引用声明:引用已有变量,使用关键字extern,不进行初始化。
作用域解析运算符(:
标签:知识点,函数,int,C++,运算符,关键,使用,指针 From: https://blog.csdn.net/hjc2006089/article/details/144595208