4、复合类型:
* 复合类型(类除外)
* new和delete以及如何使用它们来管理数据
* string类(另一种处理字符串的途径)
4.1 数组
声明数组的通用格式是:
arrayType arrayName[arraySize]
arraySize必须是可知数据,可以使用 new 运算符来避开这种限制;
【注:】
1、编译器不会检查使用的下标是否有效。若对无效的下标进行赋值,编译器不会指出错误。
但程序运行后,赋值可能引发问题,它可能破坏数据或代码,也可能导致程序异常终止。so,必须确保只使用有效的下标值。
2、
sizeof是返回类型或者数据对象的长度(单位:字节);
如果sizeof用于数组名,得到的是整个数组的字节数;
如果用于数组元素,得到是元素的长度(单位:字节);
数组初始化的规则:
1. 只有在定义数组时可以初始化;然而,可以使用下标的方式来进行赋值;
2. 如果没有对定义的数组进行初始化,则元素的值是不确定的,意味着元素的值为以前驻留在该内存单元中的值;
3. 初始化时,若仅对一部分元素进行赋值,则其他的元素值为0;
4. 因此将数组中所有元素的值赋值为0,仅需要arrayName[500] = {0};
5. 如果初始化数组时,[]内的值为空,那么编译器将计算元素个数;
4.2 字符串
C++处理字符串的两种方式:
* C-风格字符串;
* string类;
一、C-风格字符串:
C-风格字符串有一个特殊的规则:以空字符串结尾,空字符串被写作\0,其ASCII码值为0,用来标记字符串的结尾。
char dog[8] = {'b', 'e', 'a', 'u', 'x', '', 'I', 'i'};
char cat[8] = {'f', 'a', 't', 'e', 's', 's', 'a', '\0'};//只有这个是字符串
C++有很多字符串处理函数,比如cout,它们逐个处理字符串中的字符,直至遇到空字符;
cout显示cat将显示7个字符,遇到空字符串结束;
cout显示dog显示8个字符,并接着讲内存中随后各个字节解释为要打印的字符,直至遇到空字符结束;
so,不应将不是字符串的char数组当作字符串来处理。
一种更好的方法,将字符数组初始化为字符串-这种字符串称为字符串常量或字符串字面值;
char birds[11] = "Mr. Cheeps";
char fish[] = "Bubbles";
这种初始化字符串的方式将隐式包含空字符串。
另外,各种C++输入工具通过键盘输入,将字符串都入到char数组时,将自动加上空字符作为结尾;
当然,应当确保数组足够大,能够存储字符串的所有字符-包括空字符;
让数组比字符串长没有什么害处, 只是会浪费一些空间,因为字符串的函数根据空字符串的位置[遇到空字符则停止处理],而不是数组长度进行处理。
注:不能将'S' 和 "S"互换:
'S'是字符常量,是83的另一种写法,是字符串编码的简写表示。
"S"是字符串常量,代表字符S和\0组成的字符串,另外,“S”实际上表示的是字符串所在的内存地址。
4.2.1 拼接字符串常量
任何两个由空白(空格、制表符和换行符)分隔的字符串常量将自动拼接成一个。【拼接时不会在被连接的字符串之间添加空格】
4.2.2 在数组中使用字符串
将字符串存储在数组中,两种方法:
* 将数组初始化为字符串常量
* 将键盘或者文件都入到数组中,通过cin
char name1[15] = "Basicman" ;
char name2[15] = "C++owboy";
strlen(name1);
sizeof(name1);
sizeof指出整个数组的长度:15个字符;strlen返回存储在数组中的字符串的长度:8(strlen只计算可见的字符,而不把空字符计算在内),但数组长度需要strlen()+1;
另外,对于name2,若执行该操作,name2[3]='\0';这使得该字符串在第3个字符的时候即结束,虽然数组中还有其他的字符。
4.2.3 字符串输入
#include<iostream>
int main() {
using namespace std;
const int ArySize = 20;
char name[ArySize];
char dessert[ArySize];
cout << "Enter your name:\n";
cin >> name;
cout << "Enter your favorite dessert:\n";
cin >> dessert;
cout << "I have some desicious dessert " << dessert ;
cout << " for you " << name << endl;
return 0;
}
具体输出情况是:
Enter your name:
Jason Yang
Enter your favorite dessert:
I have some desicious dessert Yang for you Jason
由于不能通过键盘输入空字符串\0,因此cin使用空白(空格、换行符或制表符)来确定字符串的结束位置。这意味着cin在获取字符数组输入时只读取一个单词。读取该单词之后,cin将该单词放入数组中,并自动添加空字符。这样实际结果是,cin吧Jason作为第一个字符,并将它放到name中,把Yang放到输入队列中。当cin在输入队列中搜索甜点时,发现了Yang,因此读取Yang,将Yang放入到数组dessert中。
另一个问题是,输入字符串的长度可能大于目标数组长度,那么,该怎么办呢?这需要使用cin的更高级特性。在17章进行介绍。
4.2.4 每次读取一行字符串输入
面向行的输入:
* cin类的getline
* cin类的get
getline丢弃换行符,get将换行符放入到输入队列中;
1、面向行的输入:getline()
通过回车输入的换行符来确定字符串的结束;
* 有两个参数,第一个参数表示要输入的数组名称,第二个参数代码要输入的字符串的长度;
* 如果参数为20,则该函数仅接受19个字符,剩余的一个字符用来存储在结尾处自动添加的空字符;
* getline在读取指定数目的字符或者遇到换行符的时候停止;
2、面向行的输入:get()
使用方式与getline相同,不同点:get读取换行符并保留在输入队列中。
cin.get(name, ArSize);
cin.get(dessert, ArSize);
第一次调用,将换行符留在输入队列中,因此第二次调用看的到第一个字符就是换行符,且认为已经达到行尾。
get有一种变体,cin.get()可读取下一个字符(即使是换行符)。
cin.get(name, ArSize);
cin.get();
cin.get(dessert, ArSize);
另一种使用get的方式是将两个类成员函数连接起来:
cin.get(name, ArSize).get();
因为cin.get(name, ArSize)返回一个cin对象;
下面的语句将输入的两行语句分别读入到数组name和dessert中。
cin.getline(name, ArSize).getline(dessert, ArSize);
3、为什么使用get而不是getline呢?
* 因为老式实现没有getline;
* 怎么确定停止读取的原因是因为换行符或者是数组已经满了呢?查看下一个输入符,如果是换行符,说明已经读取了整行;如果不是换行符说明数组已经满了,该行还有其他的输入。
4.2.5 混合输入字符串和数字
#include<iostream>
int main() {
using namespace std;
cout << "Enter the year:" << endl;
int year;
cin >> year;
cout << "Enter the address: \n";
char address[80];
cin.getline(address, 80);
cout << "year: " << year << endl;
cout << "address: " << address << endl;
return 0;
}
运行结果是:
Enter the year:
2000
Enter the address:
year: 2000
address:
用户根本没有输入地址的机会,因为cin读取年份时,将回车键生成的换行符放在了输入队列中。cin.getline看到换行符后,认为是一个空行,将空字符串赋值给address。
解决方法:丢弃换行符。
方法:
* 没有任何参数的cin.get()
* 使用一个char参数的cin.get(ch);
C++ 通常使用指针而不是数组来处理字符串。我们再介绍指针之后,在介绍字符串方面的特性。
下面介绍一种新的字符串处理方式:string类。
4.3 String类介绍
要使用string类,必须包含头文件string,string类位于命名空间std中。需要添加using编译指令;
string类与使用数组字符串有很多相似之处:
* 可以使用C-字符串风格来初始化string对象
* 可以使用cin将键盘输入存储到string对象
* 可以使用cout来显示string对象
* 可以使用数组表示法来访问存储在string对象中的字符
string对象与数组字符串的主要区别就是:
可以将string声明为简单变量;
类设计让程序自动处理string的大小;
4.3.2 赋值、拼接和附加
可以将一个string对象的值赋值给另一个string对象;可以使用+将两个字符串连接起来,可以使用+=将字符串附加到string对象的末尾;
4.3.3 string类的其他操作
在有string类之前,对于C-风格字符串,使用C语言库中的函数来完成,头文件ctring提供了这些函数。
eg:
* strcpy()将字符串赋值到字符数组
* strcat()将字符串附加到字符数组中
不同之处:
赋值:
* string,赋值操作:str2 = str1;
* C-风格字符换,赋值操作:strcpy(ch2, ch1);
附加:
* string,附加操作:str2 += " Boy";
* C-风格字符串,附加操作:strcat(ch2, " Girl");
另外,使用字符数组,总是存在目标数组长度过小,无法存储指定信息的危险。
函数strcat如果试图将全部12个字符复制到数组site中,这将覆盖相邻的内存。这可能导致程序种植,或者程序继续运行,但数据被损坏。
string具有自动调节大小的功能。
获取长度:
* strlen(ch2)
* str2.size()
4.3.4 string类I/O
* 使用cin >> 将输入存储到string对象中
* 使用cout << 显示string对象
其句法与C-风格字符串相似。但每次读取一行而不是一个单词时,使用的句法不同。
* cin.getline(charr, 20);
* getline(cin, strr);
代码如下:
#include<iostream>
#include<string>
#include<cstring>
int main() {
using namespace std;
char charr[20];
string strr;
cout << "the length of charr is: " << strlen(charr) << endl;
cout << "the length of strr is: " << strr.size() << endl;
cout << "Enter the charr: " << endl;
cin.getline(charr, 20);
cout << "Enter the strr: " << endl;
getline(cin, strr);
cout << "the length of charr is: " << strlen(charr) << endl;
cout << "the length of strr is: " << strr.size() << endl;
return 0;
}
运行结果如下:
1. 未初始化的数组内容是未定义的;其次,strlen从数组的第一个元素开始计算字节数,直至遇到空字符;
2. 未初始化的string对象长度自动被设置为0;
3. C-风格字符串getline用法,cin.getline(charr, 20);
4. string getline用法,getline(cin, strr); 表明getline不是类方法,将cin作为参数,指出要到哪里查找输入,未指定长度,string对象会自动调整大小;
问题:为何一个getline是类方法,一个不是呢?
4.4 结构简介
结构:用户定义类型,可以存储多种类型的数据。
使用:
1. 定义结构描述--描述标记能够存储在结构中的各种数据类型
2. 按照描述创建结构变量
struct student {
int age;
string name;
};
* 定义结构之后,就可以创建这种类型的变量了;
* C++ 允许在声明结构变量时省略struct关键字,结构标记的用法和基本类型的用法相同;
* 可以使用成员访问符(.)来访问成员;
eg:声明和初始化(逗号分割,并用花括号括起)
student stu1 = {
24,
"cql"
};
4.4.1
定义结构类型的位置很重要:
* 定义在main函数中,内部声明仅可以被该声明所属的函数使用;
* 放到main的外面,位于函数外面的称为外部变量,外部声明可以被后面的所有函数访问;
C++ 也允许定义外部变量,但不提倡声明外部变量,提倡外部声明结构,另外,外部声明符号常量通常更合理。
4.4.3
C++ 结构体允许将string作为成员吗?可以,只要可以编译通过。
4.4.4 其他结构属性
* 可以将结构作为参数进行传递,以及让函数返回一个结构;
* 使用赋值运算符(=)将结构赋值给另一个结构,即使结构中包含数组;这种赋值被称为成员赋值;
* 可以同时完成定义结构和声明结构变量
struct student {
int age;
string name;
} stu1, stu2;
甚至可以以这种方式既声明又初始化:
struct student {
int age;
string name;
} stu1 = {
24,
"cql"
};
另外,C++的结构不仅仅可以有成员变量之外,还可以有成员函数。这点将在类中介绍
4.4.5结构数组
创建元素为结构的数组;
struct student {
int age;
string name;
} stu1 = {
24,
"cql"
};
初始化结构数组的规则可以结合初始化数组和初始化结构的规则。
由于数组中的每个元素都是结构,因此可以使用初始化结构的方式来提供值。最终结果为一个逗号分割,花括号括起来的列表。
4.5 共用体
一种数据格式,可以存储多个不同的数据类型,但同时只能存储其中的一种。
union one4all {
int int_val;
double double_val;
string string_val;
};
one4all可以存储int、double、string类型,但条件是在不同的时间;
one4all onetest;
onetest.int_val = 10;
cout << onetest.int_val << endl;
onetest.double_val = 13.8; (int_val has lost)
cout << onetest.double_val << endl;
由于共用体可以每次只可能存储一个值,因此它必须有足够的空间来存储最大的成员。所以,共用体的最大程度为其最大成员的长度。
4.6 枚举
。。。。。。
4.7指针和自由存储空间
计算机在存储数据时必须跟踪的3种基本属性;
* 信息存储在何处;
* 存储的值是多少;
* 存储的信息类型;
实现上述策略的方法:
* 定义一个简单变量
* 使用指针
* 指针是一种变量,存储的是值的地址,而不是值本身;
* 获取常规变量的地址方法:使用地址运算符&
* 使用常规变量时,值是指定的量,而地址是派生量
* 使用指针时,地址是指定的量,而值是派生量
* *运算符,将其应用于指针,可以得到该地址处存储的值
指针策略(C++ 内存管理编程理念的核心):
int updates = 6;
int * p_updates;
p_updates = &updates;
cout << "the value of updates is: " << updates << endl;
cout << ", *p_updates is: " << *p_updates << endl;
cout << "the address of updates is: " << &updates << endl;
cout << ", p_updates is: " << p_updates << endl;
*p_updates = *p_updates + 1;
cout << "the value of updates is: " << updates << endl;
其实,p_updates和updates只不过是硬币的正反面:
* p_updates表示地址,使用*可获得值;
* updates表示值,使用&可获得地址;
由于p_updates指向updates,因此,*p_updates和updates是等价的,可以像使用int变量那样使用*p_updates,甚至可以赋值给*p_updates,这样将修改指向的值,即updates;
4.7.1 声明和初始化指针
计算机需要跟踪指针指向的值的类型,例如,char与double的地址看上去没有什么区别,char和double使用的字节数不同,内部表示格式也不同,因此,在声明指针时,需要指明指针指向的数据的类型。
* int*是一种复合类型,是指向int的指针
声明方式如下都可以:
* int *ptr
* int* ptr
* int*prt
* int * ptr
使用同样的语法来声明其他类型的指针变量:
char * char_ptr;
double * dou_prt;
虽然char_ptr和 dou_prt指向两种长度不同的数据类型,但是这两个变量本身的长度通常是相同的。
地址需要2个字节还是4个字节(取决于具体的计算机系统)(有些系统可能需要更大的地址,系统可以针对不同类型使用不同长度的地址)。
4.7.2 指针的危险
C++在创建指针时,将分配用来存储地址的内存,但不会分配用来存储地址指向的值得内存。
int * fellow;
*fellow = 223344;
fellow指向哪里呢?并没有将地址赋值给fellow,那么223344将放在哪里呢?
由于fellow没有被初始化,它里面可能有任何值,不管是什么,都将它解释为存储223344的地址。
比如,fellow碰巧值为1200,那么1200存储的值就是223344,即使恰巧1200存储的是代码的地址。
so,警告:一定要在指针使用*运算符之前,将指针初始化为一个确定的、适当的地址。
4.7.3 指针的数字
4.7.4 使用new来分配内存
4.7.5 使用delete释放内存
4.7.6 使用new来创建动态数组
。。。。。。。。。。
5
5.1 for循环
5.1.1 for循环的组成部分
其中注意,测试表达式的值不一定为true或者false,可以使用任意表达式,C++将把结果强制转换成true或者false;
若为0,为false;若非零,为true;
C++程序会在需要整数值的时候将true和false转换为1和0,在需要bool值的地方将0和非零转换成false和true;
1、表达式和语句
(任何值)或者(任何有效的值和运算符的组合)都是表达式;每个表达式都有值
eg:10、22+27、x=22;x=22这个表达式的值为22;
x = y = z = 0;(赋值运算符从右到左执行)
int x;
cout << "1: " << (x = 1000) << endl;
cout << "2: " << (x < 3) <<endl;
cout << "3: " << (x > 3) <<endl;
cout.setf(ios::boolalpha);
cout << "2: " << (x < 3) <<endl;
cout << "3: " << (x > 3) <<endl;
输出结果:
通常,cout在现实bool值之前会将他们转换成int,但cout.setf(ios::boolalpha);函数设置了一个标记,
该标记命令cout显示true或者false,而不是1或者0;(当然,前提是运算结果是bool)
从表达式到语句的转换:只需要加上分号即可。
2、非表达式和语句
* 对任何表达式加上分号都可以成为语句
* 但语句去掉分号并不一样是表达式
3、修改规则
5.1.2 回到for循环
5.1.3 改变步长
5.1.4 使用for循环访问字符串
5.1.5 递增运算符(++)和递减运算符(--)
5.1.6 副作用和顺序点
5.1.7 前缀和后缀格式
前缀和后缀对执行速度有细微的差别;
* 前缀是将值加1,返回结果;
* 后缀首先复制一个副本,将值加1,返回副本;
相比而言,前缀效率比后缀效率高;
5.1.8 递增\递减运算符和指针
将*和++同时用于指针;怎么处理,取决于运算符的位置和优先级。
* 前缀递增、前缀递减和*优先级相同;从右到左的方式进行结合;
* 后缀递增、后缀递减的优先级比前缀运算符的优先级高;从左到右的方式进行结合;
int age[10] = {10, 20, 30, 40, 50};
int *p;
p = age;
cout << *p << endl;
*p++;// 将p的地址+1,然后获取值,age[1]
cout << *p << endl;
++*p;// 获取age[1]的值,然后+1
cout << *p << endl;
注意:
指针递增和递减遵循指针算数规则。因此,如果p指向数组的第一个元素,++p将修改p,使之指向第二个元素。
5.1.9 组合赋值运算符
-= += *= /= %=
5.1.10 复合语句(语句块)
多条语句使用花括号括起来;
* 复合语句有一个有趣的特性,语句块中定义一个新的变量,则当程序执行该语句块中的语句时,该变量才存在。
执行完该语句块,变量将被释放。这表明此变量仅在该语句块中才是有用的。
* 若语句块中声明一个变量,外部声明也有一个重名的变量,该怎么处理呢?
* 在声明位置到内部语句块结束的范围之内,新变量隐藏旧变量
5.1.11 其他语法技巧---逗号运算符
逗号运算符允许将两个表达式放到只允许放一个表达式的地方。
string word;
char temp;
cin >> word;
for (int i = 0, j = (word.size()-1); i < j; i++, j--) {
temp = word[i];
word[i] = word[j];
word[j] = temp;
}
cout << word << endl;
到目前为止,逗号运算符最常见的用途是将两个或者更多的表达式放到一个for循环表达式中。
另外,
* 逗号运算符先计算第一个表达式,在计算第二个表达式
int m, n, p;
p = (m = 2, n = 2 * m);
cout << p << endl;
* 其次,逗号表达式的值是第二部分的值,如上,值为4;
* 逗号运算符的优先级是最低的;
5.1.12 关系表达式
C++提供了6种关系运算符来对数字进行比较。由于字符用其ASCII码表示,因此也可以进行关系比较。
不能将它们用于C-风格字符串,但可以用于string类对象。
< <= == > >= !=
5.1.13 赋值、比较和可能犯的错误
使用=赋值,使用==比较
5.1.14 C-风格字符串比较
假设要知道字符数组中的字符串是不是mate,word是数组名。
word="mate"
注:数组名是数组的地址,字符串常量"mate"也是其地址。这就是判断两个字符串是否存储在相同地址上
使用strcmp()函数。
接受两个字符串地址作为参数,意味着参数可以是指针、字符串常量或者字符数组名。
若两个字符串相同,返回0;
若第一个字符串按字母排序排在第二个字符串之前,返回负值;
若第一个字符串按字母排序排在第二个字符串之后,返回正值;
实际上,按“系统排列顺序”比按“字母排列顺序”更准确。这意味着字符是根据字符的系统编码来比较的。
C-风格字符串通过空字符来定义,而不是其所在数组的长度。这意味着,即使两个字符串被存储在不同长度的数组中,也可能是相同的。
虽然不能使用关系运算符来比较字符串,但却可以来比较字符,因为字符实际上就是整型。
5.1.15 比较string类字符串
类设计能够使用关系运算符进行比较。
之所以可行,是因为类函数重载了这些运算符。具体在12章介绍。
string类重载运算符!=使用条件:至少有一个操作数为string对象,另一个操作数可以是string对象或者C-风格字符串。
5.2 while循环
5.2.1 for 与while循环
5.2.2 等待一段时间,编写延时循环
。。。。
5.3 do和while循环
5.4 基于范围的for循环(C++11)
5.5 循环和文本输入
cin对象支持3中不同模式的单字符输入,其用户接口各不相同
5.5.1 使用原始的cin进行输入
cin忽略空格和换行符
char a;
int count = 0;
cin >> a;
while(a != '#'){
count++;
cin >> a;
}
cout << count << endl;
5.5.2 使用cin.get(char)进行补救
能够读取包括空格、制表符和换行符;
如果熟悉C语言,可能以为这个程序存在严重的错误。
cin.get(ch)调用将一个值放到ch边两种,意味着改变该变量的值。
C语言中,要修改变量的值,必须将变量的地址传递给函数。
但cin.get(ch),传递的是ch,而不是&ch。在C语言中,这样的代码无效,但在C++中,这样的代码有效。
只要函数将参数声明为引用即可。第8张介绍。
另外,通常,在C++中传递的参数的工作方式与C语言相同。然后,cin.get(ch)并不是这样;
char a;
int count = 0;
cin.get(a);
while(a != '#'){
count++;
cin.get(a);
}
cout << count << endl;
5.5.3 使用哪一个cin.get()
cin.get()的三种使用方法:
cin.get(arrayName, arraySize);
cin.get();
cin.get(ch);
函数重载,函数重载允许创建多个同名函数,条件式参数列表不同;
函数以不同的方式或针对不同类型执行相同的任务;
5.5.4 文件尾条件
。。。。。。。。。。。
5.6 嵌套循环和二维数组
5.6.1 初始化二维数组
5.6.2 使用二维数组
在此,仅介绍下声明城市数组的集中方式;
char 指针
const char * cities[5] = {
"Beijing",
"Shanghai",
"Henan",
"Tianjin",
"Ganchazhuang"
};
cout << cities[0] << endl;
char数组的数组:
const char cities[5][25] = {
"Beijing",
"Shanghai",
"Henan",
"Tianjin",
"Ganchazhuang"
};
cout << cities[0] << endl;
string对象数组:
const string cities[5] = {
"Beijing",
"Shanghai",
"Henan",
"Tianjin",
"Ganchazhuang"
};
cout << cities[0] << endl;
6 分支语句与逻辑运算符
* 条件运算符;
* 逻辑运算符;
* 文件输入\输出;
6.1 if语句
和循环测试条件一样,if测试条件也被将值转换为bool值,因此0将被转换为false,非零为true。
6.1.1 if else 语句
6.1.2 格式化if else语句
对多条语句添加大括号;
6.1.3 if else if else 结构
6.2 逻辑表达式
逻辑OR || 逻辑AND && 逻辑NOT !
6.2.1 逻辑OR ||
char yourchoice;
cout << "Enter your choice: " << endl;
cin >> yourchoice;
if (yourchoice == 'Y' || yourchoice == 'y')
cout << "you choose" << endl;
else if (yourchoice == 'N' || yourchoice == 'n')
cout << "you don't choose'" << endl;
else
cout << "no choice" << endl;
由于程序只读取一个字符,因此只读取响应的第一个字符。意味着用户可以用NO进行回答,程序只读取N.
然而,如果程序后面再读取输入时,将从O开始读取,O已经进入的输入队列了。
6.2.2 逻辑AND &&
char * ch_ptr = "my god";
cout << ch_ptr << endl;
程序正常运行,但该方式不建议使用,已经被弃用
6.2.3 用&&来设置取值范围
6.2.4 逻辑NOT运算符!
6.2.5 逻辑运算符细节
逻辑OR和逻辑AND运算符优先级 < 关系运算符
!运算符优先级> 所有关系运算符和算术运算符>逻辑OR和逻辑AND
6.2.6 其他表示方式
* C++ 可用and 、or 和not;
* C可用and、or和not,前提:包含头文件iso646.h
6.3 字符函数库cctype
cctype:与字符相关的、方面的函数软件包;
函数名称返回值isalnum()如果参数是字母数字,即字母或数字,该函数返回trueisalpha()如果参数是字母,该函数返回真isblank()如果参数是空格或水平制表符,该函数返回trueiscntrl()如果参数是控制字符,该函数返回trueisdigit()如果参数是数字(0~9),该函数返回trueisgraph()如果参数是除空格之外的打印字符,该函数返回trueislower()如果参数是小写字母,该函数返回trueisprint()如果参数是打印字符(包括空格),该函数返回trueispunct()如果参数是标点符号,该函数返回trueisspace()如果参数是标准空白字符,如空格、进纸、换行符、回车
、水平制表符或者垂直制表符,该函数返回true
isupper()如果参数是大写字母,该函数返回trueisxdigit()如果参数是十六进制的数字,即0~9、a~f、A~F,该函数返回truetolower()如果参数是大写字符,则返回其小写,否则返回该参数toupper()
如果参数是小写字母,则返回其大写,否则返回该参数
什么是控制字符?
什么是打印字符?
6.4 ?:运算符
6.5 switch语句
switch(integer-expression)
{
case label1: statement(s)
case label2: statement(s)
...
default statement(s)
}
integer-expression必须是一个结果为整数值的表达式。
每个标签都必须是整数常量表达式。
最常见的标签是int或char常量、枚举量。
6.5.1 将枚举量用作标签
cin无法识别枚举类型(它不知道程序员是如何定义他们的)。因此要求用户输入一个整数。
当switch语句将int值和枚举量标签进行比较时,将枚举量提升为int。
另外,在while循环测试条件中,也会将枚举量提升为int类型。
enum {red, green};
int code;
cin >> code;
while(code >= red && code <= black) {
switch(code){
case red:
cout << "oh,red." << endl;
break;
case green:
cout << "oh,green." << endl;
break;
}
cin >> code;
}
6.5.2 switch 和 if else
switch和if else相比,if else更通用。
switch的每个case标签值必须是单独的值且必须是整数(包括char)。
6.6 break和continue语句
6.7 读取数字的循环
假设要编写一个将一系列数字读入到数组中的程序,并允许用户在数组填满之前结束输入。
如果用户输入一个单词,而不是一个数字,情况将如何呢?发生这种类型不匹配的情况时,将发生4中情况:
* n值保持不变;
* 不匹配的输入将被留在输入队列中;
* cin对象中的一个错误标记被设置;
* 对cin方法的调用将返回false(如果被转换为bool类型);
方法返回false意味着可以用非数字输入来结束读取数字的循环。
非数字输入设置错误标记意味着必须重置该标记,程序才能继续读取输入。clear方法重置错误输入标记,同时也重置文件尾(EOF条件)。输入错误和EOF都将导致cin返回false。
while(i < MAX && cin >> fishes[i]){
cout << fishes[i] << endl;
i++;
}
cin >> fishes[i] ;
是一个cin方法函数调用,该方法返回cin。如果cin位于测试条件中,则将被转换为bool类型。
如果输入成功,则转换后的值为true,否则为false。
当用户输入的不是数字时,该程序不再读取输入。
可以使用cin输入表达式的值来检测输入是不是数字。程序发现用户输入了错误内容时,应采取3个步骤:
1. 重置cin以接受新的输入
2. 删除错误输入
3. 提示用户再输入
double fishes[MAX];
int i = 0;
while(i < MAX){
while(!(cin >> fishes[i])){
cin.clear();
while(cin.get()!='\n')
continue;
cout << "error input, please go on: " << endl;
}
cout << fishes[i] << endl;
i++;
}
return 0;
6.8 简单文件输入/输出
6.8.1 文件I/O和文本文件
使用cin输入时,程序将输入视为一系列的字节,其中每个字节都被解释为字符编码。不管目标数据类型是什么,输入一开始都是字符数据--文本数据。然后,cin对象负责将文本转换为其他类型。
对于输出,将执行相反的转换,即整数被转换为数字字符序列,浮点数被转换为数字字符和其他字符组成的字符序列(如284.53或-1.58E+06)。字符数据不需要做任何转换。
6.8.2 写入到文本文件中
对于文件输入,C++ 使用类似于cout的东西。
* 必须包含头文件iostream
* 头文件iostream定义了一个用处理输出的ostream类;
* 头文件iostream 声明了一个名为cout的ostream变量(对象)。
* 必须指明命名空间std;
* 可以结合使用cout和<<来显示各种类型的数据。
文件输出与此及其相似。
* 必须包含头文件fstream
* 头文件fstream定义了一个用于处理输出的ofstream类。
* 需要声明一个或多个ofstream变量(对象)
* 必须指明命名空间std
* 需要将ofstream对象与文件关联起来,为此,方法之一是使用open()方法。
* 使用完文件后,应使用方法close()将其关闭
* 可结合ofstream对象和运算符<<来输出各种类型的数据。
char filename[20];
cout << "Enter the filename: " << endl;
cin >> filename;
ofstream infile;
infile.open(filename);
double test = 12.5;
infile << test;
infile.close();
* 重要的是,声明一个ofstream对象并将其同文件关联起来后,便可以像使用cout那样使用它。所有可用于cout的操作和方法(如<< 、 endl和self())都可以用于ofstream对象。
* 另外,close不需要使用文件名作为参数,因为infile已经同文件关联起来。如果忘记关闭文件,程序正常终止时将自动关闭它。
* infile可使用cout可使用的任何方法。不但能使用<<还可以使用各种格式化方法,如self()和precision(),这些方法只影响调用它们的对象。
* open(),若文件不存在,将新建;若文件已经存在,丢弃原有内容,重写文件。
6.8.3 读取文本文件
接下来介绍文本文件输入,它是基于控制台输入的,控制台输入涉及多个方面:
* 必须包含头文件iostream
* 头文件iostream定义了一个用于处理输入的istream类
* 头文件iostream声明了一个名为cin的istream对象
* 必须指明命名空间std
* 结合使用cin和>>来读取各种类型的数据
* 可以使用cin和get()方法读取一个字符,使用cin和getline()来读取一行字符
* 可以结合使用cin和eof() fail() 方法来判断输入是否成功
* 对象cin本身被用作测试条件时,如果最后一个读取操作成功,它将被转换为布尔值true,否则被转换为false
文件输出与此及其相似:
* 必须包含头文件fstream
* 头文件fstream定义了一个用于处理输入的ifstream类。
* 需要声明一个或多个ifstream对象
* 必须指明命名空间std
* 需要将ifstream与文件关联起来,方法之一使用open()方法
* 使用完文件后,使用close()方法将其关闭
* 可结合使用ifstream对象和get()方法来读取一个字符,使用ifstream对象和getline()来读取一行字符。
* 可以结合使用ifstream和eof()、fail()等方法来判断输入是否成功
* ifstream对象本身被用作测试条件时,如果最后一个读取操作成功,它将被转换为布尔值true,否则被转换为false
ifstream outfile;
outfile.open(filename);
double test;
outfile >> test;
char test2[20];
outfile >> test2;
cout << test << endl;
cout << test2 << endl;
outfile.close();
如果打开一个不存在的文件用于输入,将导致使用ifstream对象使用输入时失败。检查文件是否成功打开的首先方法就是使用is_open()。
outfile.open(filename);
if (!outfile.is_open())
exit(EXIT_FAILURE);
如果文件被成功打开,方法is_open()返回true,否则,返回false;
函数exit原型在头文件cstdlib中定义的,在该头文件中,还定义了一个用于同操作系统通信的参数值EXIT_FAILURE。函数exit()终止程序。
is_open是C++相对较新的内容。如果编译不过,可使用较老的方法good()来代替。不过,方法good在检查可能存在的问题方面,没有is_open()那么广泛。
ifstream outfile;
outfile.open(filename);
if (!outfile.is_open())
exit(EXIT_FAILURE);
int sum = 0;
int number;
outfile >> number;
while(outfile.good()){
outfile >> number;
sum += number;
}
if(outfile.eof()) {
cout << "eof" << endl;
} else if (outfile.fail()) {
cout << "fail" << endl;
} else {
cout << "other" << endl;
}
outfile.close();
检查文件是否成功打开至关重要,比如:
* 指定的文件不存在;
* 文件可能位于另一个目录中;
* 访问被拒绝;
* 输错了文件名或省略了文件扩展名。
程序读取文件时不应超过EOF。
* 首先,如果最后一次读取数据时遇到EOF,方法eof()返回true。
* 其次,程序可能遇到类型不匹配的情况,方法fail()返回true(如果遇到了EOF,该方法也返回true)。
* 最后,可能出现意外问题,如文件受损或者硬盘故障。
* 如果最后一次读取文件时放生了这样的问题,方法bad()将返回true。
不要分别检查这些情况,一个更简单的方法是使用good()方法,该方法在没有发生任何错误时返回true;
如果愿意,可以使用其他方法来确定循环终止的真正原因:
if(outfile.eof()) {
cout << "eof" << endl;
} else if (outfile.fail()) {
cout << "fail" << endl;
} else {
cout << "other" << endl;
}
eof()只能判断是否到达EOF;
fail()可以检查是EOF还是类型不匹配;
方法good()指出最后一次读取输入的操作是否成功,这一点至关重要。这意味着在执行读取输入的操作后,立刻应用这种测试。为此,一种标准的方法是,在循环之前放置一条输入语句,并在循环的末尾放置另一条输入语句。
outfile >> number;
while(outfile.good()){
++count;
outfile >> number;
sum += number;
}
可对上述语句进行精简;
表达式outfile>>number的结果为outfile,而在需要一个bool值得情况下,outfile的结果为outfile.good()即,true或者false;
因此,可以改造成如下:
while(outfile >> number){
++count;
sum += number;
}