1.关于读取字符串
在读取输入时,如果使用
cin
函数,仅能每次读取一个单词,因为该函数默认遇到空白(空格、换行、制表符)来确认字符串的结束位置,并自动添加空值字符\0
。-
当需要读取一行含有空格的语句时,则需要使用到
cin
类的成员函数getline()和get()
,这2个函数读取一行输入,直到换行符结束,区别在于getline()
读取且丢弃换行符,而get()
不读取换行符且将其继续保留在输入队列中,容易使接下来的函数直接读取输入队列中的换行符。cin.getline(字符数组名或数组地址,数组大小)
:其在读取完指定的数目(数组大小 - 1)或遇到换行符时停止。-
cin.get(字符数组名或数组地址,数组大小)
和cin.get()
:这是get函数的两种变体(函数重载),前者同getline函数类似,参数意义也相同,但其仍然不读取不丢弃换行符,而是将其保留在输入队列中。为了解决该问题引入cin.get()
函数,不加任何参数的get函数专门用来接收单个字符(这个换行符),所以为了能和getline()函数那样实现连续两行的输入可以采取以下语句://以从键盘向name1和name2两个不同字符数组输入为例 //第一种方法: cin.getline(name1,NameSize); cin.getline(name2,NameSize); //第二种方法: cin.get(name1, NameSize); cin.get(); cin.get(name2, NameSize); //第三种方法 cin.get(name1, NameSize).get(); //cin.get(name1,NameSize)返回一个cin对象,其再次调用get()以消耗掉最后的换行符。 cin.get(name2, NameSize).get();
既然get()函数这么麻烦,为何还要保留该函数呢?原因在于其可以通过后一个get()收到的字符获知前一个get函数停止读取的原因:如果是换行符,则说明已经读取了整行,若不是,则是因为数组空间已填满。
当get()读取到空行时,会设置失效位failbit,以阻断接下来的输入,但可以使用
cin.clear();
语句来恢复输入。如果输入行比分配的字符数组要长,则getline和get函数均会把余下的字符留在输入队列中,但getline()会设置失效位,并关闭后面的输入。当数字和字符串混用时,为了避免输入数字后,留在输入队列的换行符自动填充到后面的字符串读取函数(getline或get) 可以采取如下2种办法:
//第一种方法: int year; cin >> year; cin.get(); //or cin.get(ch); char name[10]; cin.getline(name,10); //第二种方法: int year; (cin >> year).get(); //or (cin >> year).get(ch); char name[10]; cin.getline(name,10);
-
string类:可以简单将string作为一种定义字符串数据类型变量的方法,不同于数组字符串的是,我们事先无需确定其占有空间的大小,程序将结合其内容动态调整。要使用string类,必须包含头文件string,且使用
using namespace std
语句,或者使用全称std::string来引用它。用string定义的字符串变量可直接通过cin和cout实现赋值和打印。- string类变量可以像字符数组那样通过初始化和赋值,不同的是string类变量相互间可以赋值,但字符数组则不行。
- string类简化了字符串合并操作,可以直接使用+号和+=运算符实现字符串的拼接。
- sring类型的变量可以使用cin和cout实现输入和输出。也可以使用
getline(cin, str)
来实现输入,(其中str是string类型的变量)
wchar_t、char16_t、char32_t字符类型
以上宽字符类型,分别冠以L、u、U前缀,后两种为C++11新增的,其使用如下:
wchar_t title[] = L"Chief Astrogator";
char16_t name[] = u"Felonia Ripova";
char32_t name[] = U"Humber Super";
C++11还支持Unicode字符编码方案UTF-8。使用u8前缀来表示该类型字符串字面值。
C++还支持原始字符串(raw)。原始字符串表示的就是自己,例如\n不再表示换行符,而是\和n本身。因此,原始字符串以R作为前缀,且使用 “( 和 )“ 表示字符串的起始和终止符。例如:
cout << R"(Jim "King" uses "\n" instead of endl.)" << '\n'
上述代码将输出:Jim "King" uses "\n" instead of endl.
而如果使用标准字符串字面值,将需要如下编码:
cout << "Jim \"King\" uses \"\\n\" instead of endl." << '\n'
如果在原始字符串中需要输入“( 和 )”时又该如何表达呢?此时你可以自定义定界符,只要满足“(或)”之间加入任意数量的除空格、左右括号、斜杠和制表符、换行符等控制字符之外的基本字符即可。例如“+-(和)+-”
在键盘上输入原始字符串时,按下回车键不仅会移到下一行,而且还会在原始字符串中添加回车字符。
可将前缀R与其它字符串前缀结合使用,例如Ru、UR等。
2.关于结构
- C++允许在声明结构变量时省略struct关键字,使得结构名如同一种数据类型名,而C一般不省略。
struct inflatable //结构体声明
{
char name[20]; //或使用string类: std::string name;
float volume;
double price;
};
struct inflatable goose; //C风格,struct必须包含
balls goose; //C++风格,struct可省略
-
结构赋值
与数组一样,C++11也支持将列表初始化用于结构体,且等号是可选的:
inflatable duck = {"Daphne", 0.12, 9.98}; inflatable duck {"Daphne", 0.12, 9.98}; //也可以省略=
结构体类型的变量同基本类型变量一样可以相互赋值,也可以作为函数参数传递或函数返回值
结构中的位字段
与C一样,字段的类型应为整型或枚举,接下来是字段名(可选)和冒号,后面跟字段的位数,一般用以表示某寄存器结构。
struct torgle_register
{
unsigned int SN : 4; //4 bits for SN value
unsigned int : 4; //4 bits unused
bool goodIn : 1;
bool goodTorgle : 1;
};
torgle_register tr = {14, true, false};
3.关于共同体
共同体一般用于对象可能是多种数据类型的情况,可以大大节省存储空间。例如商品id既可能是数字也可能是字符串:
struct widget
{
char brand[20];
int type;
union id
{
long id_num;
char id_char[20];
}id_val;
};
...
widget prize;
...
if (prize.type == 1)
cin >> prize.id_val.id_num;
else
cin >> prize.id_val.id_char;
当然,也可以使用匿名共同体,此时上述程序可以改写为:
struct widget
{
char brand[20];
int type;
union
{
long id_num;
char id_char[20];
};
};
...
widget prize;
...
if (prize.type == 1)
cin >> prize.id_num;
else
cin >> prize.id_char;
由于匿名,id_num和id_char被视为prize的两个成员,因为他们的地址相同,所以不需要使用中间标识符id_val。程序猿负责确定当前哪个成员是活动的。
4.关于枚举
enum提供了另一种创建符号常量的方式,这种方式可以代替const。其句法如下:
enum spectrum {red, orange, green, blue, violet, indigo, ultraviolet};
以上定义让spectrum成为新的类型名称,其定义的变量只允许赋大括号的值(red, orange等),默认情况下,依次赋值0,1,2…给red, orange等。当然,也可以显示的指定整数值来覆盖默认值。
枚举变量仅定义了赋值运算,其它运算符对其非法。
枚举变量是整型,可被提升赋值给int变量,但反过来结果未知。但如果int变量的值在枚举值范围内,则也是可以的,如定义了一个spectrum枚举变量band,如下句子合法:band = spectrum(3);
。
如果只使用枚举常量,而不创建枚举类型的变量,则可以省略枚举类型名称。
- 设置枚举量的值
//默认依次从0增加,后者的值比前者的值加1.若前者显示赋值,则后者在前值基础上加1
enum bits {zero, null = 0, one, numero_uno = 1, two};
//zero = null =0; one = numero_uno = 1,two = 2.
- 枚举的取值范围
C++通过强制类型转换,可以将在枚举变量取值范围内的整数合法的赋给枚举变量。这个范围是:上限是枚举变量最大值向上取2的幂整减去1,下限是0或者负数的向下取2的幂整加1.
enum bits {one = 1, two = 2, four = 4, eight = 8}; //rang=[0, 15]
enum aha {neg = -6, one = 1, hud = 101}; //rang=[-7, 127]
aha value;
value = aha(120); //合法
所以,只要赋给bits类型枚举变量的整数值在[0,15]范围内都是合法的,赋给aha类型枚举变量的整数值在[-7,127]范围内都是合法的.
每个枚举变量占用存储空间大小由编译器决定,早期C++,只能将int型赋给枚举变量,但新版本可以使用long,甚至long long型的值。所以其取值范围小的可以用单字节,取值范围大的可以用4字节。
5.关于指针和动态存储空间(堆heap)
- 在同时定义多个地址变量时,一定要注意书写格式。
int *ptr_a; //C style
int* ptr_a; //C++ style
int* ptr_a, ptr_b; //ptr_a是指针,但ptr_b是int变量!!!
int *ptr_a, *ptr_b; //此时二者均为指针!
- 在给指针解引用前一定一定要初始化!!!否则容易造成难以查找的错误。
long *fellow; //定义了一个地址,但未赋初值。
*fellow = 22334; //将22334存贮在一个未知地址,极其危险!!!
- 不可直接将整数赋给地址变量,需要进行强制类型转换!
int *ptr;
ptr = (int *)0xb8000000;
- C++一般使用new和delete动态分配(程序运行时分配)存储空间,C一般使用malloc和free动态分配存储空间,与静态分配(自动变量,编译时分配)消耗栈空间不同的是它们消耗的是堆空间。尤其注意的是new和delete一定要配对使用,否则会造成内存泄漏,最终导致程序崩溃。
int *ps = new int;
...
delete ps; //释放ps指向的空间到内存池中,但ps指针变量不会被删除,可以重新指向另一个地址。
- 不要二次释放内存块,也不要释放不是用new分配的地址,其结果是未知的。
int *ps = new int;
int *pq = ps;
delete pq; //仅需要释放一次,不需要再释放ps了。
- 如果使用new [ ]为数组分配内存,则应使用delete [ ]来释放。且不要用elete [ ]来释放new分配的单个变量。
int *psome = new int[10];//分配含10个int元素的数组,并返回数组首地址
int *pone = new int;
。。。
delete []psome; //释放动态数组
delete []pone; //非法!!!
delete null; //合法。
- 应用sizeof运算符不可以获得new分配数组的大小。
int * ps = new int[3];
int pa[3];
...
size_ps = sizeof(ps); //得到的是地址大小4,假设地址变量占用4个字节空间
size_pa = sizeof(pa); //得到数组大小3*4=12,假设int变量占用4个字节空间
- 应用new分配的数组,其返回的指针变量可以进行算数运算,而采用声明的数组名确是常量,不可以进行加减。
int * ps = new int[3];
ps[0] = 0; //or *ps = 0;
ps[1] = 1; //等价于 *(ps+1) = 1;
ps[2] = 2;
cout << ps[0] << endl; //输出0
ps = ps + 1; //指向下一个元素地址
cout << ps[0] << endl; //输出1
ps -= 1;
delete []ps;
ptr[i] = = *(ptr + i) == i[ptr] = *(i + ptr)
数组名被解释为第一个元素的地址,但对数组名应用取址符&时,得到的是整个数组的地址。假设定义一个数组
int arr[10];
,则arr == &arr[0]
,arr +1 将地址加4(下一个元素),但&arr + 1
将地址加40(指向数组最后一个元素的下一个元素地址),既你可以这样声明和初始化:int (*pas)[10] = &arr
。指针加1等于下一个元素地址,而同一个数组里的不同元素间地址相减,代表这两个元素的间隔。
字符数组名、char指针和引号括起来的字符串常量都被解释为字符串第一个字符地址!
-
如果给cout提供一个地址,它将输出地址。但如果该地址是指向字符串的,则输出字符串。所以如果想输出字符串的地址,则需要强制转化为指向其它类型的地址,如(int *)。
6.模板类vector, C++98 style
模板类vector类似于string类,是一种动态数组。可在运行时设置vector对象的长度、在末尾或中间插入新数据。基本上是使用new和delete创建动态数组的替代品。它的功能强大,但效率较低。
要使用vector类,必须包含头文件vector,且其包含在std名称空间中,需要使用using编译指令或using声明或std::vector。
模板使用不同的语法来指出它存储的数据类型和元素数量:
vector<类型名> 对象名(元素个数)
,表示元素个数即可用整型常量或变量,vector<int> vd(n);
vector对象的初始化只能逐个赋值,不能使用列表赋值。采用标准数组表示法来访问每个元素。
为了错误引用数组界外的元素,可使用vector类的成员函数at(index),它会检查提供的索引值index是否在数组界内。设vi是vector型变量,访问其第4个元素表达式如下:
vi[3] 或者 vi.at(3)
#include <vector>
...
using namespace std;
vector<int> vi; //创建0长度的整型数组vi
int n;
cin >> n;
vector<double> vd(n); //根据用户输入创建一个长度为n的双实型数组
7.模板类array, C++11 style
如果想创建长度固定的数组,且操作比数组方便,那就选择array。它与数组一样,使用栈空间,其效率同数组,但更方便和安全。创建array,需要包含头文件array。
- 模板使用不同的语法来指出它存储的数据类型和元素数量:
array<类型名, 元素个数> 对象名;
,表示元素个数是整型常量。 - array对象可在定义时进行列表初始化。
- array变量之间可相互赋值,采用标准数组表示法来访问每个元素。
#include <array>
...
using namespace std;
array<int, 5> ai; //创建包含5个元素的整型数组ai
array<double, 4> ad = {1.2, 2.3, 3.4, 4.5}; //创建包含4个元素的double型数组di
为了错误引用数组界外的元素,可使用array类的成员函数at(index),它会检查提供的索引值index是否在数组界内。