C++—复合类型

内容概要:

  -创建和使用数组

  -创建和使用C-风格字符串

  -创建和使用string类字符串

  -使用方法getline()和get()读取字符串

  -混合输入字符串和数字

  -创建和使用结构

  -创建和使用共用体

  -创建和使用枚举

  -创建和使用指针

  -使用new和delete管理动态内存

  -创建动态数组

  -创建动态结构

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

  -vector和array类简介

 

1.1创建和使用数组

  1.1.1数组

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

  1.1.2数组的创建

    要创建数组需要使用数组声明,声明应该指出以下三点:

      -存储在每个元素中的值的类型;

      -数组名;

      -数组中的元素数。

    C++通过修改简单变量的声明,添加中括号(其中包含元素数目)来完成数组的声明,中括号中的元素数目可以省略让编译器自行推断。访问数组中的元数使用带索引的方括号来指定数组中的元素,C++中数组中元素的索引都是从0开始,即第一个元素的索引是0而不是1。

#include <iostream>

int main(int argc, const char * argv[]) {
    int array[4] = {0,1,2,3};
    int num = array[0];
    std::cout<<"num:"<< num << std::endl;
    int arr[] = {33,22,123,5};
    int num1 = arr[2];
    std::cout <<"num1:"<< num1 << std::endl;
    return 0;
}

输出结果:
num:0
num1:123

   1.1.3数组的初始化规则

    (1)只能在定义数组是才能使用初始化,此后就不能使用初始化,也不能将一个数组赋给另一个数组。可以使用下标给数组中的元素赋值。

 1 #include <iostream>
 2 
 3 int main(int argc, const char * argv[]) {
 4     int array[4] = {23,33,12,90};//可以
 5     int arr [4];//可以
 6     //arr[4] = {11,22,33,44};//不可以,因为arr[4]是指数组中的一个元素,不能将一个数组赋值给一个简单变量;并且,数组arr中也没有arr[4]这个元素(arr数组中元素的最大索引为3,索引为4指的是第5个元素,而arr中没有第5个元素)。
 7     //arr  = array;//不可以将一个数组赋给另一个数组
 8     arr[0] = 12;//可以使用下标给数组中的元素赋值
 9     
10     return 0;
11 }

    (2)初始化数组的时候,提供的值可以少于数组中元素的个数,编译器则将其它元素的值设置为0;

#include <iostream>

int main(int argc, const char * argv[]) {
    int arr[5] = {12,2};
    for (int m = 0; m < 5; m ++) {
        std::cout << "arr[" << m << "]:" << arr[m] << std::endl;
    }
    
    return 0;
}

输出结果:

arr[0]:12
arr[1]:2
arr[2]:0
arr[3]:0
arr[4]:0

    因此将数组中的所有元素初始化为0很简单,使用:int arr[4] = {0};

    注意:在定义数组的时候不进行初始化,其中元素的值为不确定的,元素的值将是之前存储在相应内存中的值。例如:

#include <iostream>

int main(int argc, const char * argv[]) {
    int arr[6] ;
    for (int m = 0; m < 6; m ++) {
        std::cout << "arr[" << m << "]:" << arr[m] << std::endl;
    }
    
    return 0;
}

输出结果:
arr[0]:0
arr[1]:0
arr[2]:0
arr[3]:0
arr[4]:1606416448
arr[5]:32767

//其中第5个元素arr[4]和第6个元素arr[5]的值不为0

    (3)如果初始化数组时放括号中为空,C++编译器将计算元素个数。例如:int arr[] = {1,2,3};编译器会自行计算出数组中有三个元素。

  1.1.4数组的初始化方法

    大括号初始化方法是一种通用初始化方法,C++对数组的初始化增加了一些新特性:

      (1)初始化数组时可以省略等号(=);

      (2)可在大括号中不包含任何东西,这将把所有元素设置为0;

#include <iostream>

int main(int argc, const char * argv[]) {
    int arr[3]{};//可以省略等号,可以在大括号中不包含任何东西(将把所有元素初始化为0)
    for (int m = 0; m < 3; m ++) {
        std::cout << "arr[" << m << "]:" << arr[m] << std::endl;
    }
    
    return 0;
}

输出结果:
arr[0]:0
arr[1]:0
arr[2]:0

    (3)列表初始化禁止缩窄转换

int arr[4] = {12,22,3.0,2.5}; //不允许,第三个和第四个元素都是float类型,不能缩窄变换为int类型

 

 

1.2字符串

  字符串是存储在内存的连续字节的一系列字符。C++处理字符串有两种方式,一种是采用C风格字符串,另一种是基于string类库。

   C字符串具有一种特殊的性质,以空字符('\0')结尾,写作\0,其ASCII码为0;

#include <iostream>

int main(int argc, const char * argv[]) {
    char name[4] = {'n','a','m','e'};//不是字符串,因为没有以'\0'结尾
    char father[5] = {'n','a','m','e','\0'};//是字符串
    std::cout << "name:" <<name << std::endl; //cout在打印完name中所有字符后,会接着把随后的各个字节打印出来,直到遇到空字符为止
    std::cout << "father:" << father << std::endl;
    return 0;
}

输出结果:
name:nameP\370\277_\377
father:name

  初始化字符串的方法:

   使用数组的方式初始化字符串比较麻烦,并且还很容易忘记最后加上空字符(如果不加空字符就不能算作字符串,只能算作字符数组)。有一种很简便的的方法初始化字符串,使用一个用引号括起来的字符串即可,这种字符串被称作字符串常量或者字符串字面值;并且,这种初始化方法隐式地包括了结尾的空字符。同样,在计算字符串常量的长度时,要算上被隐藏了的空字符,不能看表面上的字符个数。

 1 #include <iostream>
 2 
 3 int main(int argc, const char * argv[]) {
 4     char name[] = "name";
 5     int num = sizeof(name);
 6     std::cout<< "name:" << name << std::endl;
 7     std::cout << "num:" << num << std::endl;
 8     return 0;
 9 }
10 
11 输出结果:
12 name:name
13 num:5

  注意:使用字符串常量初始化字符数组时,让编译器计算元素个数更加安全。同时,在计算存储字符串常量的字符数组时,不要忘记被隐藏掉了的空字符。例如:

char ch = "a";//这样将会收到编译器的错误警告。

  1.2.1 拼接字符串常量

    任何两个由空白(空格、制表符和换行符)分隔的字符串常量都将自动拼接成一个。

    注意: 拼接时,不会在被链接的字符串之间添加空格,第二个字符串的第一个字符将紧跟第一个字符串的最后一个字符(不考虑\0),第一个字符串的\0将被第二个字符串的第一个字符替代。 

  1.2.2 在数组中使用字符串

    要将字符串存储到数组中,最常见的方法有两种——将数组初始化为字符串常量、将键盘或文件输入读入到数组中。

 1 #include <iostream>
 2 #include <cstring> //为strlen()函数提供原型
 3 
 4 int main(int argc, const char * argv[]) {
 5     const int num = 15;
 6     char name[num] = {};
 7     char father[num] = {};
 8     std::cout << "请输入您的名字:\n";
 9     std::cin >> name;
10     std::cout <<"你的名字长度是"<< strlen(name) << std::endl;
11     std::cout << "请输入您父亲的名字:\n";
12     std::cin >> father;
13     std::cout << name << "的父""亲是" << father << std::endl;//字符串的拼接
14     
15     return 0;
16 }
17 
18  输出结果:
19 请输入您的名字:
20 liyi //用户输入
21 你的名字长度是4 //strlen()函数返回的是数组中字符串的长度,如果把strlen()换成sizeof()函数,返回的将是数组的整个数组的大小
22 请输入您父亲的名字:
23 liruhua //用户输入
24 liyi的父亲是liruhua

  1.2.3字符串输入

  例子:

#include <iostream>

