目录
- 21.for循环(1)
- 22.for循环(2)
- 23.while循环
- 24.do while循环
- 25.二维数组与嵌套循环
- 26.if语句
- 27.逻辑表达式
- 28.条件表达式
- 29.switch语句
- 30.文件概念
- 31.文本文件的输入输出
- 32.函数详解(1)回顾
- 33.函数详解(2)参数传递
- 34.函数详解(3)数组传递
- 35.函数详解(4)C风格字符串
- 36.递归概念
- 37.函数指针
- 38.内联函数
- 39.引用变量
- 40.函数参数的默认值
- 41.函数重载
- 42.函数模板(1)定义与使用
- 43.函数模板(2)函数模板的重载
- 44.函数模板(3)模板的具体化
- 45.多文件程序
- 46.多文件的编译和链接
- 47.变量的作用域
- 48.变量的存储持续性(1)-自动持续性
- 49.变量地存储持续性(2)-静态持续性
- 50.命名空间
21.for循环(1)
for循环作用:指定某段程序执行指定次数
格式:
for(计数器赋初值; 检查是否达到指定次数; 修正计数器值)
{
计算过程;
}
我们使用for循环在计算机中的执行步骤为:
通常,for循环用在循环次数确定的场景中。
使用for访问字符串:字符串可以用字符数组,string类或指向字符的指针表示。
注意,虽然VS中写法为const char* str,但我们将鼠标移动至str变量,它的类型显式其实为const char *str,所以确实是pointer to const。
在C++11中,为了处理循环变量无规律变化的情况,新增了范围for;
格式:for (循环变量 : 数组或列表)
;
22.for循环(2)
在for循环中,常用 + +运算符和 - -运算符,这是两个一元运算符;
前缀:++k
后缀:k++
两个运算符有前缀和后缀区别:表达式的值不同,前缀的表达式结果是运算对象本身,后缀的表达式是修改前的运算对象值;
C++中,任何一个二元运算符,只要形式为:”变量=变量 op 表达式”
,都可以简写为:“变量 op=表达式”
,op是二元运算符。
比如:x*=5
等价于x=x+5
;
逗号表达式:在只允许出现一个表达式的地方放多个表达式。
格式:表达式1,表达式2,…,表达式n
执行过程:依次执行表达式1,表达式2,…,整个表达式的执行结果是表达式n的值。
示例如下:
for循环中的关系表达式
作用:通过比较进行判断
格式:表达式1 关系运算符 表达式2,关系表达式的返回值:bool类型
当一个表达式出现多个关系运算符,按左结合性,C++先执行左边再执行右边。
字符串比较
C++风格的字符串比较:直接用关系运算符比较大小。
C风格的字符串比较:用cstring库中的函数strcmp。
举例:const char *s1=”abcde”; const char *s2=”aaaaaab”
,在比较时,先比较第一个字符,两个都是a,比较下一个字符,s1中是b,值大于s2中的a,所以s1大于s2,应该返回正整数。
23.while循环
当循环次数不确定时,我们通常使用while循环。
格式:
while (测试条件)
{循环体}
示例:显式C风格字符串中每个字符和对应的内码。
在上面示例中,我们使用cin将字符串输入到char型数组中。
对于输入一个单词:
cin>>字符数组名,比如cin>>str,cin以回车字符或者空格字符作为输入结束的标记
对于输入一行:
cin.getline(字符数组名,数组规模)
cin.get(字符数组名,数组规模)
以回车字符或达到数组规模结束输入,区别:getline将回车的换行符丢弃,get会将换行符留在缓冲区放在下一次输入的最开始位置。
上面示例,我们用while循环访问字符串效率高于使用for,因为for循环需要结束条件(需要计算字符串长度strlen(),其中strlen()函数其实是已经遍历了一次字符串才知道长度的,所以for循环相当于遍历字符串两次,而while则是一次);
示例:回显输入的字符并统计输入的字符数(进一步认识cin和输入队列或输入缓冲区)
过程分析:当我们用键盘输入字符串时,实际上都是不断向输入队列输入数据。只有当我们键入回车符,C++才会向输入队列读取数据。(所以我们在回车前,我们可以在控制台删除输入错误的字符,再重新输入);
因此在上面示例中,我们虽然已经输入了#
,但是由于没有回车,我们还可以输入后面的字符串。
当我们回车后,程序开始读输入队列,我们使用cin去读输入,cin的特点是以空格符和回车符作为每次的分隔(cin每次读到空格或者回车都会自动过滤这两种字符)。因此cin读到char中的字符是不会存在空格符和回车符的。当cin读#
并存储到ch变量后,while循环结束。
完整回显式的解决方案:
用get函数,注意原型是cin.get(char),可以读入空格符和回车符;
不是cin.getline(字符数组名,数组规模)
不是cin.get(字符数组名,数组规模)
因此,修改为:
由于#
可能是文本中本来存在的字符,因此,我们更需要一个键盘上没有的字符作为结束符。
我们应该用文件结束符EOF作为结束标记。
EOF是一个符号常量,表示文件结束。就像Linux的一切皆文件,C++的控制台可以看作文件,键盘是输入文件,显示器是输出文件。因此读键盘(其实是读输入队列)就是在读文件。当读到EOF时,就结束。
EOF的输入:Unix系统:Ctrl+D,Dos系统(Win):Ctrl+z
在C++中,当cin.get(char)读到EOF后,cin.fail()会返回true,因此,我们有以下示例:
再次强调cin.get(char),cin.get(char)其实已经把回车符读入了,因为在控制台上可以看到,回显字符串后我们输入EOF时已经在下一行,说明回显已经包含了回车符。
另外,观察到Ctrl+z与36 chars read之间空了一行,那一行其实是我们输入Ctrl+z后键入回车符导致的。键入那个回车符代表光标已经来到下一行,但是cout<<count之前,我们cout了endl,所以光标又下移一行。因此出现了空一行的现象。
24.do while循环
先执行循环体,再判断循环条件表达式。格式:
do
{循环体}
while(测试条件);
示例:输入并回显字符,直到遇到#
,输出#
并输出字符数(包含#
)
25.二维数组与嵌套循环
二维数组:一维数组的数组
定义:类型名 数组名[常量1][常量2]
,常量1是一维数组的数量,常量2是一维数组的元素数。比如int a[3][4]
二维数组的初始化和元素表示:
二维数组的访问:用两层嵌套的for循环
三种字符串数组的表示:
26.if语句
第一种:if 后为then子句;
例如:统计输入字符中的空格数和总字符数
注意回顾缓冲区的原理,每次换行后,键盘输入的字符串被送到缓冲区,程序读缓冲区的数据并执行各种功能。
第二种:if条件不满足时,由else子句执行;
例如:将输入字符回显成字母表的下一字符:
第三种:if语句的嵌套,if语句的then子句或else子句是if语句
27.逻辑表达式
前面的那些表达式:比较谁大谁小,是否相等,这些是关系表达式。为了处理更复杂的条件,我们应使用逻辑表达式。
逻辑运算符:与&&(二元运算符) 或||(二元运算符) 非!(一元运算符)
真值表为:
结合性为左结合,先计算左边。
对于逻辑表达式的运算对象:0表示false,非0表示true,不同于bool类型。
bool类型:表示逻辑”真”和”假”,bool类型的值(true和false)
bool类型的机器内表示为一个字节,true是1,false是0,bool可以作为算术运算的运算数。
bool不能直接输入输出,直接输出bool类型的值得到的不是true或flase,而是1或0。
注意以下次序问题:a为非0,即true,对于||,有一个为true,结果就为true,所以后面的b+=a就不会计算,因此b不是8而是3。
示例:避免输入的整数超出合法范围
28.条件表达式
示例:输出两个整数的最大者
29.switch语句
if语句和条件表达式只有两个分支,为了处理多个分支,我们应该使用switch语句
break语句与多分支,break可以跳出当前的switch语句
switch与break配合使用才能实现多分支
示例:菜单选择
break与continue
30.文件概念
文件是存储在外存储器中的数据集合,程序运行时可以从文件中获取信息,不一定要从键盘输入。
C++将文件看成是一串流动的数据,称为数据流,从外围设备流入程序的数据称为输入流,从程序流向外围设备的数据称为输出流,每个数据流表示为一个对象。
C++将控制台输入输出看成是一个文本文件:
文件类fstream,用于文件访问,需要包含头文件fstream,之后:
读文件:定义ifstream类的对象;
写文件:定义ofstream类的对象;
读写文件:定义fstream类的对象;
比如:
31.文本文件的输入输出
根据上一节的内容,我们需要遵循以下流程:
1.包含头文件fstream
2.定义一个文件流对象
3.将对象与被访问的文件关联起来
4.从文件读取数据
5.关闭文件
示例:写文件
读文件示例:从文件读取一组实数,计算总和与均值
注意到inFile.fail(),其实inFile就像cin,我们用inFile向内存读入文件中的内容,即输入队列中的逐个对象读入value,由于value是double型,当遇到非数值的对象时,inFlie.fail()返回true 1。
32.函数详解(1)回顾
函数定义:
函数原型声明:让编译器检查函数调用的正确性;
函数调用:
函数示例:
33.函数详解(2)参数传递
C++中,参数传递的默认方式为值传递(将数值传给函数)
函数中创建形式参数,为形式参数分配空间,将实际参数作为初值。
对于多个参数,实际参数间用逗号分开。
实际参数可以是一个表达式,比如:
示例,计算m^n
34.函数详解(3)数组传递
数组传递:函数的参数是一组同类变量;
数组传递需要两个参数:数组名,长度;
比如计算一组整数的和:int sum(int a[], int size);
示例:
注意实际参数传递时:sum_arr(cookies, ArSize);
数组名字是数组首元素的地址,数组名是指针常量,
因此,数组传递其实传递的不是数组,而是地址(等价于sum_arr(&cookies[0], ArSize);
),所以我们可以用指针作为函数的参数。
数组名是一个指针,数组传递可以用指针表示:
int sum_arr(int* arr, int n);
实际参数也可以表示成指针:
int *p=&cookies[0]; 或者int *p=cookies;
sum=sum_arr(p, ArSize);
用指针形式使得数组参数的处理更灵活,比如我们要求从第3个元素开始的元素之和:
int *p=cookies+2;
sum=sum_arr(p, ArSize);
数组传递中传递的是数组的起始地址,不需要像值传递中需要为所有对象开辟空间,所以数组传递更节省空间;
示例:输入,修改,显示数组
注意到值的问题:
可以发现,在值传递时,经过调用函数,实际参数在函数退出后并未被修改,但在数组传递时,调用函数退出函数后,数组内容被修改了。
实际上,函数的参数传递是将实际参数拷贝到形式参数上,由于数组传递拷贝的是地址,这导致地址下的内容确实被修改了,而值传递就不会受到影响,被修改的只是拷贝的值,在函数退出后,该拷贝值就自动被栈回收了。
35.函数详解(4)C风格字符串
C风格的字符串是用字符数组表示,注意最后有结束符’\0’
;
字符串传递,由于有结束符这个特点;
我们只需要一个参数,字符数组名 或者 指向字符的指针
。函数从数组名中的地址开始一直处理到’\0’
。
示例:
字符串也可以为返回值,返回一个指向字符的指针。
注意指针指向的空间必须是离开函数后依然可用的,该空间不应该来自局部自动变量,所以我们通常用动态内存来实现。
因为,如果只返回一个指针,我们只能返回该指针的地址,函数退出后,地址对应的内容已经被回收(如果是局部自动变量)。
示例:
36.递归概念
适用条件:求解一个问题时,需要用到同类问题的解。此时可以通过调用自身获得同类问题的解。比如汉诺塔问题。
递归函数:调用自身的函数
递归函数必须满足两个特点:1.调用自身;2.有终止条件。
递归解决汉诺塔问题:假设有一个function可以实现将n个盘从A经过B移动到C;
1.n-1个盘从A经过C移动到B;(调用function,改变参数位置,注意问题规模已经缩小到n-1)
2.第n个盘直接从A移动到C;
3.n-1个盘从B经过A移动到C;(调用function,改变参数位置,注意到问题规模已经缩小到n-1)
注意:我们要加上终止条件。
递归函数的示例:
该示例演示了函数从栈申请内存的过程,逐层调用,最后回溯归还内存,以下图解方便理解:
递归与倒序
递归函数执行时,最先完整执行的是最后一次调用的函数,所以递归常用于倒序处理;
例如,输出一个十进制数n的二进制表示:
初始想法:依次输出二进制的第一位,第二位,…,最后一位
问题:如何得到第一位;
提示:得到最后一位很方便,奇数为1,偶数为0;
解决方案:先以二进制输出n/2,再输出最后一位。
37.函数指针
前面我们知道,对于变量,我们可以通过变量名直接访问,也可以利用指针间接访问。
事实上,指针不仅能保存变量的地址,也可以保存函数的地址。
保存函数地址的指针被称为函数指针。
定义指向函数的指针:
返回类型 (*指针变量名) (形式参数列表);
注意:我们必须在 *指针变量名
加括号,不然编译器会将该声明视为函数返回的值是指针。
我们可以将函数指针作为函数名使用,也可以用*
运算符来间接调用函数;
为什么我们要这么麻烦地用指针间接调用函数?因为我们可以将指向函数的指针作为函数的参数。
例如:
调用时,可以用(*pf)(lines),也可以用 pf(lines)
总结:指向函数的指针用于将函数作为另一个函数的参数。
38.内联函数
在C语言发展到C++后,新增了一类函数,叫内联函数。
内联函数:有函数的形式,但是没有函数调用的代价,我们知道,函数调用存在代价,A函数调用B函数时,先把A暂停,将实际参数拷贝到形式参数,然后执行B,再返回值,继续执行A,可以看到,函数调用会产生额外的代价。
针对简单的函数,传统的调用反而开销相对过大,所以我们应该使用内联函数。
格式:inline 返回类型 函数名 (形式参数列表);
内联函数的定义必须出现在函数调用之前。
示例:
39.引用变量
引用变量实际上是变量的别名;
引用变量声明:
类型 &变量名=已有的同类变量;
如:int k;
int &j=k;
用途
对引用变量操作其实是在对被引用变量进行操作。
因此,我们可以将引用变量作为函数的参数,使形式参数是实际参数的别名,从而代替指针传递,使得函数可以修改到实际参数。
引用传递的示例:
引用传递会修改实际参数,如果想让引用传递代替值传递,需要在形式参数前面加const限定;
引用返回(返回一个引用变量)
格式:
类型& 函数名 (形式参数表);
函数最后返回的必须是一个变量名(所以不可以返回非变量名的表达式);
作用:
将函数用于赋值运算符的左边,即作为左值。
比如:
注意,返回值必须是离开函数后依然存在的左值,如果在返回类型前加const,此时函数不能作为左值。
40.函数参数的默认值
对于某些函数,程序往往会用一些固定的值去调用它;
例如对于以某种数制输出整型数的函数print:void print(int value, int base);
在大多数情况下都是以十进制输出,因此base值总是10;
C++在定义或声明函数时可以为函数的某个参数指定默认值,当调用函数时没有为它指定实际参数时,系统自动将默认值赋给形式参数。
默认参数的好处,使函数应用更灵活,方便
默认参数的示例:
注意函数参数的默认值是在原型声明中指定的,编译器根据原型检查函数调用的正确性,不同的源文件可以指定不同的默认值,使函数使用更加灵活。
默认值参数都必须放在参数列表的最后。
41.函数重载
一组同名函数,比如:
函数特征标:函数的参数数目,类型,及排列顺序,
对于重载函数,必须有不同的函数特征标。
示例:
42.函数模板(1)定义与使用
当我们处理不同相似类型的数据时(比如对int和double都只要执行相同的处理过程),结果需要写两个重载函数,比较麻烦,所以C++提出函数模板;
在函数模板中,用参数表示函数中的变量类型,调用时,用具体的类型代入,形成一个真正的函数。
作用:实现通用函数
在写函数模板时,占位符T可以由template<typename T>
声明,
也可以由template<class T>
声明。
示例:
显式地实例化
使用函数模板时,为了强制明确参数与返回值类型,我们可以在调用时,在函数名后面明确指出模板的实际参数:
函数名<模板实际参数表>(函数的实际参数表);
在C++11中,我们可以使用尾置返回类型,让编译器自动推断返回类型
格式:
auto 函数名 (形式参数列表)-> decltype(表达式)
比如:
43.函数模板(2)函数模板的重载
函数模板的重载:一组同名的函数模板,注意重载函数模板必须有不同的函数特征标;
44.函数模板(3)模板的具体化
函数模板是泛型编程(处理过程相同,只是数据类型不同)的实现方式。
有时候,为了缩小泛型编程的范围,我们可以在定义函数模板时,就对模板进行具体的实例化(模板特化)。
编译器在调用函数时:
先检查是否是普通函数;
再检查是否是模板特化函数;
再检查是否是普通的模板。
示例:
对于上面的场景,当没有重载要求时,我们也可以用普通模板实现结构的成员交换:
45.多文件程序
之前的程序都是仅基于一个源文件,实际项目通常很大,我们应该使用多文件程序。
程序是函数组成的,函数数量不多时,可以用一个源文存放。函数数量很多时,可以分别放在多个文件中。
文件分类
源文件:函数定义;
头文件:程序中声明的结构类型,符号常量,函数原型;
每个源文件包含一组函数,源文件划分通常遵循以下习惯:
main函数单独放在一个源文件中;
功能类似的函数放在一个源文件;
关系比较密切的函数放在一个源文件。
多个源文件的程序,如果一个源文件A中的函数需要调用另一个源文件B中的函数f,需要在A中声明函数f;
解决方案:使用头文件,各个源文件可包含此头文件
示例:
自定义的头文件用include " "
注意到两个cpp都包含了头文件,在链接两个源文件时,会出现重复声明的情况,为了避免重复声明,我们需要使用头文件保护符:
意义是,当声明过时,标识符成立,比如#ifndef
代表标识符还未定义,则我们#define
标识符,于是执行#endif
前面的声明。
事实上,在避免包含多次声明问题上,常有两种做法:
方式一:
#ifndef __SOMEFILE_H__
#define __SOMEFILE_H__
... ... // 声明、定义语句
#endif
方式二:
#pragmaonce
... ... // 声明、定义语句
46.多文件的编译和链接
在不同环境下,有不同的方式实现编译和链接。
在命令行界面下,通常我们使用:
编译命令 一组源文件名
比如在UNIX中,如果安装了C语言编译器cc,现在一个项目包含两个源文件,我们可以用以下方式编译链接:
cc file1.cpp file2.cpp
如果在IDE(集成开发环境),比如Visual Studio中,则可以通过RUN直接实现编译链接和运行。
47.变量的作用域
C++中,变量有两个性质:作用域和存储持续性
作用域:程序中可以使用该变量的区域,可以是一个复合语句,函数,源文件或整个程序;
存储持续性:变量在内存中保留多长时间。
注意区分两个特性:作用域只是描述程序可以使用变量的区域,持续性是描述变量在内存中何时被回收。
作用域分为两种:块作用域和文件作用域
块作用域:一个复合语句内或函数内部声明的变量,包括函数的形式参数,从定义开始到该复合语句或函数结束。
文件作用域:声明函数外的变量,从定义开始到文件末尾有效。
全局作用域 或 程序作用域:可以被本程序的其他函数或文件使用的文件作用域变量;
作用域是程序的一个区域,一般来说有三个地方可以定义变量:
• 在函数或一个代码块内部声明的变量,称为局部变量。
• 在函数参数的定义中声明的变量,称为形式参数。
• 在所有函数外部声明的变量,称为全局变量。
作用域示例:
48.变量的存储持续性(1)-自动持续性
存储持续性描述变量在内存中保留的时间。
存储持续性分为两类:自动 和 静态(程序执行结束才回收)
块和函数中声明的变量(包括形式参数)默认都是自动变量;
可以显式地用关键词auto或register声明;
比如:
int x;
auto int x;
register int x; //希望变量在寄存器中,可以加快读写速度
在定义时生成,所在块或函数执行结束后被回收;
循环或if语句即使没有括号也是一个块,也可以定义自动变量;
比如:for(int k=0; k<10; ++k) cout<<k<<endl;
示例:
49.变量地存储持续性(2)-静态持续性
静态变量:当程序运行结束变量才被回收,静态变量一旦生成,在整个程序运行期间都存在。
对于静态变量,如果定义时没有指定初值,编译器会将值设为0;
静态变量有三种:
1.外部链接
2.内部链接
3.无链接
外部链接
外部链接也称为外部变量,整个程序的所有函数都可以使用。变量只能定义一次,但可以在多处声明。
链接全局变量时的声明,需要加extern,注意到,在这个多文件例子中,我们没有包含头文件就能使用f()函数,因为我们已经在file1中声明了file2的函数(头文件只是在链接时把声明语句链接到了file1中)。
内部链接
只有本文件中的函数可用。定义在文件开头,用static关键字说明,比如:static int x;
无链接
函数或块中用关键字static修饰的变量。
首次进入函数或语句块时生成,函数执行结束后并不回收,下次进入函数时也不重新生成,依然在原有空间下。
补充:
cin.getline(字符数组名,数组规模)
cin.get(字符数组名,数组规模)
以回车字符或达到数组规模结束输入,区别:getline将回车的换行符丢弃,get会将换行符留在缓冲区放在下一次输入的最开始位置。
cin.get的重载:
无参数的cin.get(),cin.get()读入任意一个字符,包含回车。
在早期C语言没有getline时候,只能使用get,但是get对于读入回车的处理会让人们对字符文本的逻辑容易出错,为了让get每次都只输入一行,并让回车不放在下一次输入的行中。
cin.get(char),可以读入空格符和回车符。
50.命名空间
名字空间的用途:防止名字的冲突。
名字包括:变量名,函数名,结构名,类名等。
局部变量:不同的函数可以有同名的局部变量。
成员名:不同的结构,类中可以有同名的成员。
不允许同名的情况:全局变量,函数,类。
名字空间:声明名称的区域,一个名字空间中的名字不会与另一个名字空间中的名字冲突。
创建名字空间:
Namespace 名字空间名 { 名字声明 }
比如:
名字空间的引用:
名字空间名 :: 名字
比如:
using声明
using 名字空间 :: 成员;
作用:将该成员加入到当前作用域,在此作用域中引用该成员不需要加名字空间名的限定。
比如:
using编译指令
using namespace 名字空间名;
作用:将该名字空间的成员加入到当前作用域,在此作用域中引用该名字空间的成员不需要加名字空间名的限定。
比如:
示例: