C++_primer_plus学习笔记 第4章 复合类型

本文详细介绍了C++中的复合类型,包括数组、字符串、结构、共用体、枚举、指针和自由存储空间。重点讨论了数组初始化规则、string类的使用、结构体数组以及枚举量的值设定。此外,还讲解了动态内存分配和指针算术,以及如何避免指针使用中的陷阱。

数组

  • 数组(array)是一种数据格式,能够存储多个同类型的值。
  • 每个值存储在独立数组元素中,在内存中连续存储各个元素
  • 创建数组,使用声明语句
  • 数组声明包括:存储在每个元素中值的类型、数组名、数组中元素数
  • 通用格式:typeName arrayName [arraySize]; arrarSize必须整型常数,不能是变量
  • 单独访问数组元素的方法是使用下标或索引对元素编号。
  • C++数组从0开始编号,最后一个元素的索引比数组长度少1

//程序清单4.1
//整数数组

#include <iostream>
int main (void)
{
    using namespace std;
    int yams[3];            //创建包含三个元素的int类型数组
    yam[0] = 7;             //给每个元素赋值
    yam[1] = 8;
    yam[2] = 6;

    int yamcosts[3] = {20, 30, 5];    //创建和初始化数组

    cout << "Total yams = " << yams[0] + yams[1] + yams[2] << endl;
    cout << "The package with " << yams[1] << " yams costs " << yamcosts[1] << " cents per yam.\n";
    int total = yams[0] * yamcosts[0] + yams[1] * yamcosts[1] + yams[2] * yamcosts[2];
    cout << "The total yam expense is " << total << " cents.\n";

    cout << "\nSize of yams array = " << sizeof yams << " bytes.\n";        //12 bytes
    cout << "Size of one element = " << sizeof yams[0] << " bytes.\n";      //4 bytes

    return 0;
}
  •  程序给yam赋值和给yamcost赋值采用了两种方法
  • sizeof运算符返回类型或数据对象的长度(单位为字节)

数组初始化规则

  • 只有定义数组时才能使用初始化,此后不能使用,也不能将一个数组赋值给另一个数组;可以使用下标分别给数组元素赋值
int cards[4] = {3, 6, 8, 10};        //可以
int hand[4];                         //可以
hand[4] = {5, 6, 7, 8};              //不可以
hand = cards;                        //不可以
  • 初始化时提供的值可以小于数组元素数目,编译器将剩余元素设置为0
  • 将数组所有元素初始化为0,只需显式地将第一个元素初始化为0
  • 如果初始化为{1}而不是{0},则第一个元素被设置为1,其余元素为0
  • 如果初始化数组是【】为空,则C++编译器计算元素个数
  • 数组初始化,可省略=

字符串

  1. 字符串常量(双引号,字符+ \0)——字符串地址
  2. 字符常量(单引号,字符)——ASCII码
//程序清单4.2
//在数组中存储字符串

#include <iostream>
#include <cstring>        //为了使用strlen()函数

int main (void)
{
    using namespace std;

    const int Size = 15;
    char name1[Size];
    char name2[Size] = "C++owboy";

    cout << "Howdy! I'm " << name2 << "! What's your name?\n";
    cin >> name1;        //Basicman
    cout << "Well, " << name1 << ", your name has " << strlen(name1);    //8 letters
    cout << " letters and is stored in an array of " << sizeof(name1) << " bytes.\n";//15 bytes
    cout << "Your initial is " << name1[0] << ".\n";        //B
    name2[3] = '\0';
    cout << "Here are the first 3 characters of my name: " << name2 << endl;    //C++

    return 0;
}
  • sizeof运算符指出整个数组的长度:15字节;strlen()函数返回存储在数组中字符串的长度,不算空字符在内
  • 此程序有个缺陷:cin使用空白(空格、制表符、换行符)确定字符串结束位置。如果输入输入的是pin duoduo,程序只是读取pin到name1中,将duoduo 保留在输入队列。此时要用面向行的输入:getline()和get()