int main(int argc, const char * argv[]) {
    const int num = 15;
    char name[num] = {};
    char father[num] = {};
    std::cout << "请输入您的名字:\n";
    std::cin >> name;
    std::cout <<"你的名字长度是"<< strlen(name) << std::endl;
    std::cout << "请输入您父亲的名字:\n";
    std::cin >> father;
    std::cout << name << "的父""亲是" << father << std::endl;//字符串的拼接
    
    return 0;
}

输出结果:
请输入您的名字:
Jum li //用户输入
你的名字长度是3
请输入您父亲的名字://注意这里,这里接下来因该是用户输入
Jum的父亲是li

  说明:上述代码中,在“请输入您父亲的名字”这里直接跳过了,而最后输出的时候却把“请输入您的名字:”接下来输入的内容拆分成了两部分,分别保存在name数组和father数组中。导致这种情况的原因是,由于用户不能从键盘输入空字符(\0),cin函数使用空白(制表符、换行符和空格)来确定字符串的结束位置,这意味着cin在获取字符串的时候只获取一个单词,并自动在其后面加上空字符。这个例子的实际结果是,cin把第一个单词Jum读取并保存在name数组中,而把单词li保留在输入队列中,在第二次读取的时候它发现了单词li,读取并保存到father数组中,因此才导致直接跳过“请输入您父亲的名字:”紧接着的输入步骤。

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

   C++为了读取整行的输入,istream中的类提供了类成员函数(比如cin)getline()和get()来读取整行的输入。

  说明:getline()和get()比较

    共同点:getline()和get()这两个函数都读取一行输入,直到到达换行符。

    不同点:getline()读取完整行输入后将丢弃换行符,但在存储字符串时他用空字符来替换换行符;而get()将换行符保留在输入序列中。

  (1)getline()

  getline()函数读取整行,它使用通过回车键输入的换行符来确定输入结尾。

  函数调用方法:cin.getline();

  函数说明:getline()函数有两个参数,第一个参数适用来存储输入行的数组的名称;第二个参数是要读取的字符数。如果第二个参数为n,则函数最多读取n-1个字符,余下的空间(剩下的空间全部被添加了空字符)用于存储自动在结尾处添加的空字符。getline()函数在读取指定数目的字符或者遇到换行符停止读取。

 1 #include <iostream>
 2 
 3 int main(int argc, const char * argv[]) {
 4     char name[6] {};
 5     std::cout << "请输入一段字符串存入name:\n";
 6     std::cin.getline(name, 4);//getline()函数实际上只能读取3个字符,即如果getline()函数的第二个参数为n,那么getline()函数实际上只能读取n-1个字符,第n个将会被来保存空字符;另外,getline()函数如果没有读满n-1个字符,字读取了m个字符,那么其余的n-m个都会是空字符。
 7     std::cout << "打印字符串:" << name << std::endl;
 8     for (int m = 0; m < 6;  m++) {
 9         std::cout << "name[" << m << "]:" << name[m] << std::endl;
10     }
11 
12     return 0;
13 }
14 
15 输出结果:
16 请输入一段字符串存入name:
17 abcdefg //用户输入
18 打印字符串:abc
19 name[0]:a
20 name[1]:b
21 name[2]:c
22 name[3]:
23 name[4]:
24 name[5]:

  (2)面向行的输入get()

    1),get()函数有几种变体。其中一种变体的工作方式和getline()类似,他们接收相同的参数,解释参数的方式也相同,并且都读取到行尾。但get()并不在读取并丢弃换行符,而是将其留在输入队列中。

 1 #include <iostream>
 2 
 3 int main(int argc, const char * argv[]) {
 4     char name[6] {};
 5     char name1[6] {};
 6     std::cout << "请输入字符串保存在name数组中:\n";
 7     std::cin.get(name,3);
 8     std::cout << "请输入字符串保存到name1数组中:\n";
 9     std::cin.get(name1,3);
10     std::cout << "name:" << name << "\n" << "name1:" << name1 << std::endl;
11 
12     return 0;
13 }
14 
15 输出结果1:                                输出结果2:                           输出结果3:
16 请输入字符串保存在name数组中:               请输入字符串保存在name数组中:          请输入字符串保存在name数组中:
17 aa   /*用户输入*/                         abc /*用户输入*/             a  /*用户输入*/
18 请输入字符串保存到name1数组中:              请输入字符串保存到name1数组中:         请输入字符串保存到name1数组中:
19 name:aa                                  name:ab                             name:a
20 name1:                                   name1:c                       name1:

  说明:上段代码中,由于第一次调用后,换行符被保留在输入队列中,因此第二次调用时看到第一个字符便是换行符,由此get()认为已经到达结尾。

  2),get()函数有另一种变体,即不带任何参数,用来读取下一个字符(即使是换行符),可用它来处理换行符。

  3)第三种变体,是将两个类成员函数拼接起来(合并),例如:

  cin.get(name,size).get();//之所以这样做,是因为get(name,size)返回的是一个cin对象。但是不带参数的get()没有返回值。

  cin.get(name,size).get(name2,size).get();

  (3)空行和其他问题

  当getline()或get()读取空行时,最初的做法是,下一条语句将在前一条getline()和get()结束读取的位置开始读取;但当前的做法是,当get()(而不是getline())读取空行后将设置失效位。这意味着接下来的输入将会被阻断,但是可以用下面的方法恢复输入:

 cin.clear();

  1.2.5混合输入字符串和数字

  当用cin读取数字,然后紧接着用getline()读取字符串,将会导致问题。在cin读取数字以后,将回车键生成的空字符留在了输入队列中,接下来getline()在读取的时候就会遇到空字符,将认为是一个空行。解决的办法是在读取字符串之前先读取并丢弃换行符,可以通过集中方法来实现,可以采用不带参数的get()函数解决。

1.3string类简介 

  要使用string类,必须在程序中包含头文件string。string类位于名称空间std中,因此必须使用一条suing编译指令,或者使用std::string来引用它。

  string类隐藏了字符串的数组性质,因此可以像处理其他普通变量那样处理string类。

  

 1 #include <iostream>
 2 #include <string>//要想使用string类,必须先包含string文件
 3 
 4 int main(int argc, const char * argv[]) {
 5     using namespace std;//string类位于命名空间std中,因此需要使用using编译指令,或者使用std::string
 6     char charr1[20];
 7     char charr2[20] = "father";//使用字符串常量初始化字符数组,字符串常量在结尾处隐式包含了空字符
 8     string str1;
 9     string str2 = "mother";
10     cout << "请输入一个字符串:\n";
11     cin >> charr1;
12     cout << "请输入另一个字符串:\n";
13     cin >> str1;
14     cout << "这里有一些字符串:\n" << "charr1:" << charr1 << endl <<"str1:" << str1 << endl;
15     cout << "charr2中第3个字符是" << charr2[2] << endl;
16     cout << "str2中的第3个字符是" << str2[2] << endl;
17     
18     return 0;
19 }
20 输出结果:
21 请输入一个字符串:
22 myName //用户输入
23 请输入另一个字符串:
24 yourName //用户输入
25 这里有一些字符串:
26 charr1:myName
27 str1:yourName
28 charr2中第3个字符是t
29 str2中的第3个字符是t

 

  从这个例子中可以看出,在很多方面,使用string类与使用字符数组相同:

    1)可以使用C风格字符串来初始化string对象;

    2)可以使用cin将键盘输入存储到string对象中;

    3)可以使用cout来显示string对象;

    4)可以使用数组表示法来访问存储在string对象中的字符。

  string与字符数组的区别:可以将string对象声明为简单变量,而不是数组。

  类设计可以让程序自动处理string的大小。如上面的例子中,str1的声明创建了一个长度为0的string对象,当用cin将输入读取到str1中的时候,程序将自动调节str1的长度。

  1.3.1 string的初始化

 

  C++允许将列表初始化用于C风格字符串和string对象。

  char name[] = {"my son"};

  char name1[]{"my son"};

  string str1 = {"your son"};

  string str2{"his dog"};

   string简化了字符串的合并操作,可以使用+将两个string对象合并起来,还可以使用+=将字符串附加到string对象的末尾。

 1 #include <iostream>
 2 #include <string>//要想使用string类,必须先包含string文件
 3 
 4 using namespace std;
 5 int main(int argc, const char * argv[]) {
 6     string str1 {"My dog's"};
 7     string str2 {" name is Jum "};
 8     string str3;
 9     cout << (str3 = str1 + str2 )<< endl; //使用+将两个string对象拼接
10     cout << (str1 += str2) << endl; //使用+=将一个字符串符加到string对象的末尾
11     return 0;
12 }
13 
14 输出结果:
15 My dog's name is Jum 
16 My dog's name is Jum 

  1.3.2 string对象的其他操作

  

 1 #include <iostream>
 2 #include <string>//要想使用string类,必须先包含string文件
 3 #include <string.h>
 4 
 5 using namespace std;
 6 int main(int argc, const char * argv[]) {
 7     char charr[]{};
 8     char charr1[]{"apple"};
 9     string str{"fruit"};
10     string str1;
11 
12 
13     
14     strcpy(charr, charr1);//将charr1复制到charr,C字符窜风格
15     cout << "charr:" << charr <<",charr的长度:" << strlen(charr) << endl; //strlen()函数用来计算C风格字符串的长度
16     strcat(charr, " fruit");//将字符串"fruit"附加到charr结尾
17     cout << "charr:" << charr << ", charr的长度:" << strlen(charr) << endl;
18     
19     //strcpy(str1, str);不能使用strcpy()或者strcat()对string对象进行操作
20     str1 +=str;
21     cout << "str1:" << str1 << ",str1的长度:" << str1.size() << endl;//可以使用string对象的成员函数size()来计算string对象的长度
22     
23 }
24 
25 输出结果:
26 charr:apple,charr的长度:5
27 charr:apple fruit, charr的长度:11
28 str1:fruit,str1的长度:5
#include <iostream>
#include <string>//要想使用string类,必须先包含string文件
#include <string.h>

