第四章 复合类型

第四章  复合类型

4.1  数组

    4.1.1  数组简介

    数组(array)是一种数据格式,能够存储多个同类型的值。

    声明数组的通用格式如下:

    typeName arrayName[arraySize];

     表达式arraySize指定数组的元素数目,它只能是以下三种情况之一: 

    1)        整型常数(如10,枚举值也可以);

    2)        const值

    3)        常量表达式(如8 * sizeof(int))

    注意:使用数组要注意下标的正确。编译器不会检查使用的下标是否有效。将一个值赋给不存在的元素,编译器不会指出错误,但是会隐藏着致命的结果。编写程序时要小心,使用有效的下标值。

 4.1.2  数组的初始化规则

    只有声明时才能对数据初始化,但是可用下标为每个元素分别赋值。

    常规的初始化方法如下:

  int array1[4] = { 1, 2, 3, 4 };        //常规初始化

      大括号中提供的值可以少于数组元素的数目,这种情况下只对数组前面的元素赋值,其他元素被赋予0值,如下:

    int array2[4] = { 1, 2 };            //值的数目少于元素数目

      如果[]中没有给出数组元素的数目,编译器会计算元素的个数,这种情况多用于字符串的初始化,如下:

    int array3[] = { 1, 2, 3, 4 };        //[]没有给出元素数目

      C++11数组的初始化方式可以省略等号(=),如下:

    int array4[4]{ 1, 2, 3, 4 };        //省略等号

      大括号中也可以没有值,这时数组中全部元素都被赋0值,如下:

    int array5[4] = {};                    //大括号没有值

4.2  字符串 

    C风格的字符串是字符的数组,并且字符串以’\0’结束。

 4.2.1  初始化字符串的方式

    字符串是字符的数组,可以使用数组的初始化方式,如下:

    char name1[6] = {'P','e','t','e','r','\0'};    //与数组相同

      另外可以用字符串常量为字符串初始化(并非赋值),如下:

    char name2[6] = "Peter";        //长度为5的字符串要占用6个位置
    char name3[] = "Peter";        //等价于name1

 4.2.2  拼接字符串常量 

    C++允许拼接字符串字面值,将两个用引号括起来的字符串合并为一个。任何两个由空白符(空格、制表符、换行符)分隔的字符串常量都将自动拼接成为一个字符串(即去掉第一个字符串结尾的’\0’,并把第二个字符串复制到第一个字符串的结尾处)。如:

    "I have a" " pen."
    "I have a pen."

    两个字符串内容是相同的。

 4.2.3  字符串的输入

    可以使用cin输入字符串的内容,这点很重要,只限于字符数组,如下:

  char name[10];
  cin >> name;

    上面的语句遇到空白符(空格、制表符、换行符)将结束输入。可以使用cin的getline或者get成员函数来得到一行。如下:

   cin.get(name, 10);
   cin.getline(name, 10);

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

 上面两个语句的作用相同,都是读取最长长度为10-1=9的字符串,如果遇到换行符则停止输入(即使不到9)。但是,注意get和getline是有区别的,get不会吃掉结尾的换行符,换行符会留在缓冲队列。

    1)        wchar_t。使用L前缀。如下:

    wchar_t name1[10] = L"Peter";

      2)        char16_t。使用u前缀。如下:

    char16_t name2[10] = u"Peter";

      3)        char32_t。使用U前缀。如下:

    char32_t name3[10] = U"Peter";

4.3  string类简介 

      C++风格的字符串使用string类。可以用字符串字面量为string类初始化和赋值(C风格字符串不能赋值)。如下: 

    string str = "Hello C++!";
    str = "Hello";
    str += " C++!";

      C++11初始化列表的方式初始化,如下:

    string str2 = { "Hello C++" };
    string str3{ "Hello C++" };

      同样可以使用cin来获得输入,也可以使用getline获得一行输入:

    cin >> str;
    getline(cin, str);

  注意,这里的getline不是cin的成员函数。

