C++PrimerPlus6-04-复合类型

第4章 复合类型

  • 数组
  • C风格字符串
  • string类字符串
  • getline(), get()
  • 混合输入字符串和数字
  • 结构
  • 共用体
  • 枚举
  • 指针
  • new, delete管理动态内存
  • 动态数组
  • 动态结构
  • 自动存储、静态存储、动态存储
  • vertor, array

4.1 数组

数组声明应指出三点:

  • 存储在每个元素中的值的类型
  • 数组名
  • 元素数。必须是确定的,空着的话有初始化时的元素个数来确定
typeName arrayName[arraySize];

4.2 字符串

C风格字符串,以空字符结尾,被写作\0,其ASCII码为0。空字符对C风格字符串很重要,很多处理字符串的函数,遇到空字符才停止。
字符串常量(字面值)初始化字符串,数组的大小要把空字符考虑进去。

char dog[3] = {'d', 'o', 'g'}; // 不是字符串,没以空字符结尾
char cat[4] = {'c', 'a', 't', '\0'}; // 是字符串
char bird[11] = "Mr. Cheeps"; // 是字符串,自动加上空字符
char fish[] = "Bubbles"; // 是

4.2.1 拼接字符串常量

char kg[] = “kaige is “ “sb”;

4.2.2 在数组中使用字符串

sizeof运算符指出整个数组的长度,而strlen()只计算可见的字符。

4.2.3 字符串输入

cin的缺陷是,使用空白(空格、制表符、换行符)来确定字符串的结束位置,这意味着cin在获取字符数组输入时只读取一个单词。

4.2.4 每次读取一行字符串输入

  • getline():丢弃换行符
  • get():将换行符保留在输入序列中
cin.getline(name, 20);

cin.get(name, ArSize); // read first line
cin.get(); // read newline
cin.get(dessert, Arsize); // read second line

带参数的cin.get是将一个字符串放入数组中,不带参数可读取下一个字符。这样跳过换行符。

4.2.5 混合输入字符串和数字

会出现问题

4.3 string类简介

string类定义隐藏了字符串的数组性质,能像处理普通变量那样处理字符串。需要包含头文件string,string类位于名称空间std中。
自动调整大小,可以将char数组视为一组用于存储一个字符串的char存储单元,而string类变量是一个表示字符串的实体。

4.3.1 C++字符串初始化

可以用列表初始化C风格字符串和string对象。

4.3.2 赋值、拼接和附加

4.3.3 string类的其他操作

这样处理C风格字符串

  • strcpy():将字符串复制到字符数组中
  • strcat():将字符串附加到字符数组末尾
  • size():str.size(),string的长度
  • strlen(charr1):char数组的长度

4.3.4 string类I/O

4.3.5 其他形式的字符串字面值

4.4 结构简介

4.4.1 在程序中使用结构

通常使用放在main函数外的外部声明。C++不提倡使用外部变量,但提倡使用外部结构声明。

4.4.2 C++结构初始化

4.4.3 结构可以将string类作为成员吗

4.4.4 其他结构属性

与C结构不同,C++结构除了成员变量之外,还可以有成员函数。但是成员函数一般用在类中。

4.4.5 结构数组

可以创建元素为结构的数组。

4.4.6 结构中的位字段

位字段通常用在低级编程中。指定占用特定位数的结构成员。字段的类型为整型或枚举,接下来是冒号,冒号后面是一个数字,她指定了位数。可以使用没有名称的字段来提供间距。

struct torgle_register
{
  unsigned int SN : 4;  // 4 bits for SN value
  unsigned int : 4;     // 4 bits unused
  bool goodIn : 1;      // valid input (1 bit)
  bool goodTorgle : 1;  // successful torgling
}

torgle_reggister tr = {14, true, false};
...
if (tr.goodIn) // if statement covered in Chapter6
...

4.5 共用体

共用体(union),和结构相似,也能存储不同的数据类型,但只能同时存储其中一种。

union one4all
{
  int int_val;
  long long_val;
  double double_val;
}

one4all pail;
pail.int_val = 15; // 存了一个int
pail.double_val = 1.38; // 存了一个double,丢掉了之前的int

以上pail有时是int,有时是double。共用体的长度为其最大成员的长度,为了有足够的空间。她的用途之一,是数据项使用两种或更多格式(但不会同时使用)时,可节省空间。(对于内存很多的系统,没必要;对于嵌入式等编程,内存很宝贵,就能用到?)

4.6 枚举

enum spectrum {red, orange, yellow, green, blue, violet, indigo, ultraviolet};

以上让spectrum成为新类型的名称,她是枚举(enumeration)。大括号内的red、orange、yellow作为符号常量,他们对应整数值0~7,这些常量叫做枚举量(enumerator)。
枚举量是整型,可被提升为int,但int类型不能自动转换为枚举类型。枚举没有定义算数运算符。

4.6.1 设置枚举量的值