using namespace std;
int main(int argc, const char * argv[]) {
    char charr[20];
    char charr1[20];
    string str;
    
    cout << "输入前charr的长度:" << strlen(charr) << endl;
    cout << "输入前str的长度:" << str.size() << endl;
    cout << "输入前charr1的长度:"<< strlen(charr1) << endl << endl;
    
    cout << "请输入一段字符串存入charr:\n";
    cin.getline(charr, 20); 
    cout << "你输入的字符串charr:" << charr << endl << endl;;
    
    cout << "请输入一段字符串存入str:\n";
    getline(cin,str);//这个时候cin变成了一个参数,不需要说明字符串的读取长度
    cout << "你输入的字符串str:" << str << endl << endl;
    
    cout << "charr的长度:" << strlen(charr) << endl;
    cout << "str的长度:" << str.size() << endl;
    
    
    
    return 0;
    
}

输出结果:
输入前charr的长度:0
输入前str的长度:0  //str的长度为0,是因为未被初始化的string对象的长度自动被设置为0
输入前charr1的长度:6  //charr和charr1都没有进行初始化,导致charr的长度为0而charr1的长度为6的原因是:strlen()从数组中的第一个元素开始计算,一直到遇到空字符结束。

请输入一段字符串存入charr:
apple //用户输入
你输入的字符串charr:apple

请输入一段字符串存入str:
namee //用户输入
你输入的字符串str:namee

charr的长度:5
str的长度:5

  说明:

    cin.getline(charr,20);

  这种点句法说明getline()是cin的一个类方法,第一个参数为目标数组;第二个参数为读取长度,getline()使用它避免超越数组长度。

    getline(cin,str);

  这里没有使用点句法,表明getline()不是类方法。它将cin作为参数,表明到哪里去查找输入;此外,也没有指出字符串长度,因为string根据字符串的长度自动调节字节大小。

  1.3.3 其它形式的字符串字面值

  C++除了char类型外,还有wchar_t、char16_t和char32_t,可以创建这些类型的数组和这些类型的字符串字面值。对于这些类型的字符串字面值,C++使用前缀L、u和U表示。

  C++11中新增一种原始(raw)字符串。在原始字符串中,字符串表示的就是自己,原始字符串不再使用"作为界定,而是使用“(”和“)”作为界定,并使用前缀R来标识原始字符串。输入原始字符串时,按回车键不仅会移到下一行,还将在原始字符串中加入回车字符。在原始字符串中当遇到第一个“)”时,将会认为字符串到此结束,如果想要在原始字符串中包含“)”,可以用自定义定界符,方法是在默认定界符之间添加人意数量的基本字符,但空格、左括号、右括号、斜杠和控制字符(如制表符和换行符)除外。

  可将R和其他字符串前缀结合使用,以标识wchar_t等类型的原始字符串。可以将R放在前面,也可以将R放在后面,如:Ru,uR等。

 1 #include <iostream>
 2 
 3 using namespace std;
 4 int main(int argc, const char * argv[]) {
 5     
 6     wchar_t wCharr[] {L"my apple"}; //wchar_t string
 7     char16_t uCharr[] {u"your apple"}; //char16_t string
 8     char32_t UCharr[] {U"his apple"}; // char32_t string
 9     
10     cout << "wCharr:" << wCharr << endl;
11     cout << "uCharr:" << uCharr << endl;
12     cout << "UCharr:" << UCharr << endl;
13     cout <<L"my name" << endl;
14     cout << u"your name" << endl;
15     cout << U"his name" << endl;
16     cout << R"(Hi! "Jum" We use "\n" instead of endl.)" << endl; //原始字符串
17     cout <<R"+(Hi! My apple's(a computer) price is $900.)+" << endl;//自定义定界符+()+
18     
19     return 0;
20     
21 }
22 输出结果:
23 wCharr:0x7fff5fbff7b0
24 uCharr:0x7fff5fbff790
25 UCharr:0x7fff5fbff760
26 0x100001e68
27 0x100001ec6
28 0x100001e88
29 Hi! "Jum" We use "\n" instead of endl.
30 Hi! My apple's(a computer) price is $900.

 1.4 结构体

  结构是用户自己定义的类型,而结构声明定义了这种类型的数据属性。创建结构体包含两步:

    (1)定义结构描述——它描述并标记了能够存储在结构体中的各种数据类型;

    (2)按描述创建结构变量(结构数据对象)。

 1 #include <iostream>
 2 #include <string>
 3 
 4 using namespace std;
 5 struct person{   //关键字struct表明这些代码定义的是一个结构的布局,标识符person是这种数据格式的名称
 6     string name;
 7     int age;
 8     int ID;
 9    
10 };
11 void print();
12 int main(int argc, const char * argv[]) {
13     
14     person onePerson;
15     onePerson.age = 10; //结构体使用成员运算符(.)访问或设置其中的各个成员
16     onePerson.ID = 1001;
17     onePerson.name = "MuPiaomiao";
18     cout << "onePerson的name:" << onePerson.name << endl;
19     cout << "onePerson的age:" << onePerson.age << endl;
20     cout << "onePerson的ID:" << onePerson.ID << endl;
21     
22     
23     return 0;
24 }
25 
26 输出结果:
27 onePerson的name:MuPiaomiao
28 onePerson的age:10
29 onePerson的ID:1001
 1 #include <iostream>
 2 #include <string>
 3 
 4 using namespace std;
 5 struct person{   //关键字struct表明这些代码定义的是一个结构的布局,标识符person是这种数据格式的名称
 6     string name;
 7     int age;
 8     int ID;
 9    
10 };
11 void print();
12 int main(int argc, const char * argv[]) {
13     
14     person onePerson = { //和数组一样,结构也可以使用列表初始化方法,并且“=”可以省略;其次,如果大括号中未包含任何东西,各个成员都将被设置为0
15         "MuPiaomiao",   //和数组一样,使用逗号","分隔值列表,并将这些值用花括号括起来。同时,不支持缩窄变换
16         10,
17         1001
18     };
19     cout << "onePerson的name:" << onePerson.name << endl;
20     cout << "onePerson的age:" << onePerson.age << endl;
21     cout << "onePerson的ID:" << onePerson.ID << endl;
22     
23     
24     return 0;
25 }
26 
27 输出结果:
28 onePerson的name:MuPiaomiao
29 onePerson的age:10
30 onePerson的ID:1001

  说明:结构体的声明的位置很重要,有两种选择。第一种,可以将声明放在函数中,紧跟在开始括号的后面,称为内部声明;第二种,是放在函数的外面,称为外部声明。这两种声明有所区别,对于内部声明,只能被包含该声明的函数使用;而外部声明可以被其后面的人和函数使用。

  1.4.1 其他结构属性

  (1)可以将结构作为参数传给函数,也可以让函数返回一个结构

  (2)可以使用赋值运算符将一个结构赋给另一个相同类型的结构,这样结构中的每一个成员也将赋给另一个相同类型结构中对应的成员;这种赋值称为成员赋值。  

 1 #include <iostream>
 2 #include <string>
 3 
 4 using namespace std;
 5 struct person{
 6     string name;
 7     int age;
 8     int ID;
 9    
10 };
11 person grow(person per);
12 int main(int argc, const char * argv[]) {
13     
14     person onePerson = { //结构体的初始使用通用的{}初始化方法
15         "MuPiaomiao",   //注意,初始化结构体时,各个成员之间用","隔开,不能用分号
16         10,
17         1001
18     };
19     person thePer = grow(onePerson);//可以将一个结构作为参数传给一个函数,函数也可以将结构作为返回值返回
20     
21     
22     
23     return 0;
24 }
25 person grow(person per){
26     person oneper = per;//可以使用赋值运算符将一个结构赋给另一个相同类型的结构,这样结构中的每一个成员也将赋给另一个相同类型结构中对应的成员;这种赋值称为成员赋值。
27     oneper.age ++;
28     cout << "per的name:" << oneper.name << endl;
29     cout << "per的age:" << oneper.age << endl;
30     cout << "per的ID:" << oneper.ID << endl;
31     return oneper;
32 }
33 
34 输出结果:
35 per的name:MuPiaomiao
36 per的age:11
37 per的ID:1001

  (3)可以声明没有名称的结构类型,方法是省略名称,同时定义一个结构类型和一个这种类型的变量。例如:

    struct {

      int age;

      string name;

      int ID;

    } student;

  (4)C++结构除了可以有成员变量以外,还可以有成员函数。

  1.4.2 结构中的位字段

    C++允许指定占用特定位数的结构成员,这使得创建与某个硬件设备上的寄存器对应的数据结构非常方便。字段的类型为整型或者枚举,接下来是冒号,冒号后面是数字,它指定了使用的位数。可以使用没有名称的字段来提供间距。每个成员都被称为位字段。

  struct person{

    int age : 4;

    int :4; // 使用没有名称的字段来提供间距

    bool switch :1;

  };

  可以向通常那样来初始化这些字段,还可以使用标准的结构表示法来访问为字段。

  person onePer = {12,true};

  if(onePer.switch) cout << "Switch is on" << endl;

  为字段一般用在低级变成中。

