栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++Prime Plus(2)

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++Prime Plus(2)

目录
  • 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< 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风格的字符串是用字符数组表示,注意最后有结束符’’;

字符串传递,由于有结束符这个特点;
我们只需要一个参数,字符数组名 或者 指向字符的指针。函数从数组名中的地址开始一直处理到’’。

示例:

字符串也可以为返回值,返回一个指向字符的指针。

注意指针指向的空间必须是离开函数后依然可用的,该空间不应该来自局部自动变量,所以我们通常用动态内存来实现。

因为,如果只返回一个指针,我们只能返回该指针的地址,函数退出后,地址对应的内容已经被回收(如果是局部自动变量)。

示例:

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声明,
也可以由template声明。

示例:

显式地实例化
使用函数模板时,为了强制明确参数与返回值类型,我们可以在调用时,在函数名后面明确指出模板的实际参数:

函数名<模板实际参数表>(函数的实际参数表);


在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<

示例:

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 名字空间名;

作用:将该名字空间的成员加入到当前作用域,在此作用域中引用该名字空间的成员不需要加名字空间名的限定。
比如:

示例:

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/862237.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号