enum bits{one=1, two=200, for}; // 默认0开始,可以显式定义,后面比前面大1
enum {zero, null=0, one, numero_nuo=1}; // 可以创建多个值相同的枚举量

4.6.2 枚举的取值范围

通过强制类型转换,可以将取值范围中的任何整数值赋给枚举变量,即使这个值不是枚举值。

enum bits{one=1, two=2, for=4, eight=8};
bits myflag;
myflag = bits(6); // 可

枚举的取值范围:最大值是最大值的最小的2的幂减1。(例如最大值是101,则枚举范围的最大值为127。)最小值是0或者类似最大值方法。

4.7 指针和自由存储空间

计算机程序在存储数据时必须追踪的3种基本属性:信息存储在何处,存储的值是多少,存储的信息是什么类型。

指针是一个变量,其存储的是值的地址,而不是值本身。对于一个常规变量,使用地址运算符&,可以获得她的位置。
OOP强调运行阶段(而不是编译阶段),更加灵活。

指针名表示的是地址,*运算符被称为间接值(indirect value)或解除引用(dereferencing)运算符,将其应用于指针,可以得到该地址处存储的值。

#include <iostream>

int main()
{
    using namespace std;

    int updates = 6;
    int* p_updates;
    p_updates = &updates;

    // express values two ways
    cout << "Values: updates = " << updates;
    cout << ", *p_updates = " << *p_updates << endl;

    // express address two ways
    cout << "Addresses: &updates = " << &updates;
    cout << ", p_updates = " << p_updates << endl;

    // use pointer to change value
    *p_updates = *p_updates + 1;
    cout << "Now updates = " << updates << endl;
    return 0;
}

以上p_updates指向updates,他们完全等价。

4.7.1 声明和初始化指针

指针声明必须指定指向的数据类型。

4.7.2 指针的危险

一定要在对指针应用解引用*之前,将指针初始化为一个确定的、适当的地址。C++种创建指针时,计算机将分配用来存储地址的内存,但不会分配用来存储指针所指向数据的内存。
下面是不对的:

long* fellow; // create a pointer-to-long
*fellow = 223323; // place a value in never-never land

4.7.3 指针和数字

指针不是整型,但计算机通常把地址当作整数来处理。不能简单的将整数赋给指针,如果要将数字作为地址来只用,应通过强制类型转换将数字转换为适当的地址类型。

int* pt;
pt = 0xB8000000; // type mismatch
pt = (int*) 0xB8000000; // types now match

4.7.4 使用new来分配内存

对于小型程序,声明一个简单变量方便;对于大型数据(如数组、字符串、结构),应使用new。如果通过声明来创建数组,编译时(静态联编static binding)将为他分配内存空间,不管程序最终是否使用这个数组,她都在哪里,占用了内存。
使用new时,如果在运行阶段需要用到,则创建它,否则不创建。(动态联编dynamic binding)
静态联编时,必须在编写程序时指定数组的长度;动态联编时,程序将在运行时确定数组的长度。动态数组(dynamic array)

int* psome = new int[10];
delete [] psome;

使用new和delete时,应遵守以下规则:

  • 不要使用delete来释放不是new分配的内存。
  • 不要使用delete释放同一个内存块两次。
  • 如果使用new []为数组分配内存,则应使用delete []来释放。
  • 如果使用new为一个实体分配内存,则应使用delete(没有方括号)来释放。
  • 对空指针应用delete是安全的。

使用new创建动态数组后,她指向数组中的第一个元素,*psome是第一个元素的值。可以用下标来访问元素,例如psome[1]。psome = psome + 1,将指向第二个元素。

4.8 指针、数组和指针算数

指针和数组基本等价的原因在于指针算数(pointer arithmetic)和C++内部处理数组的方式。
将整数变量加1后,其值增加1;但将指针变量加1后,增加的量等于她指向的类型的字节数。

4.8.1 程序说明

4.8.2 指针小结

  1. 声明指针:typeName* pointerName;
  2. 给指针赋值:应将内存地址赋给指针。pn = &bubble; pc = new double[30];
  3. 对指针解除引用:对指针解除引用意味着获取指针指向的值,不要对未被初始化为适当地址的指针解除引用。cout << *pn; pn[0];
  4. 区分指针和指针所指向的值:如果pt是指向int的指针,*pt完全等同于一个int类型的变量。
  5. 数组名:多数情况下,C++将数组名视为数组的第1个元素的地址。但将sizeof用于数组名时,返回整个数组的长度。
  6. 指针算数:允许将指针和整数相加。
  7. 数组的动态联编和静态联编:使用数组声明来创建数组时,采用静态联编;使用new[]来创建数组,采用动态联编。
  8. 数组表示法和指针表示法:使用方括号数组表示法等同于对指针解除引用。

4.8.3 指针和字符串

数组和指针的特殊关系可以扩展到C风格字符串。

4.8.4 使用new创建动态结构

inflatable* ps = new inflatable;