1.5 共用体

  共用体是一种数据格式,它能够存储不同的数据类型,但只能同时存储其中的一种类型。共用体的句法与结构体相似,但是含义不同。共用体中的成员名称标识了共用体的变量的容量。由于共用体每次只能存储一个值,因此它必须有足够的容量来存储最大的值,所以,共用体的长度为其最大成员的长度。

  共用体的用途之一是,当数据项使用两种或更多种(但不会同时使用)格式时,可节省空间。

 1 #include <iostream>
 2 #include <string>
 3 
 4 using namespace std;
 5 union goods{  //比如在给商品做记录的时候,有的商品用字符串进行标识,而有的商品用数字进行标识,这个时候就可以使用共用体
 6     char chName;
 7     int noName;
 8 };
 9 
10 int main(int argc, const char * argv[]) {
11     goods goodsName1;//代表一种商品
12     goods goodsName2;//代表一种商品
13     goodsName1.chName = 'A';//商品goodsName1 使用字母'A'进行标识
14     goodsName2.noName = 1; //商品goodsName2 使用数字1进行标识
15     cout << goodsName1.chName << endl;
16     cout << goodsName2.noName << endl;
17     cout << sizeof(goods) << endl;
18     cout << sizeof(goodsName1) << endl;
19     cout << sizeof(goodsName2) << endl;
20     
21     return 0;
22 }
23 
24 输出结果:
25 A
26 1
27 4
28 4
29 4

 

 匿名共用体没有名称,其成员将成为位于相同地址处的变量。

  共用体常用语节省内存,也常用语操作系统数据结构或硬件数据结构。

1.6 枚举 

  C++的enum提供了另一种创建符号常量的方式,这种方式可以代替const。它允许定义新类型,但必须按照严格的限制进行。使用enum的语法与结构相似。

  enum color {red,black,white,blue,green};

在默认情况下,将整数值赋给枚举量,第一个枚举量的值为0,第二个枚举量的值为1,后面一次类推。如果给枚举中的某个枚举量赋有初值,则其后面的枚举量会在此初值上依次递增。

#include <iostream>

using namespace std;
enum color{red,yellow,blue,black,white};//枚举量的值,会从0开始一次递增
enum grade{A = 90 ,B = 80 ,C ,D, F = 80 };//枚举量的值可以重复,如果其后面的枚举量没有制定值,那么就会一次递增
int main(int argc, const char * argv[]) {
    
    cout << "red:" << red << endl;
    cout << "yellow:" << yellow << endl;
    cout << "blue:" << blue << endl;
    cout << "black:" << black << endl;
    cout << "white:" << white << endl;
    
    cout << "A:" << A << endl;
    cout << "B:" << B << endl;
    cout << "C:" << C << endl;
    cout << "D:" << D << endl;
    cout << "F:" << F << endl;
    
    return 0;
    
}

输出结果:
red:0
yellow:1
blue:2
black:3
white:4
A:90
B:80
C:81
D:82
F:80

  说明:枚举主要是声明了一种类型,最初,这种类型的取值范围为声明中指出的值。然而,C++现在通过强制类型转换,增加了可以赋给枚举变量的合法值。每个枚举都有取值范围,通过强制类型转换,可以将取值范围中的整数赋给枚举变量。

  取值范围的定义如下:首先要找出上限,需要找出枚举量的最大值;然后,找到大于这个最大值的、最小的2的幂,将它减去1,得到的便是取值范围的上限。要计算下限,需要知道枚举量的最小值;如果它的值不小于0,则取值范围的下限为0;否则,采用与寻找上限方式相同的方法,只不过要加上负号。例子:

 1 #include <iostream>
 2 
 3 using namespace std;
 4 enum color{red = -7,black = 17,green = 99};//该枚举中,最小值为-7,最大值为99
 5 int findMin(int num); //寻找下限,参数为枚举之中的最小值
 6 int findMax(int num); //寻找上限,参数为枚举一种的最大值
 7 int main(int argc, const char * argv[]) {
 8     
 9     cout << "color的下限:" << findMin(-7) << endl;
10     cout << "color的上限: " << findMax(99) << endl;
11     
12     color band  = color(126);//在这里color是一种数据类型,color(126)表示把整数126强制转换为color类型,但是被转换的数据不能超出枚举的取值范围,在这里color的取值范围为-7~127.
13     cout << band << endl;
14     color band1 = color (-5);//不能将整数直接赋给枚举变量,但是可以使用给枚举变量对应的枚举类型对整数进行强制类型转换后在赋值给枚举变量
15     cout << band1 << endl;
16     return 0;
17     
18 }
19 int findMin(int num){
20     if (num >= 0) {
21         return 0;
22     }
23     int number = 1;
24     while (number <= -num) {
25         number *=2;
26     }
27     return -(number - 1) ;
28 }
29 int findMax(int num){
30     int number = 1;
31     while (number <= num) {
32         number *=2;
33     }
34     return number-1;
35 }
36 
37 输出结果:
38 color的下限:-7
39 color的上限: 127
40 126
41 -5

 

 

