C++ plus6th 第4章 复合类型

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是否在数组界内。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容