#include <iostream>

struct inflatable // structure definitio
{
    char name[20];
    float volume;
    double price;
};

int main()
{
    using namespace std;

    inflatable* ps = new inflatable;    // allot memory for structure
    cout << "Enter name of inflatable item: ";
    cin.get(ps->name, 20);              // method 1 for member access
    cout << "Enter volume in cubic feet: ";
    cin >> (*ps).volume;                // method 2 for member access;
    cout << "Enter price: $";
    cin >> ps->price;
    cout << "Name: " << (*ps).name << endl;                 // method 2
    cout << "Valume: " << ps->volume << " cubic feet\n";    // method 1
    cout << "Price: $" << ps->price << endl;                // method 1

    delete ps; // free memory used by structure

    return 0;
}

4.8.5 自动存储、静态存储和动态存储

  • 自动存储:在函数内部定义的常规变量使用自动存储空间,被称为自动变量(automatic variable)。在所属的函数被调用时自动产生,在该函数结束时消亡。局部变量,通常存储在栈中,后进先出。
  • 静态存储:整个程序执行期间都存在的存储方式。
    • 在函数外面定义的
    • 声明变量时使用了关键字static
  • 动态存储:new delete,被称为自由存储空间(free store)或堆(heap)。该内存池同以上两种是分开的,数据的生命周期不完全受程序或函数的生存时间控制。

要避免内存泄露,养成同时使用new和delete的习惯。

4.9 类型组合

4.10 数组的替代品

4.10.1 模板类vector

是使用new创建动态数组的替代品。实际上vector类确实使用new和delete来管理内存,但是自动完成的。

#include <vector>
...
using namespace std;
vector<int> vi; // 创建一个zero-size的int数组
int n;
cin >> n;
vector<double> vd(n) // 创建n个double类型元素的数组

4.10.2 模板类array(C++11)

长度固定,使用栈,和数组一样方便安全效率。

#include <array>
...
using namespace std;
array<int, 5> ai; // 创建一个数组对象,有5个int
array<double, 4> ad = {1.2, 2.1, 3.42, 4.3};

4.10.3 比较数组、vector对象和array对象

#include <iostream>
#include <vector>    // STL C++98
#include <array>    // C++11

int main()
{
    using namespace std;

    // C, original C++
    double a1[4] = { 1.1, 2.2, 3.3, 4.4 };

    // C++98 STL
    vector<double> a2(4); // create vector with 4 elements
    // no simple way to initialize in C98
    a2[0] = 1.0 / 3.0;
    a2[1] = 1.0 / 5.0;
    a2[2] = 1.0 / 7.0;
    a2[3] = 1.0 / 9.0;

    // C++11 -- create and initialize array object;
    array<double, 4> a3 = { 3.14, 2.72, 1.62, 1.41 };
    array<double, 4> a4;
    a4 = a3; // valid for array objects of same size

    // use array notation
    cout << "a1[2]: " << a1[2] << " at " << &a1[2] << endl;
    cout << "a2[2]: " << a2[2] << " at " << &a2[2] << endl;
    cout << "a3[2]: " << a3[2] << " at " << &a3[2] << endl;
    cout << "a4[2]: " << a4[2] << " at " << &a4[2] << endl;

    // misdeed,不端行为
    a1[-2] = 20.2;
    cout << "a1[-2]: " << a1[-2] << " at " << &a1[-2] << endl;
    cout << "a3[2]: " << a3[2] << " at " << &a3[2] << endl;
    cout << "a4[2]: " << a4[2] << " at " << &a4[2] << endl;

    return 0;
}
  • 数组、vector对象、array对象,都可以使用下标。
  • 从地址可知,array和数组在相同的内存区域(栈);vector在另一个区域(自由存储区或堆)。
  • 可以将array对象赋值给另一个array对象,数组必须主元素复制。
  • a1[-2]在数组外面了,但能编译过去。用成员函数at()来做。

4.11 总结

数组、结构、指针。

4.12 复习题

4.13 编程练习

1

2

3 使用strcpy()和strcat()出现了C4996,在项目属性-C/C++–SDL检查,选择否,不检查此错误。

4

5 printf()输出string类的字符串时,需要调用c_str(),否则显示乱码。

6 略
7 略
8 cin下面连着用getline(),会产生name不能输入,直接结束的现象。通过调用cin.get()清空缓冲区来解决。(P68)

9 略
10 使用array需要包含头文件array,否则报使用不完整的类型。

999 Ref

  1. 错误C4996:https://blog.csdn.net/weixin_44171004/article/details/86675605
  2. c++中printf显示string类乱码的解决:https://blog.csdn.net/xx970829/article/details/115963735
  3. 连着使用cin和getline()只能输入一次的问题:https://blog.csdn.net/qq_41575507/article/details/104432933

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 cdd@ahucd.cn

×

喜欢就点赞,疼爱就打赏

B站 cdd的庇护之地 github itch