1.7 指针和自由存储空间

  计算机程序在存储数据时必须跟踪的3种基本属性:

    (1)信息存在何处

    (2)存储的值为多少

    (3)存储的信息是什么类型

在声明一个简单变量的时候,声明语句指出了值的类型和符号名,还让程序为值分配内存,并在内部跟踪该内存单元。

  指针:指针是一个变量,其存储的值是地址,而不是值本身

  怎么获取变量的地址?对变量使用地址运算符(&),就可以获得他的位置。在计算机中,显示地址一般用十六进制进行表示。

  使用常规变量时,值是指定的量,而地址是派生量。处理存储数据的新策略恰好相反,将地址视为制定的量,而将值视为派生量。一种特殊类型的变量——指针,用于存储值的地址,因此指针名表示的是地址。*运算符被称为间接值或解除引用运算符,将其用于指针,可以得到该地址存储的值。

  1.7.1 声明指针和初始化指针

  int *p_num;

  由于*运算符被用于指针,因此p_num变量本身必须是指针;int 表明指针p_num指向int类型。即声明指针的常规模式:

    类型 *指针名称;

  对每个指针变量名都需要使用*,如:

    int *p1,p2;

  该声明声明了一个指向int类型的指针p1,和int变量p2 。说明,因为*运算符作用于指针,用于取出地址中存储的值,int *p1;的声明可以理解为声明了一个名为*p1的int变量。因此,在声明指针的时候*必不可少,声明多少个指针就需要多少个*号。   

   1.7.2 指针的危险

  值得注意的是,在C++中创建指针时,计算机将分配用来存储地址的内存,但不会分配指针所指向的数据的内存,为数据提供空间是一个独立的步骤。

  注意:一定要在指针应用解除指针运算符(*)之前,将指针初始化为一个确定的、适当的地址。

  1.7.3 指针和数字

  不能将数字直接赋值给指针应该通过强制类型转换将数字转换成适当的地址类型。

  int *pt;

  pt = (int *)0xB8000000; //必须使用强制类型转换

  1.7.4 使用new来分配内存

  指针的真正用武之地在于,在运行阶段分配为命名的内存以存储值;在这种情况下,只能通过指针来访问内存。

  new运算符:使用new运算符时,需要告诉new要为那种数据类型分配内存;new将找一个长度正确的内存块,并返回该内存的地址,需要把该地址赋给一个指针。

  int *pt = new int;

  通用格式为:

  typeName *pointer_name = new typeName; //需要在两个地方指定数据类型:用来指定需要什么样的内存和用来声明合适的指针。

  new分配的内存块与常规变量声明分配的内存块不同。常规变量声明的变量被存储在被称为栈的内存区域中,而new从称为堆或自由存储区的内存区域分配内存。

  1.7.5 使用delete释放内存

  delete运算符使得在使用完内存后能够将其归还给存储池。使用delete时,后面要加上指向内存块的指针(使用new分配的内存块)。一定要配对的使用new和delete(即new一个内存块,最后必须对应地使用delete释放相应的内存块)。同时,也只能使用delete释放通过new分配的内存块,不能使用delete来释放声明变量所获得的内存块。delete用于new的地址,而不管这个地址是使用的哪一个指针指向的。

  int *pt = new int;

  ...

  delete pt;

  注意:delete能够释放指针所指向的内存块,但是不会删除指针。

  1.7.6 使用new来创建动态数组  

  如果通过声明来创建数组,则在程序编译时给它分配内存,这种方式叫做静态联编,意味着数组实在编译时加入到程序中的。但使用new时,如果在运行阶段需要它,则创建;如果不需要,则不创建;还可以在程序运行时选择数组的长度;这被称为动态联编,意味着数组实在程序运行时创建的。通过动态联编创建的数组叫做动态数组。

  动态联编和静态联编的区别:使用静态联编时,必须在编写程序时指定数组的长度;使用动态联编时,程序将在运行时确定数组的长度。

  如何使用new创建数组和使用指针访问数组元素:

  (1)使用new创建动态数组

    创建动态数组很简单:只需要将数组的元素类型和元素数目告诉new即可。必须在类型名后面加上放括号,其中包含元素数目。例如,创建包含10个int类型的数组:

    int *ptr = new int [10];

  new返回第一个元素的地址。

  对于使用new创建的数组,应该使用另一种方式的delete来释放:

    delete [] ptr;

   方括号告诉程序,应释放整个数组,而不是指针所指向的元素。

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

    1)不要使用delete来释放不是new分配的内存;

    2)不要使用delete释放同一个内存块两次;

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

    4)如果使用new []为一个实体分配内存,则应使用delete(不带方括号)来释放内存。

    5)对空指针使用delete是安全的。

  为数组分配内存的通用格式如下:

    kype_name * pointer_name = new type_name [num_elements];

  使用new运算符可以确保内存块足以存储num_elements 个类型为type_name的元素,而pointer_name将指向第一个元素。

  (2)使用动态数组

    访问动态数组元素:只需要把指针当成数组名即可。说明:实际上,在声明常规数组的时候,数组名就是一个指向数组第一个元素的指针。

  

 1 #include <iostream>
 2 using namespace std;
 3 
 4 int main(int argc, const char * argv[]) {
 5     int *ptr = new int [3];
 6     ptr[0] = 12;
 7     ptr[1] = 15;
 8     ptr[2] = 18;
 9     cout << "ptr[0]的值为:" << ptr[0] << endl;
10     ptr = ptr + 1; //这种方法用于指针是可以的,这样指针ptr将会指向数组的第2个元素;但是这种方法不能用于数组名
11     cout << "ptr=ptr+1后,ptr[0]的值为:" << ptr[0] << endl;
12     ptr = ptr - 1;
13     delete [] ptr;
14     
15     return 0;
16 }
17 
18 输出结果:
19 ptr[0]的值为:12
20 ptr=ptr+1后,ptr[0]的值为:15

 

  说明:不能修改数组名。但指针是变量,可以修改指针的值。

 

