================
第2章 基本编程语句
================
C++基本编程语句有说明语句、赋值语句、表达式语句和过程控制语句。过程控制语句又分为条件语句、循环语句和转移语句。
其中,循环过程控制的描述是编程中最大的难点之一,也是程序实现自动运行的基础。
【说明语句】
C++用名称来代表存储空间,涉及存放的数据名称有两类:
一类为变量(或常量),它是由C++内部数据类型定义而产生的;
另一类成为对象(或常对象),它是先有程序员定义类,然后再据此创建(定义)实体而产生的。
在程序中,变量和对象没有本质上的差别,它们都是占据空间的数据描述体,它们都是由具体的数据类型引导、创建,不同的是
变量用的是内部数据类型,对象用的是外部(自定义)数据类型。
说明名称,就要用说明语句,说明语句分为定义语句和声明语句:
(1)声明语句声明某个名称,因为有了名称,在后续语句中才能使用,但若要能真正运行,必须在适当地方提供该名称的定义
(2)定义语句不但声明了名称,而且还给名称分配了存储空间,使之成为一个能够存放数据的实体。大部分情况下,使用定义
语句,而不使用声明语句,只有在复杂的程序结构中为了说明一个名称可以在各处共享,而又要避免多出定义所造成的空间冲突时
才使用声明。
//==================
// cpp1.cpp
// 计算球的表面积
//=================
#include <iostream>
using namespace std;
//-----------------
void sphere(); //声明函数
//-----------------
int main()
{
sphere(); //调用函数
return 0;
}
void sphere() //定义函数
{
double radius,result;
cout<<"please input radius:";
cin>>radius;
result=radius*radius*3.14159*4;
cout<<"result:"<<result<<"/n";
}
我们看到,函数的使用、声明与定义都必须满足一定的形式,在函数使用之前要声明。C++中任何名称在使用之前都要声明。
变量定义和初始化有两种方式:
(1)类型 变量名=初始值; //和C一样
(2)类型 变量名(初始值);
如int i=5;和int i(5);是等价的。
【条件语句】
<if语句>两种形式:
if(条件) 语句;
if(条件) 语句1;else 语句2;
以及变形:if....else if....else;
<条件表达式>
(条件)?表达式1:表达式2;
条件操作符的优先级较低,所以,一般整个条件表达式一般总要带上括号,如:(x?a:b)=327;
<switch语句>
switch(表达式) //这个表达式必须是整型
{
case 常量表达式1:语句1;break;
case 常量表达式2:语句2;break;
...
default:语句n+1;
}
注意事项:
(1)switch括号中的表达式只能是整型、字符型或枚举型,case后面的常量表达式的类型必须与其匹配;
(2)case值即标号,而标号是不能重复的名字,所以每个case常量表达式的值必须互不相同,否则会出现编译错误;
如:case ‘A’:
case 65: //'A'的ascii码是65 相同字面值,标号重复,错误
(3)case语句起标号作用,所以case与default并不改变控制流程,case通常与break语句联用,以保证多路分支的正确实现
(4)case与default标号是与包含它的最小的switch相联系的。
(5)if和switch互相弥补,如果条件是一个范围,则只能用if;而如果是单独的整数值,则可以用switch或if。
【循环语句】
语言循环操作的实现使计算机真正充当了代替人工作的角色,for语句是C++编程中最主要的循环语句。
一个循环的定义,它包括四个部分:
(1)循环初始状态;
(2)能够决定是否中止循环的条件判断;
(3)对上个循环状态的值进行修正;
(4)循环体,即重复执行的语句序列;
<for循环>
语法:for(循环变量初始化;条件判断;循环变量的增量){ 循环体 }
执行逻辑:
(1)循环变量初始化;
(2)条件判断,若为真,则执行(3),若为假则推出;
(3)执行循环体,循环体执行一般是依赖当前循环变量状态的;
(4)循环变量增量—对循环状态进行修正
(5)转到(2)
<while循环>
语法逻辑:
循环变量初始化;
while(条件判断)
{
执行循环体;
循环变量增量;
}
<do while循环>
语法逻辑:
循环变量初始化;
do{
执行循环体;
循环变量增量;
}while(条件判断);
(1)循环变量初始化;
(2)执行循环体;
(3)循环变量增量;
(4)条件判断,若为假则退出;若为真,则转到(2)
可见do-while结构,至少要执行循环体一次,且循环变量的初始化必须在do-while的外部;
编程中,循环语句用的最多的是for循环,很少用到do-while循环。语言在设计的时候,有它的需要性,但随着程序设计方法的
变迁,某种语句表达较常用,而另一种语句表达不常用,这是很正常的。语言总是变得越来越庞大,因为必须要能兼容过去的代码
,而语言无法缩减,除非代之以新的语言。
灵活熟练掌握循环的使用,是编程的基础。
【输入输出语句I/O Statements】
程序运行的最初时候需要数据的引入,结束后需要显示运行结果,输入设备和输出设备各种各样,尽管编程语言本身不跟这些
具体的各不相同的设备打交道,但其开发工具(将程序转换为机器代码)却必须首先能够使用这些设备。
控制这些设备的软件是操作系统,所以C++的工具必须具有针对一定操作系统的操作集合提供给编程人员,这个操作集合就是标
准输入/输出流。
流是同C++语言工具捆绑的资源库,在计算机硬件中,输入/输出设备的底层操作是很复杂的,但通过流的抽象操作,编程人员
可以不必理会底层操作的细节,这些底层操作由操作系统来控制。
|输入设备|—————>|内 存|——————>|输出设备|
键盘、鼠标、文件等 程序/进程 显示器、文件等
cin>> cout<<
<标准I/O流>
C++的标准输入/输出库的头文件为iostream,它不但提供了I/O库,也提供了使用该库的流模式,从“cin>>”流入和“cout<<”
流出道输出设备的操作符,正是流入和流出的形象描述。
<流状态Stream States>
流iostream主管数据类型的识别工作和沟通操作系统,全权负责把流中的数据送到对应的设备上。流的格式操作,如对齐、宽度
定制、精度规定、数制等显示形式也可直接以输出流状态的方式操作。
1、常用的流状态
showpos 在正数(包括0)之前显示+号;
showbase 在十六进制整数前加0x,八进制整数前加O;
uppercase 十六进制格式字母用大写字母表示(默认为小写字母);
showpoint 浮点输出即使小数点后都为0,也要加上小数点;
boolalpha 逻辑值1和0用true和false来表示;
left 左对齐(填充字符在右边);
right 右对齐(填充字符在左边);
dec 十进制显示整数;
hex 十六进制显示整数;
oct 八进制显示整数;
fixed 定点数格式输出;如3.2342
scientific 科学计数法格式输出;如1e-8
例如:
cout<<showpos<<12; //输出:+12
cout<<hex<<18<<" "<<showbase <<18; //输出:12 0x12
cout<<hex<<255<<" "<<uppercase<<255;//输出:ff FF
cout<<123.0<<" "<<showpoint<<123.0; //输出:123 123.000
cout<<(2>3)<<" "<<boolalpha<<(2>3); //输出:0 false
cout<<fixed<<12345.678; //输出:12345.678000
cout<<scientific<<123456.678; //输出:1.234568e+05
取消流状态的操作为:
noshowpos,noshowbase,nouppercase,noshowpoint,noboolalpha,...
注意:
(1)有些流状态是对立的,设置了此,就取消了彼。如left与right是对立的,dec、oct、hex三者是对立的;而fixed、
scientific和一般显示方式三者是对立的,但它们的取消方式比较别扭,为cout捆绑函数调用的方式:
cout.unsetf(ios::scientific);
例:
//===================
//对立流状态演示
//===================
#include <iostream>
using namespace std;
void main()
{
cout<<hex<<12<<"/n";
cout<<oct<<13<<"/n";
cout<<dec<<14<<"/n"; //hex,oct,dec相互对立,设置此就取消了彼
cout<<scientific<<1234.5454<<"/n";
cout<<2343.23432<<"/n"; //当前流状态仍设置着科学计数
cout.unsetf(ios::scientific); //取消scientific状态
cout<<2343.2343<<"/n";
cout<<scientific<<1234.5454<<"/n";
cout<<fixed<<2343.23432<<"/n"; //scientific和fixed相互对立,设置此就取消了彼
}
输出:
--------------------------
c
15
14
1.234545e+003
2.343234e+003
2343.23
1.234545e+003
2343.234320
--------------------------
(2)有些流状态是相容的,设置了此,并不会影响彼(两者同时起作用);因此使用流状态后恢复其默认值应该是一种良好的
习惯。
例:
//===================
//相容流状态演示
//===================
#include <iostream>
using namespace std;
void main()
{
cout<<showpos<<12<<"/n";
cout<<13<<"/n";
cout<<14.0<<"/n"; //流状态设置着showpos状态
cout<<showpoint<<14.0<<"/n"; //showpoint和showpos是相容的,设置互不影响
cout<<noshowpos<<14.0<<"/n"; //取消了showpos状态,但仍保留着showpoint状态
}
输出:
-------------------------
+12
+13
+14
+14.0000
14.0000
--------------------------
2、有参数的三个常用流状态
以下这三个常用的流状态是有参数的,并且不能与流出符<<连用,而是以cout捆绑调用的形式设置:
width(int) //设置显示宽度
fill(char) //设置填充字符
precision(int) //设置有效位数(普通显示方式)或精度(定点或科学计数法方式)
例如:
//===================
//流状态演示
//===================
#include <iostream>
using namespace std;
void main()
{
cout.width(5);
cout.fill('S');
cout<<23<<"/n";
}
输出:
--------------------
SSS23
--------------------
特别注意:width(n)为一次性操作,即第二次显示时将不再有效,默认为width(0),表示仅显示数值。例:
//===================
//流状态演示
//===================
#include <iostream>
using namespace std;
void main()
{
cout.width(5);
cout.fill('S');
cout<<left<<23<<45<<"/n"; //操作相当于:cout<<left<<23;cout<<45;cout<<"/n";
}
输出:
------------------
23SSS45
------------------
这里,cout<<left<<23<<45<<"/n"; 操作相当于:
cout<<left<<23;
cout<<45;
cout<<"/n";
所以23输出时width(5)生效,显示23左对齐,右边填充3个'S';而显示45时width(5)已失效,默认为width(0);
3、与<<连用的设置方式
以上width、fill和precision不能与流出符<<连用,必须通过cout捆绑方式设置,而另一种是与之等效且能与流出符<<连用的设
置方式,但在使用时,必须包含另一个头文件iomanip。
setw(int) //等效于width()
setfill(char) //等效于fill()
setprecision(int) //等效于precision()
例:
//===================
//流状态演示
//===================
#include <iostream>
#include <iomanip> //注意要加上
using namespace std;
void main()
{
cout<<setw(5)<<setfill('$')<<23<<endl;
cout<<left<<setw(5)<<setfill('S')<<23<<45<<"/n";
}
输出:
---------------------
$$$23
23SSS45
---------------------
<文件流File Streams>
现代程序设计,总是将获得的数据的程序和处理数据的程序分离,以使处理速度大幅提高。这样,输入数据多数从数据文件而不
是从标准输入中获得的。
文件操作在操作系统的底层中十分复杂,然而,C++已为我们做了文件操作的绝大部分工作,程序员只要以流的概念来实施文件
操作即可。
文件有两种,一种是文本文件,其任何内容总是与字符码表(如ASCII码)对应。另一种是二进制文件,它不硬性规定与字符码
表的对应关系,而是把内容看成是一连串的0和1,因此操作二进制文件时,虽仍以字节为单位,但往往不考虑字符的识别,更谈不
上数据类型的识别了,而只是字节的整数值。
要进行文件的写入和读出,首先需要有一个对应磁盘存储的文件名称,然后以输入或输出打开方式来规定文件操作的性质,之后
便可以读写了。打开一个文件,就是将实际的文件名与文件流名相对应,程序中只要操作文件流就可以实际地进行文件读写了。
与标准输入/输出一样,流被看做是一种设备、一种概念设备,只要将流与某个实际设备捆绑,对流的操作便是对实际设备的操
作了,这样可以屏蔽低层硬件操作的细节,同时提高程序的可移植性。
文件打开方式(即文件输入流/输出流的建立方式):主要完成实际文件名和流名称的对应,建立文件流,语法格式如下:
ifstream fin(filename,openmode=ios::in); //文件输入流
ofstream fout(filename,openmode=ios::out); //文件输出流
说明:
(1)ifstream和ofstream是类型名,表示输入/输出文件流,其中名称中的i和o表示输入和输出,f表示文件,stream表示流。
因此其定义的对象fin和fout就是文件流的名称了。
(2)filename是外部文件名,一个外部文件名是指设备中的实体,如磁盘文件,它与文件流一一对应。
(3)openmode是打开方式,ifstream默认打开方式是ios::in,表示输入方式,ofstream的默认打开方式是ios::out,表示输出
方式,因此,再打开已经存在的输入文件和新建一个输出文件时可以省略这个参数。
|文件1|————>|内存块|————>|文件2|
输入流 程序 输出流
ifstream in; ofstream out;
in>> out<<
例子:已有一个文本文件source.txt,现在通过程序将source.txt中的文本内容复制到dest.txt(尚未建立)文件中。
source.txt中的内容:
---------------------
hello world!
你好! 世界!
---------------------
//==================
//简单地文本文件复制
//程序1
//==================
#include <iostream>
#include <fstream>
#include <string> //getline()、in>>和out<<需要的头文件
using namespace std;
void main()
{
ifstream in("source.txt");
ofstream out("dest.txt");
for(string s;getline(in,s);)
out<<s<<endl;
cout<<"文件复制成功"<<endl;
}
程序运行结果(查看dest.txt文件):
---------------------------
hello world!
你好! 世界!
---------------------------
#include <iostream>
#include <fstream>
#include <string> //getline()、string s及其<<、>>所需要的头文件
using namespace std;
void main()
{
ifstream in("source.txt");
ofstream out("dest.txt");
for(string s;in>>s;) //注意这里
out<<s<<endl;
cout<<"文件复制成功"<<endl;
}
程序运行结果(查看dest.txt文件):
------------------------------
hello
world!
你好!
世界!
------------------------------
比较两个程序运行结果的区别,原因在于:
流入>>,输入流操作会屏蔽掉输入字符前的所有空格(包括tab、回车等),同时当遇到字符串后第一个空格时停止,因此使用
in>>s,相当于将hello<cr>、world!<cr>、你好!<cr>、世界!<cr>分别写入输出流(输出流out对应的物理文件为dest.txt)。
而采用getline(in,s),它是将输入流中一行的所有内容(以<cr>终止),但不包括最后的回车<cr>,读入string s中。就是说,
getline(in,s)从输入文件流中读入一行数据,放入string变量s中,由于整行读入,而读入到s中时,文件中的每个换行符都被丢掉
了,为了照文件原样输出,在out流上输出s的同时,还要再补上一个回车。
程序中的for循环,每次都用getline来获得输入,如果输入不成功,例如文件结束了或读了一半文件有故障了或者文件非法操作
,或者文件早在打开时便因为不存在而处于糟糕状态等,则循环结束。文件操作大大简化,甚至无须关闭文件操作,因为程序结束
后,它会自动善后处理。
许多输入/输出语句都能返回操作状态(true或flase),例如以下几种流读取方式:
if(cin>>a) cout<<a; //若读入成功,则输出
if(getline(in,str)) cout<<str; //若读入成功,则输出
if((a=cin.get())>0) cout<<a; //若读入字符成功,则输出
if(cin) cin>>a; //若文件流状态正常,则输入
所以在循环读入数据中,常常将读入操作放在循环的条件判断上,这样既省事,又明了。
我们学习程序设计的方法先是模仿,然后举一反三,在自己的知识面还没有铺开到足够解决本领域的问题时,不要将精力过分集
中于某个对全局无足轻重的地方。
关于break应注意它只是用来跳出当前循环体,如果是多重循环一并跳出的话,这要借助于每重循环中的额外条件判断或者是
goto语句来完成。
【小结】
一个完整的程序通常有两个部分,一个是说明部分,另一个是过程部分。过程部分操作和计算语句所要用到的数据类型、变量、
对象和函数都在说明语句中说明。说明语句一般包括变量和对象的定义和函数声明及定义,也包括涉及的类型声明和定义。
在初级编程阶段,可以需要什么就说明什么,随着学习的深入,说明部分将体现程序的架构,会变得越来越重要。在程序规模扩
大后,还要始终保持过程部分的清晰和简明,就必须让过程语句更抽象,因而就得让说明部分做更多的事情,这就是编程方法不断
进化的方向。