4.4  结构体简介     

    结构体(struct)是多种数据类型的组合。

    4.4.1  结构体声明

    声明结构体可以在函数的开始,也可以在函数之外。但一般在函数之外声明结构体,这样所有的函数都可以使用该结构体。声明结构体语法如下: 

    struct Student
    {
        char name[20];
        float grade;
    };

    4.4.2  结构体变量初始化 

    结构体变量的初始化: 

    Student stu1 = { "Peter", 1.0f };

    C++11初始化,如下:

   Student stu2{ "Peter", 1.0f };
   Student stu3
= {};

 说明:C++使用户定义的结构与内置类型尽可能相似。

 4.4.3  结构体变量使用     

    访问结构体中的变量,可以使用点运算符(.)。如: 

    cout << stu1.name << endl;

 4.4.4  结构体中的位字段 

    C和C++允许指定占用特定位数的结构成员。字段的类型应为整型或者枚举型,接下来是冒号,冒号后面是数字。例如: 

    struct Data{
        unsigned int SN : 4;
        unsigned int : 4;
        bool b : 1;
        bool : 1;
    };

    其中第二个unsigned int和第二个bool不使用,作为补齐的用处。 笔者在这里实验,发现连续同一种类型会共享一字(非字节,一个字一般为32位),不同类型不会共享一个字。如2个4位int后面1个1位bool占用8个字节。

4.5  共用体

    共用体(union)是一种数据格式。使多种类型的数据共享同一个内存空间。同时只能使用共用体中的一种类型值。共同体占用的内存空间与共用体中最长的类型一致。共用体的声明如下:

    union Data{
        int int_val;
        long long_val;
        double double_val;
    };
    int main(){
        Data d;
        d.int_val = 10;
        cout << d.int_val << endl;
        d.double_val = 10;
        cout << d.double_val << endl;
        cin.get();
        return 0;
    }

    匿名共用体。实例代码如下:     

    struct Grid{
            double x;
        double y;
        union{
                int int_val;
            long long_val;
            double double_val;
        };
    };
    int main(){
            Grid g;
        g.int_val = 1;
        cout << g.int_val << endl;
        g.double_val = 10;
        cout << g.double_val << endl;
    }

  共用体可以用来节省内存空间。

  

4.6  枚举

    枚举(enum)提供创建符号常量的方式,可以代替const,也可以定义新类型。

    4.6.1  枚举的声明

    枚举的声明中枚举量用逗号隔开,如下: 

    enum WEEKDAY{SUN,MON,TUE,WEB,THU,FRI,SAT};

    上述的语句有两个作用: 

    1)        将WEEKDAY声明为新类型。

    2)        将SUN,MON等作为符号常量。

    默认的情况下,将0值赋给第一个枚举量,1赋给第二个,依次类推。

    4.6.2  枚举类型的运算

    在不进行强制转换时,只能将定义枚举时使用的枚举量赋值给枚举变量。对于枚举,只定义了赋值运算,没有算术运算。然而,枚举类型是整型范畴,在算术运算中可以被编译器自动提升为int型。也就是说当表达式所有变量都是枚举时表达式是错误的,存在枚举变量和其他类型变量共存是,枚举自动提升为int型。Int型不能自动转换为枚举。如下: 

    WEEKDAY w1;
    WEEKDAY w2;
    w1 = MON;            //正确
    int day = MON * 3;    //正确
    w2 = 1;                //错误
    w2 = static_cast<WEEKDAY>(1);    //强制转换,正确    

 4.6.3  设置枚举值 

    在声明枚举时,可以显示给枚举量赋值,如下:

    enum COLOR{RED = 1, BLUE, BLACK = 0};

    这里BLUE值为2,因为RED=1,BLUE在RED之后,如果BLACK之后还有其他枚举量,那么一次为1,2,3…。 

    也可以创建多个值相同的枚举量,如下: 

    enum{ZERO, NO = 0, ONE, YES = 1};

    这里,ZERO和NO都为0,ONE和YES都为1。 

    4.6.4  枚举的取值范围

    C++通过强制类型转换,增加了可以赋值给枚举变量的合法值。取值范围定义为:首先要找到上限,需要知道枚举量的最大值,找到大于这个最大值的最小的2的幂,然后减一得到上限,如100的上限是127。然后找下限,如果枚举量大于0,则下限为0,否则按照上限同样的规则找到最大值,加负号。