1.8 指针、数组和指针算术
  1.8.1 

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

 1 #include <iostream>
 2 using namespace std;
 3 
 4 int main(int argc, const char * argv[]) {
 5     double arr1 [3] = {10000.0,20000.0,30000.0};
 6     int arr2 [3] = {1,2,3};
 7     
 8     double *ptr1 = arr1;
 9     int *ptr2 = arr2;
10     
11     cout << "ptr1 = " << ptr1 << "; " << "*ptr1 = " << *ptr1 << endl;
12     ptr1 = ptr1 + 1;
13     cout << "给ptr1加1后:" << endl;
14     cout << "ptr1 = " << ptr1 << "; " << "*ptr1 = " << *ptr1 << endl << endl;
15     
16     cout << "ptr2 = " << ptr2 << "; " << "*ptr2 = " << *ptr2 << endl;
17     ptr2 = ptr2 + 1;
18     cout << "给ptr2加1后:" << endl;
19     cout << "ptr2 = " << ptr2 << "; " << "*ptr2 = " << *ptr2 << endl << endl;
20     
21     cout << "数组arr1的前两个的元素:\n";
22     cout << "arr1[0] = " << arr1[0] << ";" << "arr1[1] = " << arr1[1] << endl;
23     cout << "指针ptr1前两个的元素:\n";
24     cout << "*(ptr1) = " << *ptr1 << ";" << "*(ptr1 + 1) = " << *(ptr1 + 1) << endl << endl;
25     
26     cout << "数组arr1的大小:" << sizeof(arr1) << endl;//计算的是整个数组的大小
27     cout << "指针ptr1的大小:" << sizeof(ptr1) << endl;//仅仅返回指针的大小,而不是指针所指向的内存的大小。
28     
29     
30     return 0;
31 }
32 
33 输出结果:
34 ptr1 = 0x7fff5fbff7f0; *ptr1 = 10000
35 给ptr1加1后:
36 ptr1 = 0x7fff5fbff7f8; *ptr1 = 20000
37 
38 ptr2 = 0x7fff5fbff7e4; *ptr2 = 1
39 给ptr2加1后:
40 ptr2 = 0x7fff5fbff7e8; *ptr2 = 2
41 
42 数组arr1的前两个的元素:
43 arr1[0] = 10000;arr1[1] = 20000
44 指针ptr1前两个的元素:
45 *(ptr1) = 20000;*(ptr1 + 1) = 30000
46 
47 数组arr1的大小:24
48 指针ptr1的大小:8

 

  通常使用数组表示法时,C++都会执行一下转换:

  array_name[i] 变换成 *(array_name + i);

  如果使用的是指针,而不是数组名,则C++会执行同样的转换:

  pointer_name[i] 变换成 *(pointer_name + i);

  因此,在多数情况下,可以相同的方式使用指针名和数组名。对于他们,可以使用数组方括号表示法,也可以使用解除引用运算符(*)。在多数情况中,他们都表示地址。不同的是,可以修改指针的值,而数组名是常量。

  另一个区别是,对数组名使用sizeof运算符,得到的是数组的长度;而对指针名使用sizeof运算股,得到的是指针的长度。

  

 1 #include <iostream>
 2 using namespace std;
 3 
 4 int main(int argc, const char * argv[]) {
 5     int arr[4] = {1,2,3,4};
 6     
 7     
 8     cout << "   sizeof(*arr) = " << sizeof(*arr) << endl;
 9     cout << "sizeof(*(&arr)) = " << sizeof(*(&arr) ) << endl<< endl; //说明对数组名取地址得到的是整个数组的地址
10     
11     cout << "      arr = "<< arr << endl;
12     cout << "(arr + 1) = " << (arr + 1) << endl<<endl;
13     cout << "      &arr = " << &arr << endl;
14     cout << "(&arr + 1) = " << (&arr + 1) << endl; //对数组名取地址再加1.
15     
16     
17     return 0;
18 }
19 
20 输出结果:
21    sizeof(*arr) = 4
22 sizeof(*(&arr)) = 16
23 
24       arr = 0x7fff5fbff7f0
25 (arr + 1) = 0x7fff5fbff7f4
26 
27       &arr = 0x7fff5fbff7f0
28 (&arr + 1) = 0x7fff5fbff800