//程序清单4.4 4.5
//getline() 和get()

#include <iostream>
int main (void)
{
    using namespace std;
    const int ArSize = 20;
    char name[ArSize];
    char dessert[ArSize];

    cout << "面向行的输入:getline()" << endl;
    cout << "Enter your name:\n";
    cin.getline(name, ArSize);
    cout << "Enter your favorite dessert:\n";
    cin.getline(dessert, ArSize);
    cout << "I have some delicious " << dessert << " for you, " << name << ".\n";

    cout << "面向行的输入:get()" << endl;
    cout << "Enter your name:\n";
    cin.get(name, ArSize).get();
    cout << "Enter your favorite dessert:\n";
    cin.get(dessert, ArSize).get();
    cout << "I have some delicious " << dessert << " for you, " << name << ".\n";

    return 0;
}
  • getline()函数读取整行,通过换行符确定行尾,但不保存换行符,在存储字符串时,用空字符替换换行符
  • get()函数读取整行,但保存换行符在队列中,用不带任何参数的get()处理换行符
cin.get(name, ArSize);
cin.get();
//或
cin.get(name, ArSize).get();
  • 该函数们有两个参数。第一个参数用来存储数组名称,第二个参数读取字符数。如果第二个参数为20,则最多读取19个字符,余下留给空字符\0。但cin.get()有多种参数形式,也可以没有参数get(),后面再说
//程序清单4.6
//以下数字输入与行输入

#include <iostream>
int main (void)
{
    using namespace std;
    cout << "What year was your house built?\n";
    int year;
    cin >> year;
    cout << "What is its street address?\n";
    char address[80];
    cin.getline(address, 80);
    cout << "Year built: " << year << endl;
    cout << "Address: " << address << endl;

    return 0;
}

该程序运行如下:

What year was your house built?

1966

What is its street address?

Year bulit: 1966

Address:

  •  用户根本没有输入地址的机会。问题在于:cin读取年份,将回车键生成的换行符保留在输入队列中。后面的cin.getline()看到换行符后,认为是一个空行,将空字符串赋值给address数组
  • 解决之道是:读取地址前先读取并丢弃换行符
cin >> year;
cin.get();
//或
(cin >> year).get();

string类

//程序清单4.7
//使用C++ string 类

#include <iostream)
#include <string>

int main(void)
{
    using namespace std;
    char charr1[20];
    char charr2[20] = "jaguar";
    string str1;
    string str2 = "panther";

    cout << "Enter a kind of feline: ";
    cin >> charr1;
    cout << "Enter another kind of feline: ";
    cin >> str1;
    cout << "Here are some felines:\n";
    cout << charr1 << " " << charr2 << " " << str1 << " " << str2 << endl;
    cout << "The third letter in " << charr2 << " is " << charr2[2] << endl;
    cout << "The third letter in " << str2 << " is " << str2[2] << endl;

    return 0;
}
  • 要使用string类必须包含头文件string。string类位于名称空间std中,必须提供using编译指令
  • string类隐藏了字符串的数组性质,能够像处理普通变量一样处理字符串
  • 通过示例可知,在很多方面,使用string对象的方式与使用字符数组相同
  • string对象和字符数组之间主要区别是:可以将string对象声明为简单变量,而不是数组
  • 类设计可以让程序自动处理string大小

赋值、拼接和附加

  • 不能将一个数组赋值给另一个数组,但可以将一个string对象赋值给另一个string对象
char charr1[20];
char charr2[20] = "jaguar";
string str1;
string str2 = "panther";

charr1 = charr2;        //非法
str1 = str2;           //合法
  • string类简化了字符串合并操作。使用运算符+将两个string 对象合并,也可以使用+=将字符串附加到string对象末尾
  • C++新增string类之前,程序员使用C语言库中函数完成。头文件cstring提供了这些函数,strcpy()将字符串复制到字符数组中,strcat()将字符串附加到字符数组末尾
string str3;
str3 = str1 + str2;     //合并
str1 += str2;          //拼接