4.7  指针

    指针(pointer)用于存储值的地址。

    *运算符称为间接值或可解除引用运算符。

    4.7.1  指针的声明

    指针声明时要在变量之前加*号,如下: 

    int * pInt;        //pInt是指针
    int * p1, p2;    //p1是指针,p2是int型

    注意野指针:指针如果没有被初始化或者是释放掉指针所指的内存空间后,指针会指向无效的内存地址,使用野指针会造成不可预知的错误,甚至是致命的错误。所以好的习惯是声明指针时即初始化,释放掉指针所指内存后给指针赋予nullptr值。 

    4.7.2  使用new和delete

    在C语言中使用malloc和free来动态分配和回收内存。在C++更多使用new和delete。

     使用new和delete为变量分配和回收内存:

    int * a = new int;
    *a = 1;
    cout << a << endl;
    delete a;

     使用new[]和delete[]为数组分配和回收内存:

    int * a;
    a = new int[10];
    a[0] = 1;
    delete[] a;

  注意:使用new和delete与new[]和delete[]要配对。

    使用new和delete要遵守如下规则:

    1)        不要使用delete来释放不是new分配的内存(不能释放malloc申请的内存);

    2)        不要使用delete释放同一内存超过一次;

    3)        如果使用new[]为数组分配内存,则应使用delete[]释放;

    4)        如果使用new为实体分配内存,则应使用delete释放;

    5)        对空指针使用delete是合法的。

4.7.3  指针、数组和指针运算

    在多数情况下,C++将数组解释为数组第一个元素的地址。

    将指针变量加1后,其增加的值等于指针所指向的类型的字节数。

    *(stack+1)与stack[1]等效

    Stack+1与&Stack+1等效。

    实际上,C++使用如下转换

    Array_name[i] 转换为 *(array_name+i)

    在多数情况下,可以用相同的方式使用指针名和数组名。有三个例外:

    1)        指针值可以被赋值修改,而数组名是常量,可以认为是指向常地址的指针。

    2)        Sizeof作用于数组得到数组的长度,作用于指针取得指针所占的字节数。

    3)        对数组名取地址时,数组名不会被解释为其地址。数组名被解释为数组第一个元素的地址,而对数组名使用地址运算符时,得到的是整个数组的地址。如下代码: 

    #include <iostream>
    using namespace std;
    int main()
    {
        short tell[10];
        cout << tell << endl;            //数组第一个元素的地址
        cout << &tell << endl;            //整个数组的地址
        cin.get();
    }

      结果为:

  0056FB6C
  0056FB6C

    虽然表面上看tell和&tell的值相同,但是他们的类型却是不同的。Tell如果给指针赋值,那么指针的类型应当是short *,而&tell入股给指针赋值,指针的类型应当是 short (*)[10]。

    数组和指针等价当它们做函数形式参数时。

    4.7.4  数组的动态联编和静态联编

    使用数组声明来创建数组是采用静态联编,数组的长度在编译时设置,分配在栈(stack)空间。

    使用new[]运算符来创建数组时,采用动态联编,在运行时分配。分配在堆(heap)空间。

    4.7.5  指针和字符串

    在cout和多数的C++表达式中,char数组名、char指针及用引号括起来的字符串常量都被解释为字符串第一个字符的地址。

    4.7.6  指向结构的指针

    使用指针来访问结构中的变量,C++提供了箭头成员运算符(->)。

    例如: p->value和(*p).value是等价的。

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

    1)自动存储

    在函数内部定义的常规变量,使用自动存储空间,被称为自动变量。在所属的函数被调用是自动产生,在该函数结束时消亡。自动变量通常存储在栈中。

    2)静态存储

    静态存储是整个程序执行期间都存在的存储方式。使变量成为静态的方式有两种。一是把变量定义在函数之外;二是使用static关键字在函数内部定义变量。

    3)动态存储

    使用new和delete运算符是动态存储。在堆中动态分配和回收。

4.9  数组的替代品

    数组的替代品有两种选择。一种是vector容器,另一种是C++11的array。想要学习请自行看库函数。

posted on 2014-09-03 22:01  Dream_Fish  阅读(236)  评论(0编辑  收藏  举报