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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Leon_George

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值