strcpy(charr1, charr2);    //复制charr2 给charr1
strcat(charr1, charr2);    //将charr2内容附加到charr1末尾
  • string对象语法通常比C字符串函数简单
str3 = str1 + str2;
//一句

strcpy(charr3, charr1);
strcat(charr3, charr2);
//两句 
  • string类具有自动调整大小的功能,C函数库需要使用strncpy()和strncat()

string类的I/O

//程序清单4.10
//行输入

#include <iostream>
#include <string>
#include <cstring>

int main(void)
{
    using namespace std;
    char charr[20];
    string str;

    cout << "Length of string in charr before input: " << strlen(charr) << endl;
    cout << "Length of string in str before input: " << str.size() << endl;
    //不确定,0
    cout << "Enter a line of text:\n";
    cin.getline(charr, 20);            //peanut butter
    cout << "You enter: " << charr << endl;
    cout << "Enter another line of text:\n";
    getline(cin, str);                 //blueberry jam
    cout << "You enter: " << str << endl;
    cout << "Length of string in charr after input: " << strlen(charr) << endl;
    cout << "Length of string in str after input: " << str.size() << endl;
    //13,13

    return 0;
  • 首先初始化数组内容是未定义的,其次函数strlen()从数组第一个元素开始计算字节数直到遇到空字符。未被初始化的数据,第一个空字符出现的位置是随机的
  • str中字符串长度为0,是因为未被初始化的string对象长度被自动设置为0
  • cin.getline(charr, 20); 这种句点表示法表明,函数getline()是istream类的一个类方法
  • getline(cin, str); 这里没有使用句点表示法,表明这个getline()不是类方法

结构简介

  • 结构是一种比数组更灵活的数据形式,同一个结构,可以存储多种类型的数据。

  •  关键字struct表明这些代码定义的是一个结构的布局。
  • 标识符inflatable是这种数据格式的名称。
  • 大花括号中包含的是结构存储的数据类型的列表,其中每个列表项都是一条声明语句。
//程序清单4.11
//一个简单的结构体

#include <iostream>
struct inflatable        //结构体声明
{
    char name[20];
    float volume;
    double price;
};

int main(void)
{
    using namespace std;
    inflatable guest = {"Glorious Gloria", 1.88, 29.99};
    inflatable pal = {"Audacious Arthur", 3.12, 32.99};

    cout << "Expand your guest list with " << guest.name << " and " << pal.name << "!\n";
    //Expand your guest list with Glorious Gloria and Audacious Arthur!
    cout << "You can have both for $" << guest.price + pal.price << "!\n";
    //You can have both for $62.98!

    return 0;
}
  • C++允许在声明结构变量时省略关键字struct。
  • 由于guest和pal的类型为inflatable,因此可以使用成员运算符(.)来访问各个成员。
  • 通过成员名能够访问结构的成员,就像通过索引能够访问数组的元素一样。
  • guest是一个结构,而guest.price是一个double变量。
  • 结构体一般放在main函数前面。
  • C++不提倡使用外部变量,但提倡使用外部结构声明。

  •  和数组一样,使用由逗号分隔值列表,并将这些值用花括号括起。
  • 与数组一样,c++11也支持将列表初始化用于结构,且等号(=)是可选的。
  • 如果大括号内未包含任何东西,各个成员都将被设置为零。
  • 还可以使用赋值运算符(=),将结构赋给另一个同类型的结构。这种赋值被称为成员赋值。

结构数组

//程序清单4.13
//结构数组

#include <iostream>
struct inflatable
{
    char name[20];
    float volume;
    double price;
};

int main(void)
{
    using namespace std;
    inflatable guest[2] =         //初始化结构数组
    {
        {"Bambi", 0.5, 21.99},        //注意逗号
        {"Godzilla", 2000, 565.99}
    };            //注意分号

    cout << "The guest " << guest[0].name << " and " << guest[1].name
         << "\nhave a combined volume of "
         << guest[0].volume + guest[1].volume << " cubic feet.\n";

    return 0;
}
  • 数组中每个元素都是结构类型的结构体。guest[2]是一个包含两个inflatable结构的数组。

共用体

  • 共用体是一种数据格式,它能够存储不同的数据类型,但只能同时存储其中的一种类型。
  • 结构可以同时存储int 、long和double;共用体只能存储int、 long或double。
  • 共用体的句法与结构相似,但含义不同:
union one4all
{
    int int_val;
    long long_val;
    double double_val;
};
  • 可以使用one4all变量来存储int、 long或double,条件是在不同的时间进行。
  • 共用体常用于(但并非只能用于)节省内存。常用于操作系统数据结构或硬件数据结构。

枚举

  • C++的enum工具,提供了另一种创建符号常量的方式。这种方式可以代替const。
  • 使用enum的方法与使用结构相似。
enum spectrum {red, orange, yellow, green, blue, violet, indigo, ultraviolet};
  • 这条语句完成两项工作:
  • 让spectrum成为新类型的名称。spectrum被称为枚举。就像struct变量被称为结构一样。
  • 将red,orange,yellow等作为符号常量,它们对应整数值0~7,这些常量叫做枚举量。
band = blue;        //合法
band = 2000;        //非法
  • 只能在花括号枚举范围内取值。
band = orange;            //合法
++band;                   //非法
band = orange + red;      //非法
  • 对于枚举只定义了赋值运算符,具体的说没有为枚举定义算术运算符。
int color = blue;            //合法,枚举类型提升为int
band = 3;                    //非法,int不能转换为枚举
color = 3 + red;             //合法,red转换为int
  • 枚举量是整型,可以被提升为int类型,但int类型不能自动转化为枚举类型。
  • 可以认为int类型比枚举类型高级,可以提升,但不能降低。
band = spectrum(3);            //将int类型3强制转换为枚举类型
  • 如果int值是有效的(在枚举允许范围内),则可以通过强制类型转换,将它赋给枚举变量。

设置枚举量的值

  • 可以用赋值运算符来显式的设置枚举量的值。
enum bits {one = 1, two = 2, four = 4, eight = 8};
  • 指定的值必须是整数。也可以只显式的定义其中一些枚举量的值。
enum bigstep {first, second = 100, third};
  • 这里first在默认情况下为零。后面没有被初始化的枚举量的值将比前面的枚举量大1。因此third的值为101。
  • 可以创建多个值相同的枚举量
enum {zero, null = 0, one, numero_uno = 1};

枚举的取值范围

 指针和自由存储空间

  • 如果home是一个变量,则&home是它 的地址。&称为地址运算符
  • 指针用于存储值的地址,指针名代表地址。*称为间接值或解除引用运算符,将其应用于指针,可以获得该地址处存储的值
  • 如果manly是一个指针,则manly表示的是一个地址,而*manly表示存储在该地址处的值
//程序清单4.15
//指针变量

#include <iostream>
{
    using namespace std;
    int updates = 6;            //声明变量
    int * p_updates;            //声明指针
    p_updates = &updates;       //将int类型变量的地址赋值给指针

    //表示值的两种方式
    cout << "Values: updates = " << updates << ", *p_updates = " << *p_updates << endl;
    //表示地址的两种方式
    cout << "Address: &updates = " << &updates << ", p_updates = " << p_updates << endl;

    //使用指针修改值
    *p_updates = *p_updates + 1;
    cout << "Now updates = " << updates << endl;        //7

    return 0;
}
  • int变量updates和指针p_updates是同一硬币的两面
  • 变量updates表示值,使用&运算符取址;指针p_updates表示地址,使用*运算符取值
  • 由于p_updates指向updates,因此*p_updates和updates完全等价
  • C++中,int * 是一种复合类型,是指向int的指针 
  • 指针初始化:int * p_updates = &updates;
double * tax_ptr;
char * str;
  •  虽然tax_ptr和str指向两种长度不同的数据类型,但这两个变量本身的长度是相同的
  • 好比1016可能是超声的地址,1024可以说村庄的地址。地址的长度或值既不能指示关于变量的长度或类型的任何信息,也不能指示该地址上有什么建筑物

指针的危险

long * fellow;
* fellow = 233333;
  • fellow确实是一个指针。但他指向哪里呢?上述代码没有将地址赋给fellow。 
  • 一定要在对指针应用解除引用运算符(*)之前,将指针初始化为一个确定的适当的地址。

指针和数字

  • 指针不是整型,虽然计算机通常把地址当做整数来处理。
  • 从概念看,指针和整数是截然不同的类型。整数是可以执行加减乘除等运算的数字,而指针描述的是位置,将两个地址相乘没有任何意义。
int * pt;
pt = 0xB8000000;            //类型不匹配

int * pt;
pt = (int *) 0xB8000000;   
  •  左边是指向int的指针,因此可以把它赋给地址,但右边是一个整数,通告类型不匹配。要将数字值作为地址来使用,应通过强制类型转换,将数字转换为适当的地址类型。

使用new来分配内存

  • 指针真正的用武之地在于,在运行阶段,分配未命名的内存以储存值。
  • C语言中用库函数malloc()来分配内存;C++中用new运算符
  • typename * pointer_name = new typename;
//程序清单4.17
//使用new运算符

#int main(void)
{
    using namespace std;
    int nights = 1001;
    int * pt = new int;        //new在内存中为int类型分配空间
    * pt = 1001;

    cout << "nights value = " << nights << ": location = " << &night << endl;
    cout << "int value = " << *pt << ": location = " << pt << endl;

    double * pd = new double;
    * pd = 10000001.0;
    cout << "double value = " << *pd << ": location = " << pd << endl;
    cout << "location of pointer pd: " << &pd << endl;        //指针地址
    cout << "size of pt = " << sizeof(pt) << ": size of *pt = " << sizeof(*pt) << endl;
    //size of pt = 4: size of *pt = 4
    cout << "size of pd = " << sizeof(pd) << ": size of *pd = " << sizeof(*pd) << endl;
    //size of pd = 4: size of *pd = 8
  • 变量nights和pd的值都存储在被称为的内存区域;而new从被称为堆或自由存储区的内存区域分配内存。

使用delete释放内存

//对的,new和delete要成对使用
int * ps = new int;
...
delete ps;

//错误,不能释放已经释放的内存
int * ps = new int;
delete ps;
delete ps;

//错误,没有成对使用
int jugs = 5;
int * pi = &jugs;
delete pi;

 使用new创建动态数组

  • type_name * pointer_name = new type_name [num_elements];
  • 创建一个包含10个int元素的数组
int * psome = new int [10];
delete [] psome;
  •  new运算符返回第一个元素的地址,使用完后用delete [] psome释放
//程序清单4.18
//使用new创建动态数组

#include <iostream>
int main(void)
{
    using namespace std;
    double * p3 = new double;
    p3[0] = 0.2;
    p3[1] = 0.5;
    p3[2] = 0.8;

    cout << "p3[1] is " << p3[1] << ".\n";            //0.5
    p3 = p3 + 1;
    cout << "Now p3[0] is " << p3[0] << " and p3[1] is " << p3[1] << ".\n";    //0.5, 0.8
    p3 = p3 - 1;
    delete [] p3;

    return 0;
}
  •  把指针当做数组名一样来访问元素。

指针、数组和指针算术

//程序清单4.19
//指针加法

#include <iostream>
int main(void)
{
    using namespace std;
    double wages[3] = {10000.0, 20000.0, 30000.0};
    short stacks[3] = {3, 2, 1};

    //两种取得数组地址的方式
    double * pw = wages;        //数组名
    short * ps = &stacks[0];    //第一个元素地址

    cout << "pw = " << pw << ", *pw = " << *pw << endl;
    pw = pw + 1;
    cout << "add 1 to the pw pointer:\n";
    cout << "pw = " << pw << ", *pw = " << *pw << endl << endl;
    cout << "ps = " << ps << ", *ps = " << *ps << endl;
    ps = ps + 1;
    cout << "add 1 to the ps pointer:\n";
    cout << "ps = " << ps << ", *ps = " << *ps << endl << endl;

    cout <<  "使用数组访问两个元素\n";
    cout << "stacks[0] = " << stacks[0] << ", stacks[1] = " << stacks[1] << endl;
    cout << "使用指针访问两个元素\n";
    cout << "*stacks = " << *stacks << ", *(stacks + 1) = " << *(stacks + 1) << endl;

    cout << sizeof(wages) << " = size of wages array\n";
    cout << sizeof(pw) << " = size of pw pointer\n";

    return 0;
}




  • 指针和数组基本等价的原因在于指针算数和C++内部处理数据的方式。
  • 整数变量加1后,其值加1;指针变量加1后,增加的量等于它指向类型的字节数。
  • C++将数组名解释为地址。数组名是第一个元素的地址,是常量。
  • 指针与数组的区别1:可以修改指针的值:,但数组名是常量。
  • 指针与数组的区别2:对数组应用sizeof运算符得到的是数组的长度,而对指针应用sizeof得到的是指针的长度。

 指针和字符串

//程序清单4.20
//使用指针指向strings

#include <iostream>
#include <cstring>
int main(void)
{
    using namespace std;
    char animal[20] = "bear";
    const char * bird = "wren";        //指向常量的指针,不能修改所指对象
    char * ps;        //无所指

    cout << animal << " and " << bird << ".\n";
    cout << "Enter a kind of animal: ";
    cin >> animal;        //fox

    ps = animal;
    cout << ps << endl;    //fox
    cout << "Before using strcpy():\n";
    cout << animal << " at " << (int *) animal << endl;    //强制类型转换
    cout << ps << " at " << (int *) ps << endl;

    ps = new char[strlen(animal) + 1];
    strcpy(ps, animal);
    cout << "After using strcpy():\n";
    cout << animal << " at " << (int *) animal << endl;    //强制类型转换
    cout << ps << " at " << (int *) ps << endl;
    delete [] ps;

    return 0;
}
  • 一般来说,如果给cout提供一个指针,它将打印地址,但如果指针的类型为char *,将显示指向的字符串
  • 如果要显示的是字符串的地址,则必须将这种指针强制转换为另一种指针类型(int *)。
  • 使用strcpy()将animal赋给ps,并不会复制字符串,只是复制地址。
  • 应使用strcpy()或strncpy()而不是赋值运算符来将字符串赋给数组。

使用new创建动态结构

//程序清单4.21
//使用new创建结构体
#include <iostream>
struct inflatable
{
    char name[20];
    float volume;
    double price;
};

int main(void)
{
    using namespace std;
    inflatable * ps = new inflatable;
    cout << "Enter name of inflatable item: ";
    cin.get(ps -> name, 20);
    cout << "Enter volume in cubic feet: ";
    cin >> (*ps).volume;        //法1
    cout << "Enter price: $";
    cin >> ps->price;           //法2
    cout << "Name: " << (*ps).name << endl;
    cout << "Volume: " << ps->volume << " cubic feet\n";
    cout << "Price: $" << ps->price << endl;
    delete ps;

    return 0;
}
  • 如果结构标识符是结构名,则使用句点运算符。如果标示符是指向结构的指针,则使用箭头运算符。
//程序清单4.22
//使用delete运算符
#include <iostream>
#include <cstring>
using namespace std;
char * getname(void);
int main(void)
{
    char * name;
    name = getname();
    cout << name << " at " << (int *)name << endl;
    delete [] name;

    return 0;
}

char * getname(void)
{
    char temp[80];
    cout << "Enter last name: ";
    cin >> temp;
    char * pn = new char[strlen(temp) + 1];
    strcpy(pn, temp);

    return pn;
}

类型组合(视频链接)

 数组的替代品

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值