第 4 章 编程基础
文章目录
4.1 循环
4.1.1 基于范围的 for 循环(C++11)
C++11 新增了一种循环:基于范围的 for 循环。这简化了一种常见的循环任务:对数组(或容器类,如 vector)的每个元素执行相同的操作,如:
double prices[5] = {4.99, 10.99, 6.87, 7.99, 8.49};
for(double x: prices)
cout<<x<<endl;
该循环将打印 prices 数组中所有的元素。
要修改数组的元素,需要使用不同的循环变量语法:
for (double &x:prices)
x*=0.8;
符号 & 表明 x 是一个引用变量,当修改引用变量时,原始变量(此处为数组元素)也会被修改。
还可以结合使用基于范围的 for 循环和初始化列表:
for (int x: {1,2,3,4,5})
cout<<x<<endl;
4.1.2 循环和文本输入
对于 C 语言中常见的 while((ch = getchar()) != EOF),在 C++ 中,应该用什么来替代呢?考虑下面程序:
#include <iostream>
int main()
{
using namespace std;
char ch;
int count = 0;
cin.get(ch);
while(cin.fail() == false)
{
cout << ch;
++count;
cin.get(ch);
}
cout << endl << count << " characters read\n";
return 0;
}
-
上述程序使用了
cin.get(ch)函数,至此我们已经学过三个版本的cin.get():istream & cin.get(char*, int); char cin.get(void); istream & cin.get(char&); -
当
cin读取到EOF时,cin会将两位eofbit、failbit都设置为 1 1 1,此时可以通过以下两个函数进行检测:-
eof()成员函数
eof()可以查看eofbit是否被设置;如果检测到EOF,则cin.eof()将返回bool类型值true,否则返回false。 -
fail()(用途较前者更广)如果
eofbit或failbit被设置为1,则fail()成员函数将返回true,否则返回false。
-
注意,这两个函数都报告的是最近读取的结果,也就是说,它们是在事后报告,而不是预先报告。因此应将 cin.eof() 或 cin.fail() 测试放在读取后。
当 cin 方法检测到 EOF 后,cin 将不再读取输入,再次调用 cin 也不管用。在这种情况下,cin.clear() 方法将清除 EOF 标记,使输入继续进行。
有没有更简短的检测 EOF 的方法呢?istream 类提供了一个可以将 istream 对象转换为 bool 值的函数。当最后一次读取成功时,则转换得到的 bool 值为 true;否则为 false。这意味着可以将 while(cin.fail() == false) 简写成 while(cin)。这比 while(!cin.eof()) 或 while(!cin.fail()) 更加通用。
最后,由于 cin.get(ch) 返回 cin 对象,所以可以将循环精简成以下格式:while(cin.get(ch))。
-
C++ 中的
getchar()和putchar()熟悉 C 的用户可能更喜欢
getchar()和putchar(),它们在 C++ 中仍然适用。也可以使用 C++ 中istream和ostream类中类似功能的成员函数:char ch; ch = cin.get(); //ch=getchar() cout.put(ch); //putchar(ch)所以
while((ch=getchar())!=EOF)也可以写成while((ch=cin.get())!=EOF)。值得一提的是,cin.get()读到EOF后也会终止cin的任何读入,如:#include <iostream> int main() { using namespace std; char ch; while((ch=cin.get())!=EOF) cout << ch << endl; cin.get(); cin.get(); char s[10]; cin >> s; cout << s; }下面代码中,当
cin.get()读取到EOF后将退出while循环,并且终止后面所有的cin读入,所以字符串s将不会有任何输入。
4.2 分支语句和逻辑运算符
4.2.1 逻辑运算符的另一种表示方式
标识符 and、or 和 not 都是 C++ 关键字,可以分别代替 &&、|| 和 !。
4.2.2 读取数字的循环
考虑下面一种情形:
int n;
cin>>n;
如果用户输入一个单词,而不是一个数字,会发生什么呢?发生这种类型不匹配的情况时,将发生 4 4 4 种情况:
n的值保持不变,本次输入将被跳过- 不匹配的输入将被留在输入队列中
cin对象中的一个错误标记将被设置- 此时
(bool) cin等于false
要继续输入,需要使用 clear() 方法重置错误输入标记。
#include <iostream>
const int Max = 5;
int main()
{
using namespace std;
double fish[Max];
cout << "Please enter the weights of your fish.\n";
cout << "You may enter up to " << Max << " fish <q to terminate>.\n";
cout << "fish #1: ";
int i=0;
while(i<Max&&cin>>fish[i])
{
if(++i<Max)
cout << "fish #" << i + 1 << ": ";
}
cout << "There are " << i << " fishes.\n";
cin.clear();
char s[10];
cin >> s;
cout << "s: " << s;
}
考虑上面的程序,当用户输入 q 时,cin>>fish[i] 转换为 bool 类型为假,且将跳过本次输入(下次输入将从 cin>>s 开始),退出 while 循环。随后,clear() 方法将重置错误标记(不重置错误标记无法再读入数据),然后 s 将读取输入的 q 并输出。注意,此处 clear() 并不会清空输入队列。而 cin 在读取到 Ctrl + Z 时会清空输入队列。
4.2.3 简单文件输入/输出
-
cin处理输入的方式假设有以下输入行:
38.5 19.2来看一下使用不同数据类型的变量来存储时,
cin处理输入行的方式:-
charchar ch; cin >> ch;cin会将输入行中的第一个字符3赋给ch,输入队列中的下一个字符为 8 8 8,下一次处理输入将从 8 8 8 开始。 -
intint n; cin >> n;在这种情况下,
cin将不断读取,直到遇到非数字字符。也就是说,n将等于 38 38 38,下一次输入将从.开始。 -
doubledouble x; cin >> x;在这种情况下,
cin将不断读取,直到遇到第一个不属于浮点数的字符。也就是说x将等于38.5,下一次输入将从开始。 -
char数组char word[50]; cin >> word; //case 1 cin.getline(word, 50); //case 2在第一种情况下,
cin将不断读取,直到遇到空白字符。也就是说,cin会将38.5这 4 4 4 个字符的编码存储到word中,并在末尾加上一个空字符(\0)。下一次输入将从 开始。在第二种情况下,
cin将不断读取,直到遇到换行符,随后换行符会被丢弃,下一次输入将从下一行开始。
-
-
使用文件输入/输出
在 C 语言中,常见的文件操作为
FILE* fp = fopen()等,在 C++ 中,更强调用流的概念。下面分文件输入、输出讨论 C++ 的这种特性。-
文件输出
文件输出需要按照以下几个操作:
- 必须包含头文件
#include <fstream>,该头文件定义了用于输出的ofstream类 - 需要声明
ofstream对象 - 需要将
ofstream对象与文件关联起来。方法之一是使用open()方法 - 像使用
cout那样使用该ofstream对象 - 使用完文件之后,应使用
close()方法将其关闭
例如:
#include <iostream> #include <fstream> using namespace std; int main() { ofstream fout; fout.open("test.txt"); fout << "This is a test."; fout.close(); return 0; } - 必须包含头文件
-
文件输入
文件输入的基本操作和文件输出类似:
- 必须包含头文件
#include <fstream>,该头文件定义了用于输入的ifstream类 - 需要声明
ifstream对象 - 需要将
ifstream对象与文件关联起来。方法之一是使用open()方法 - 像使用
cin那样使用该ifstream对象 - 使用完文件之后,应使用
close()方法将其关闭
例如:
#include <iostream> #include <fstream> using namespace std; int main() { ifstream fin; fin.open("test.txt"); char s[15]; fin >> s; cout << s; fin.close(); return 0; }如果试图打开一个不存在的文件,将导致后面使用
ifstream对象进行输入时失败。检查文件是否被成功打开的方法之一是使用is_open(),当文件成功打开时,该方法返回true。可以参考以下代码:fin.open("test.txt"); if(!fin.is_open()) exit(EXIT_FAILURE); //终止程序运行方法
is_open()是 C++ 中相对较新的内容。C++ 较老的方法是good(),下面程序是一个示例:#include <iostream> #include <fstream> #include <cstdlib> using namespace std; const int SIZE = 60; int main() { char filename[SIZE]; ifstream inFile; cout << "Enter name of data file: "; cin.getline(filename, SIZE); inFile.open(filename); if(!inFile.is_open()) { cout << "Could not open the file " << filename << endl; cout << "Program terminating.\n"; exit(EXIT_FAILURE); } double value; double sum = 0.0; int count = 0; inFile >> value; while(inFile.good()) { sum += value; count++; inFile >> value; } //上述循环可以简化成 // while(inFile >> value) //{ // sum += value; // count++; //} if(inFile.eof()) cout << "End of file reached.\n"; else if(inFile.fail()) cout << "Input terminated by data mismatch.\n"; else cout << "Input terminated for unknown reason.\n"; if(count==0 cout << "No data processed.\n"; else { cout << "Items read: " << count << endl; cout << "Sum: " << sum << endl; cout << "Average: " << sum / count << endl; } inFile.close(); return 0; }读取文件时,有几点需要检查。首先,程序读取文件时不应超过
EOF。如果最后一次读取数据时遇到EOF,方法eof()将返回true。其次,程序可能遇到类型不匹配的情况(如给int类型变量输入字符),在这种情况下,方法fail()将返回true(如果遇到了EOF,该方法也将返回true)。最后,可能出现意外问题,如文件受损或硬件故障。如果最后一次读取文件发生了这样的问题,方法bad()将返回true。不要分别检查这些情况,一种更简单的方法是使用
good()方法,该方法在没有发生任何错误时返回true。然后,当循环终止时,再分别检查原因,正如程序逻辑展示的那样。 - 必须包含头文件
-
4.3 函数
4.3.1 函数与 string 对象
可以像字符串一样将 string 对象作为函数的参数。但与字符串传的是地址不同,string 对象是按值传递,也就是说在函数内部修改将不会改变 string 对象,例如:
#include <iostream>
#include <string>
using namespace std;
void f(string s1, char* s2)
{
s1[0] = '1';
s2[0] = '1';
}
int main()
{
string s1 = "abc";
char s2[] = "abc";
f(s1, s2);
cout << "s1: " << s1 << endl;
cout << "s2: " << s2 << endl;
}
在上面的程序中,字符串 s1 将不会被修改,而字符串 s2 将被修改为 1bc。
4.3.2 函数与 array 对象
传递 array 作为函数参数也和传递数组不一样,在函数内部修改前者将不起作用。如果想要在函数内部修改 array 对象,需要将该对象的地址传递给函数。例如:
std::array<double, 4> expenses;
//展示数组
void show(std::array<double,4> da);
//修改数组
void fill(std::array<double,4> *pa);
上述传递方法都有缺点。函数 show() 按值传递数组,效率太低;函数 fill() 按址传递数组,操作不便。下一章讨论的按引用传递可以解决这两个问题。
4.3.3 函数指针
-
函数的地址
获取函数的地址很简单:函数名就是函数的地址。例如,
think()函数的地址就是think。 -
声明函数指针
通常要声明指向特定类型的函数的指针,可以首先编写这种函数的原型,然后用
(*pf)替换函数名。这样,pf就是这类函数的指针。如:double pam (int); //指针如下 double (*pf) (int) = pam; -
使用指针来调用参数
使用指针参数有两种方式:
//case 1 double x = (*pf)(1); //case 2 double x = pf(2);解引用函数指针或者直接使用函数指针都可以作为调用函数的方式。
4.3.4 函数指针数组
考虑下面三个函数:
const double * f1(const double ar[], int n);
const double * f2(const double [], int);
const double * f3(const double *, int);
这三个函数的形参列表看似不同,实则相同。接下来,假设要声明一个指针 p1,指向这三个函数之一,则只需要将目标函数的函数名换成 (*p1):
const double * (*p1)(const double *, int) = f1;
可以使用 auto 自动类型推断,简化代码:
auto p2 = f2;
还可以使用 typedef 进行简化:
typedef const double *(*p_fun)(const double*, int);
p_fun = f1;
如果需要使用这三个函数,则用函数指针数组将更方便:
const double * (*pa[3])(const double *, int) = {f1,f2,f3};
p_fun pa[3] = {f1,f2,f3};
这里不能使用 auto。自动类型推断只能用于单值初始化,而不能用于初始化列表。
如果想要声明一个指向函数指针数组的指针,应该如何创建呢?显然,应该在某个地方添加一个 *。具体地说,如果这个指针名为 pd,则需要指出它是一个指针,而不是一个数组。这意味着声明的核心部分应该为:(*pd),指向的类型是包含三个函数指针的数组,最终声明如下:
//pd指针指向的类型如下
const double * (*()[3])(const double *, int);
//最终声明
const double * (*(*pd)[3])(const double *, int) = &pa;
//typedef
p_fun (*pd)[3] = &pa;

被折叠的 条评论
为什么被折叠?