说明:数组名被解释为数组第一个元素的地址,但是对数组名取地址得到的则是整个数组的地址;正如上面的例子中,虽然数组中第一个元素的地址和整个数组的地址一样,但是两者的含义却大不相同:对数组名加1,得到的仅仅是第一个元素地址加上数组元素的类型长度(这里int的长度为4个字节);而对数组名取地址后加1,得到的却是第一个元素的地址加上整个数组的长度(如上例子中,增加了16个字节)。

  1.8.2 指针小结   

  (1)声明指针

    要声明指向特定类型的指针,使用下面的格式:

    typeName * pointerName;

  (2)给指针赋值

    应该将内存地址赋给指针。可以对变量名使用取地址运算符&,来获得被命名的内存地址,new返回的是未被命名的内存地址。

  (3)对指针解除引用

    对指针解除引用意味着获得指针指向的值。对指针应用解除引用或间接值运算符(*)来解除引用。另一种解除引用的方法是使用数组表示法,例如,ptr[0]和*ptr是等价的。

    绝不能对为初始化为适当地址的指针引用解除引用运算符。

  (4)区分指针和指针指向的值

    如果pointer_name是指向type_name类型的指针,则*pointer_name不是指向type_name的指针,而是完全等同于一个type_name类型的变量。pointer_name才是指针。

  (5)数组名

    多数情况下,C++将数组名视为数组中第一个元素的地址。一种情况是例外,将sizeof运算符用于数组名是,得到的是整个数组的长度。

  (6)指针算术

    C++允许指针和整数相加。加1的结果等于原来的地址值加上指向的对象占用的总字节数。还可以将一个指针减去另一个指针,获得两个指针的差。后一种运算将得到一个整数,仅当两个指针指向同一个数组(也可以指向超出结尾的一个位置)时,这种运算才有意义:这将得到两个元素的间隔。

  (7)数组的动态联编和静态联编

    使用数组声明来创建数组时,精彩用静态联编,即数组的长度在编译时设置。

    使用new[]运算符创建数组时,将采用动态联编(动态数组),即将在运行时为数组分配空间,其长度也将在运行时确定。使用完这种数组后,应该使用delete[]运算符释放数组占用的内存。

  (8)数组表示法和指针表示法

    使用方括号数组表示法等同于对指针解除引用。

  1.8.3 指针和字符串
   如果给cout提供一个字符的地址,则它将从该字符开始打印,直到遇到空字符(\0)为止。在C++中,用引号扩起来的字符串像数组名一样,也是第一个元素的地址。这意味着,对于数组中的字符串、用引号括起来的字符串常量及指针所描述的字符串,处理方式都是一样的,都将传递它们的地址。

 1 #include <iostream>
 2 #include <cstring>
 3 using namespace std;
 4 
 5 int main(int argc, const char * argv[]) {
 6     char animal[20] = "bear"; //animal保存 bear
 7     const char * bird = "wren";//bird保存的是一个字符串的地址。"wren"实际表示的是字符串的地址,因此可以赋给char指针变量bird。字符串字面值是常量,所以要使用const关键字。const关键字意味着,可以使用bird来访问,但是不能修改
 8     char *ps;
 9     
10     cout << animal << "" << bird << endl;
11     //cout << ps << endl;可能展示垃圾信息,也可能造成系统崩溃。
12     cout << "请输入一个动物的名字:" << endl;
13     cin >> animal ;//如果输入的字符个数少于20就不会产生问题
14     //cin >> ps;非常糟糕的尝试,因为ps没有指向任何一个内存块
15     ps = animal; //让ps指向一个字符串
16     cout << ps << "\n";//和使用animal一样
17     cout << "使用strcpy()之前:" << endl;
18     cout << animal << "" << (int *)animal << endl;
19     cout << ps << "" << (int *)ps << endl;
20     
21     ps = new char[strlen(animal) + 1];//取得一个新的内存块
22     strcpy(ps, animal);//将animal复制到新的内存块中
23     cout << "使用strcpy()之后:" << endl;
24     cout << animal << "" << (int *)animal << endl;
25     cout << ps << "" << (int *)ps << endl;
26     
27     delete [] ps;
28     
29     return 0;
30 }
31 
32 输出结果:
33 bear和wren
34 请输入一个动物的名字:
35 panda //用户输入
36 panda
37 使用strcpy()之前:
38 panda在0x7fff5fbff7f0
39 panda在0x7fff5fbff7f0
40 使用strcpy()之后:
41 panda在0x7fff5fbff7f0
42 panda在0x100200000

   说明:

  (1)使用bird来进行输入并不合适:

    1)有些编译器将字符串字面值视为只读常量,如果试图修改他们,将导致运行阶段错误。在C++中,字符串字面值都被视为常量,但并不是所有的编译器都对以前的行为作了修改

    2)有些编译器只使用字符串字面值的一个副本表示程序中所有的该字面值。

  (2)C++不能保证字符串字面值被唯一地存储。也就是说,如果再程序中多次使用字符串字面值,则编译器可能存储该字符串字面值的多个副本,也可能只存储一个副本。如果是后一种情况,将一个指针指向字符串字面值,将使它是指向该字符串的唯一一个副本。将值读入一个字符串可能会影响被认为是独立的、位于其他地方的字符串。将指针声明为const,编译器将禁止改变指针指向的位置中的值。

  (3)不能试图将内容读入一个没有初始化的指针中。

  (4)一般来说,如果给cout提供一个指针,他将打印地址。但是,如果指针的类型为char *,则cout将显示指向的字符串。如果要显示的是字符串的地址,则必须将这种指针强制转换成其他类型的指针,比如int*。注意,将数组名或者一个指向某内存块的指针赋值给另一个指针,并不会复制内容,只是复制地址。这样,两个指针指向同一个内存单元或者字符串。

  (5)要获得字符串的副本,首先,要分配内存来存储字符串,这可以通过声明数组或者使用new来完成。后一种方法是的能够根据字符串的长度来指定所需的空间,同时,在指定空间的时候要注意字符串常量是隐藏了其末尾的空字符,同时strlen()函数只会获得字符串中字符的个数,而不会算上空字符。接下来,将字符串复制到新分配的内存中,这里不能直接使用赋值运算符(=),因为这样只能修改指针中存储的地址,从而失去访问新分配内存空间唯一途径。需要使用库函数strcpy()或者strncpy()。

  注意: strcpy()函数的用法:

    (1)函数原型:char *strcpy(char* dest, char* src);

    (2)头文件:C——#include<string.h>,C++——#include<cstring>

    (3)功能:从src地址开始且含有null结束符的字符串复制到以dest地址开始的字符串中,并返回指向dest的指针。如果dest数组本身有数据,会把src里的数据全部复制到dest中,如果dest中有数据小于src地址长度的将会被覆盖,而大于src长度的将保留。

    (4)说明:dest的长度要足够大,不然会产生溢出,即src超出部分会覆盖dest后面正在使用的内存,将会导致一些无法预测的后果。dest的内存长度要大于或等于src的内存长度(要获得内存长度需要使用sizeof()函数,strlen()函数只能获得字符串中字符的个数而不会包含字符串结尾的空字符)。

 

   1.8.4 使用new创建动态结构

   需要在运行是为结构分配所需的空间,可以使用new运算符来完成。通过使用new,可以创建动态结构。由于类和结构非常类似,因此有关结构的技术也适用于类。

  创建结构和访问其成员:

    (1)创建结构:要创建结构,需要同时使用结构类型和new。通用格式为:struct_name *pointer_name = new struct_name;

    (2)访问成员:创建动态结构时,不能将成员运算符句点用语结构名,因为这种结构没有名称,只知道他的地址。而应该采用尖头成员运算符(->),可用于指向结构的指针,用来访问结构的成员。另一种访问结构成员的方法是,如果ps是指向结构的指针,那么*ps就是被指向的值——结构体本身,可以对*ps采用句点成员访问法。

  1、使用new和delete的示例:

 1 #include <iostream>
 2 
 3 using namespace std;
 4 char *getString(void);
 5 
 6 int main(int argc, const char * argv[]) {
 7     char *str ;
 8     
 9     str = getString();
10     cout << str << " at " << (int *)str << endl;
11     delete [] str;
12     cout << endl ;
13     
14     str = getString();
15     cout << str << " at " << (int *)str << endl;
16     delete [] str;
17 
18     return 0;
19 }
20 char *getString(void){
21     char str[80];
22     cout << "请输入一段字符:\n";
23     cin >> str;
24     char *pin = new char [strlen(str) + 1];
25     strcpy(pin, str);
26     return pin;
27 }
28 
29 输出结果:
30 请输入一段字符:
31 WhatIsYourName?//用户输入
32 WhatIsYourName? at 0x100300000
33 
34 请输入一段字符:
35 MyNameIsMuPiaomiao.//用户输入
36 MyNameIsMuPiaomiao. at 0x100205020

  说明:

   (1)getString()函数返回一个指向输入字符串的指针。该函数将输入读入到一个大型的字符数组中,然后使用new []创建一个刚好可以存放该输入字符串的内存块,并返回一个指向该内存块的指针。对于读取大量字符串的程序,该函数可以节省大量的内存空间。

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

   根据用于分配内存的方式,C++有3种管理的数据内存的方式:自动存储、静态存储和动态存储(有时也叫做自由存储空间和堆)。

  (1)自动存储

    在函数内部定义的常规变量使用自动存储空间,被称为自动变量,这意味着他们在所属的函数被调用时自动产生,在该函数结束时消亡。

    自动变量是一个局部变量,其作用域为包含它的代码块。

    自动变量通常存储在栈中。这意味着,执行代码块时,其中的变量将一次加入到栈中,而在离开代码块时,将按相反的顺序释放这些变量,这被称为后进先出(LIFO)。因此,在执行代码的过程中,栈将不断地增大和缩小。

  (2)静态存储

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

  (3)动态存储

    new和delete运算符提供了一种比自动变量和静态变量更灵活的方法。它们管理了一个内存池,这在C++中被称为自由存储空间或堆。该内存池同存储静态变量和自动变量的内存是分开的。动态变量的声明周期不完全受程序或函数的生存时间控制。与使用常规变量相比,使用new和delete让程序员对如何使用内存有了更大的控制权,但是也更加复杂,需要程序员自己跟踪内存地址,并在适当的时候释放内存。

 

1.9 数组的替代品

  1.9.1 模板类vector

  模板类vector类似string类,也是一种动态数组。

  (1)要使用模板类vector,需要包含头文件vector;

  (2)vector包含在命名空间std中;

  (3)模板使用不同的语法来指出它存储的数据类型;

  (4)vector类使用不同的语法来指定元素数。

  #include <vector>

  ...

  using namespace std;

  vector<int>vi;//创建一个包含0个int元素的数组

  int n;

  cout << n;

  vector <double>vd(n);//创建一个包含n个double类型的数组,其中n可以是整型常量,也可以是整型变量。

  一般而言,下面的格式创建一个名为vt的vector对象,它可存储n_elem个类型为typeName的元素:

  vector <typeName> vt (n_elem);

 

  1.9.2 模板类array(C++11)

  (1)array位于命名空间std中;

  (2)与数组一样,array对象的长度也是固定的,也使用栈(静态内存分配),而不是自由存储区,因此效率和数组一样,但是更安全,更方便;

  (3)要创建array对象,需要包含array头文件。

  一般而言,下面的声明创建一个名为arr的array对象,它包含n_elem个类型为typeName的元素:

  array<typeName,n_elem> arr;

  与创建vector对象不同的是,n_elem不能是变量。

  

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

  

 1 #include <iostream>
 2 #include <array>
 3 #include <vector>
 4 
 5 using namespace std;
 6 
 7 int main(int argc, const char * argv[]) {
 8     int arr1[4] = {12,22,35,45};
 9     vector<double>arr2(4);//创建一个包含4个double的vector对象
10     arr2[0] = 1.0/3.0;
11     arr2[1] = 1.0/5.0;
12     arr2[2] = 1.0/7.0;
13     arr2[3] = 1.0/9.0;
14     array<float, 4>arr3 = {10.5,20.5,30.5,40.5};//创建一个包含4个float的array对象
15     array<float, 4>arr4;
16     arr4 = arr3;
17     cout << "arr1[2]:" << arr1[2] << " at " << &arr1[2] << endl;
18     cout << "arr2[2]:" << arr2[2] << " at " << &arr2[2] << endl;
19     cout << "arr3[2]:" << arr3[2] << " at " << &arr3[2] << endl;
20     cout << "arr4[2]:" << arr4[2] << " at " << &arr4[2] << endl << endl;
21     
22     arr1[-2] = 20.2;
23     cout << "arr1[-2]:" << arr1[-2] << " at " << &arr1[-2] << endl;
24     cout << "arr3[2]:" << arr3[2] << " at " << &arr3[2] << endl;
25     cout << "arr4[2]:" << arr4[2] << " at " << &arr4[2] << endl;
26 
27     return 0;
28 }
29 
30 输出结果:
31 arr1[2]:35 at 0x7fff5fbff7f8
32 arr2[2]:0.142857 at 0x1001054a0
33 arr3[2]:30.5 at 0x7fff5fbff650
34 arr4[2]:30.5 at 0x7fff5fbff640
35 
36 arr1[-2]:20 at 0x7fff5fbff7e8
37 arr3[2]:30.5 at 0x7fff5fbff650
38 arr4[2]:30.5 at 0x7fff5fbff640

 

  说明:

  (1)无论是数组,array对象还是vector对象,都可以使用标准数组访问发来访问各个元素;

  (2) 从地址可知,array对象和数组存储在相同的内存区域(即栈)中,而vector对象存储在另一个区域(自由存储区或堆)中;

  (3)可以将一个array对象赋给另一个对象;而对于数组,必须逐元素复制对象;

  (4)arr1[-2] = 20.2;这条语句说明数组是不安全的。vector对象和array对象也可以像前面这样操作;但是,它们有一个成员函数at();中括号表示发和成员函数表示法at()a的区别在于:使用at()成员函数是,将在运行期间捕获非法索引,即不会出现arr2.at(-2) = 20.2;这种越界错误。

  

 

  

  

 

转载于:https://www.cnblogs.com/mupiaomiao/p/4599622.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一组新的多维数组模板类 by chen3feng(RoachCock@smth) email: chen3feng@163.com, chen3fengx@163.com, chen3fengx@hotmail.com [引言] 在C/C++开发中,多维数组是一个让很多人感到棘手的问题.原因是C/C++中,多维数组被看 作是数组的数组. 在向函数传递参数的时候,多维数组经常让人感到是麻烦的制造者,通常都是传递首地址 和每一维的大小: void foo(int *,int ,int);; int a[10][10];; foo(&a[0][0],10,10);; //... 十分的麻烦,在函数中访问时也得自己用乘法来计算元素的位置,更是十分麻烦. C99标准推出了可变大小的多维数组,当然,实现该标准的编译器还不多,但是也从一个方 面说明了变量大小的多维数组是很有用的. C++标准直到现在还不支持,明年(2003年)的新标准也不知道会不会加进去.但是C++程序 员自己有办法,利用C++模板,运算符重载等技巧,完全可以构建出方便实用的多维数组类 我抢在明年之前做出这组模板类,也希望即使新标准增加了变量大小的多维数组,我的工 作也仍有一些意义, :) 另外,多维数组的空间是连续的,这跟用vector of vector实现的不一样,可以用迭代器 从头到脚挨个摸一遍. boost库也提供了多维数组类,当然还有别的几个数组类.我感觉boost多维数组类的缺点 就是只支持动态数组,对静态和动态数组没有一个统一的非GP的接口,因此我着重于这方 面的改进, [简介] 该组类有以下几个类模板组成 1. template <;typename T, size_t DimNum>; class array_base;; 该类是其他几个数组类的基类 // 由于编译器对C++标准实现参差不齐的原因,该类实际 上不是根类,不过应用中不需要知道这一点. 提供了基本的功能,比如[]运算符,迭代器的类型声明,迭代器的获取,value_type等的定 义等 等 2. template <;typename T, size_t d1, size_t d2 = -1, size_t d3 = -1>; class static_array;; 静态的数组类,从array_base派生而来,因此除了兼容也是由array_base派生出来的其他 类外,还有自己的特点,就是提供了一个elements的public成员,直接暴露给用户,访问 速度可以很快. 3. template<;typename T,size_t DimNum, typename A=std::allocator<;T>; >; class dynamic_array;; //:public array_base<;T, DimNum>; 看得出也是从array_base派生的,另外,他是可以resize的.还支持reserve等STL容器的操 作. 4. template <;typename T, size_t DimNum, typename A=std::allocator<;T>; >; class shared_array;; //: public array_base<;T, DimNum>; 就是支持引用计数的动态数组啦.不过刚写了个外皮,内容还没开工,因为我最近要回家. sorry! [用法] 先要包含各自的头文件: #include ";static_array.hpp"; #include ";dynamic_array.hpp"; #include ";shared_array.hpp"; 1.然后就可以定义对象 cfc::static_array<;int,10>; sa1;; cfc::static_array<;int,10, 10>; sa2;; cfc::static_array<;int,10, 10, 10>; sa3;; cfc::dynamic_array<;int, 1>; da1(cfc::extents[10],10);; cfc::dynamic_array<;int, 2>; da2(cfc::extents[10][10], 10);; cfc::dynamic_array<;int, 3>; da3(cfc::extents[10][10][10], 10);; cfc::shared_array<;int,1>; sha1(cfc::extents[10]]);; cfc::shared_array<;int,2>; sha2(cfc::extents[10][10]);; cfc::shared_array<;int,3>; sha3(cfc::extents[10][10][10]);; extents是一个数组的维度生成器,用起来的很方便,跟boost学的,不过没仔细看它的实现 ,我觉得我的也不错,哈哈 2.访问元素: sa1[0] = 0;; da1[0] = 0;; sa2[0][0] = 0;; da2[0][0] = 0;; sa3[0][0][0] = 0;; da3[0][0][0] = 0;; 3.比较相等与否: bool f;; f = sa1==sb1;; f = da1==da1;; f = sa1==da1;; // 说明:只提供了==和!=,别的没提供,我觉得别的意义大 4.交换: cfc::swap(da1,db1);; cfc::swap(sa1,sb1);; cfc::swap(sa1,db1);; //说明:动态数组的交换很高效,换个指针而已, :) 5.resize: da3.resize(cfc::extents[10][100][1]);; da3.resize(cfc::extents[10][50][1]);; da3.resize(cfc::extents[10][10][20]);; da3.resize(cfc::extents[10][10][10]);; //说明:只有动态数组才能resize, 还有将来的shared_array, zz 6.赋值: da3 = db3;; sa1 = sb1;; da1 = db1;; 静态数组维度不一样不能赋值,否则会引起编译错误 动态数组和丢失了静态大小成为了array_base的数组维度不一样时,赋值引发 std::length_error异常,可以捕捉到, 比较也是这样 7.作为函数的参数 还举开头的那个例子 void foo(array_base<;int,2>; &a) { a[0][0]=10;; } 8.重要概念 <;子数组>; 高维数组的子类型,也就是低一维的数组. 子数组的类型为array_base,支持array_base的所有操作,但是不再支持原来数组的特定 操作子数组由[]运算符得到, sa3[0] da3[0] //类型均为array_base<;int,2>; 子数组还可以在取子数组 da3[0][1];;//类型为array_base<;int,1>; [性能] 三维大小均为100的静态,动态,原生数组以及boost::multi_array.以三重循环每次隔一个 填充, 我测试的结果,速度大概是原生数组的60%,boost数组的速度是原生数组的1/5,因此速度 大概是boost的3倍. 如果用迭代器顺序访问的话,跟原生数组相比就区别不大了.但是代码要好写一点,而且直 接支持STL算法. [实现与移植] 由于要兼顾各种编译器,而且是在VC6上做的,因此像模板偏特化等特性都不能用,需要变 通,因此相当繁琐,由此可见一个好的编译器多么重要啊. 不过话说回来,这样的代码移植性才好呢.想想连VC6都能编译的代码,移植性应该不错, :) [后记] 这是对以前的那个多维数组类的扩充与改进,增加了不少功能,去掉了不少限制, 现在静态数组的最大维数做到了3,动态数组的维数不限//你需要多高维数的?维数越高越 慢, :) 由于时间不多,精力和水平有限,其中的缺点和错误欢迎指正,也十分欢迎哪位能帮我进一 步提高访问速度. 谢谢! 附带测试程序,其中包括与boost::multi_array<;>;的速度比较代码. //the end. ^=^

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值