您的位置:首页 > 房产 > 家装 > 免费模型网站_wordpress批量建站_建站公司排名_培训机构管理系统哪个好

免费模型网站_wordpress批量建站_建站公司排名_培训机构管理系统哪个好

2024/12/28 13:24:01 来源:https://blog.csdn.net/qq_45310624/article/details/143866118  浏览:    关键词:免费模型网站_wordpress批量建站_建站公司排名_培训机构管理系统哪个好
免费模型网站_wordpress批量建站_建站公司排名_培训机构管理系统哪个好

这里的笔记区别于精简基础,会记录较多C++的细节

文章目录

  • 前言
  • 一、数组
    • 1.1 数组的创建声明
        • 数组声明,数组元素赋值以及初始化数组
    • 1.2 数组的初始化规则
  • 二、字符串
    • 2.1 字符串介绍
    • 2.2 在数组中使用字符串
        • 字符串使用
    • 2.3 字符串输入
        • 字符串陷阱
    • 2.4 每次读取一行字符串输入
        • 利用面向行的输入:getline()解决陷阱
        • 面向行的输入:get()解决陷阱
    • 2.5 混合输入字符串和数字
        • 混合字符串
  • 三、String类简介
    • 3.1 string类介绍
        • string类
    • 3.2 C++字符串初始化
    • 3.3 赋值、拼接和附加
        • string类赋值与拼接
    • 3.4 string类的其他操作
        • string扩展知识
    • 3.5 string类I/O
        • string类I/O的使用方法
  • 四、结构简介
    • 4.1 结构介绍
    • 4.2 在程序中使用结构
        • 结构
    • 4.3 C++11 结构初始化
    • 4.4 结构可以将string类作为成员吗
    • 4.5 其他结构属性
        • 其他结构属性
    • 4.6 结构数组
        • 结构数组
  • 五、指针和自由存储空间
    • 5.1 指针和地址
        • 指针和自由存储空间
    • 5.2 声明和初始化指针
    • 5.3 使用new来分配内存,使用delete释放内存
        • 使用new来分配内存
    • 5.4 使用new来创建动态数组
        • 动态数组创建
  • 六、指针、数组和指针算术
    • 6.1 它们之间的关联
        • 指针、数组和指针算术
    • 6.2 指针小结
    • 6.3 指针和字符串
        • 指针和字符串
    • 6.4 使用new创建动态结构
        • 使用new创建动态结构
    • 6.5 使用new和delete来输入字符串
        • 利用string节省内存
    • 6.6 自动存储、静态存储和动态存储
  • 总结


前言

这一章主要研究复合类型,复合类型顾名思义就是在基于基本整型和浮点类型创建的。影响最为深远的复合类型是类,它是学习OOP的堡垒。然而这一章我们说的是更为普通的复合类型,比如数组,指针,new和delete,string类等等。


一、数组

1.1 数组的创建声明

数组是一种数据格式,能够存储多个同类型的值。例如,数组可以存储60个int类型的值(这些值表示游戏5年来的销售量),12个short值(这些值表示每个月的天数)或365个float值(这些值指出一年中每天在食物方面的开销)。每个值都存储在一个独立的数组元素中,计算机在内存中依次存储数组的各个元素。

要创建数组,可使用声明语句。数组声明应指出以下三点:

  • 存储在每个元素中的值的类型;
  • 数组名;
  • 数组中的元素数

其次,C++数组从0开始编号。C++使用带索引的方括号表示法来指定数组元素。
在这里插入图片描述
以下程序就来演示如何声明数组、给数组元素赋值以及初始化数组。

数组声明,数组元素赋值以及初始化数组
#include <iostream>
int main() {using namespace std;int yams[3];//定义完再每个元素进行赋值yams[0] = 7;yams[1] = 8;yams[2] = 6;int yamcosts[3] = { 20,30,5 };//定义的时候顺便赋值,建议写这种.也叫成员初始化列表cout << "Total yams = ";cout << yams[0] + yams[1] + yams[2] << endl;cout << "The package with " << yams[1] << " yams costs ";cout << yamcosts[1] << " cents per yam. \n";int total = yams[0] * yamcosts[0] + yams[1] * yamcosts[1];//7*20+8*30 140+240=380total = total + yams[2] * yamcosts[2];//380+6*5=410cout << "The total yam expense is " << total << " cents.\n";cout << "\nSize of yams array = " << sizeof yams;//数组大小是12,因为int占4个字节,数组yams包含了三个元素,就是3*4=12个字节cout << "\nSize of one element = " << sizeof yams[0];return 0;
}

1.2 数组的初始化规则

C++有几条关于初始化数组的规则,它们限制了初始化的时刻,决定了数组的元素数目与初始化器中值的数目不同时将发生的情况。我们来看看这些规则。
只有在定义数组时才能使用初始化,此后就不能使用了,也不能将一个数组赋给另一个数组;

int cards[4] = {3, 6, 8, 10}; //okay
int hand[4]; //okay
hand[4] = {5, 6, 7, 9}; //not allowed
hand = cards; //not allowed

然而,可以使用下标分别给数组中的元素赋值。
初始化数组时,提供的值可以少于数组的元素数目。

float hotelTips[5] = {5.0, 2.5};

如果只对数组的一部分进行初始化,则编译器会把其他元素设置为0。因此,将数组中所有的元素都初始化为0非常简单——只要显式地将第一个元素初始化为0,然后让编译器将其他元素都初始化为0即可:

long totals[500] = {0};

如果初始化数组时方括号内([ ])为空,C++编译器将计算元素个数。例如:

short things[] = {1, 5, 3, 8};

二、字符串

2.1 字符串介绍

字符串是存储在内存的连续字节中的一系列字符。
C++处理字符串的方式有两种。第一种来自C语言,常被称为C风格字符串。这一章首先介绍它,然后介绍另一种基于string类库的方法。
存储在连续字节中的一系列字符意味着可以将字符串存储在char数组中,其中每个字符都位于自己的数组元素中。
C风格字符串具有一种特殊的性质:以空字符结尾,空字符被写作\0,其ASCII码为0,用来标记字符串的结尾。
例如下面两个声明:

char dog[8] = { 'b', 'e', 'a', 'u', 'x', ' ', 'I', 'I'}; //not a string
char cat[8] = { 'f', 'a', 't', 'e', 's', 's', 'a', '\0'}; //a string

这两个都是char数组,但只有第二个数组是字符串。空字符对于C风格字符串来说至关重要。
在以上示例中,将数组初始化为字符串的工作看着很无聊——使用了大量的单引号,且必须加上空字符。但其实有一种更好的方法——只需使用一个用引号括起的字符串即可,这种字符串被称为字符串常量或字符串字面值,如下所示:

char bird[11] = "Mr.Cheeps"; //the \0 is understood
char fish[] = "Bubbles"; //let the compiler count

用双引号括起的字符串隐式地包括结尾的空字符,因此不用显式地包括它。方便了不少
在这里插入图片描述
需要注意的是,字符串常量(使用双引号)不能与字符常量(使用单引号)互换。字符常量(如’S’)是字符串编码的简写表示。在ASCII系统上,'S’只是83的另一种写法,因此,下面的语句将83赋给shirt_size:

char shirt_size = 'S';

==但"S"不是字符常量,它表示的是两个字符(字符S和\0)组成的字符串。更糟糕的是,"S"实际上表示的字符串所在的内存地址。==因此下面的语句试图将一个内存地址赋值给shirt_size:

char shirt_size = "S";

由于地址在C++中是一种独立的类型,因此C++编译器不允许这种不合理的做法。

2.2 在数组中使用字符串

要将字符串存储到数组中,最常用的方法有两种——将数组初始化为字符串常量、将键盘或文件输入读入到数组中。以下程序演示了这两种方法:

字符串使用
#include <iostream>
#include <cstring>
int main() {using namespace std;const int Size = 15;//给数组设置一个符号常量char name1[Size];char name2[Size] = "C++owboy";cout << "Howdy!I'm " << name2;cout << " what's your name?\n";cin >> name1;cout << "well, " << name1 << ",your name has ";cout << strlen(name1) << " letters and is stroed\n";cout << "in an array of " << sizeof(name1) << " bytes.\n";cout << "your initial is " << name1[0] << ".\n";name2[3] = '\0';cout << "Here are the first 3 charactes of my name:";cout << name2 << endl;return 0;
}

下面是程序运行的情况:
在这里插入图片描述
程序说明:
从以上程序中,首先sizeof运算符指出整个数组的长度:15字节,但strlen()函数返回的是存储在数组中的字符串的长度,而不是数组本身的长度。另外,strlen()只计算可见的字符,而不把空字符计算在内。因此,对于Basicman,返回的值为8,而不是9。如果cosmic是字符串,则要存储该字符串,数组的长度不能短于strlen(cosmic)+1。
由于name1和name2是数组,所以可以使用索引来访问数组中各个字符。例如,该程序使用name[0]找到该数组第一个字符。另外,该程序将name2[3]设置为空字符。这使得字符串在第3个字符后即结束,虽然数组中还有其他的字符。
在这里插入图片描述
该程序使用符号常量来指定数组的长度。

2.3 字符串输入

其实2.2中程序有一个缺陷,这种缺陷通过精心选择输入被掩盖掉了。以下代码将揭开它的陷阱,且揭示字符串输入的方式:

字符串陷阱
#include <iostream>
int main() {using namespace std;const int Arsize = 20;char name[Arsize];char dessert[Arsize];cout << "Enter your name:\n";cin >> name;//cin是以空白字符作为结束标志cout << "Enter your favorite dessert:\n";//cout是以/0作为结束标志cin >> dessert;//所以上面如果输入了带空格和回车的名字后,这里就会直接跳过cout << "I have some dessert " << dessert;cout << " for you, " << name << ".\n";return 0;
}

该程序的意图很简单:读取来自键盘的用户名和用户喜欢的甜点,然后显示这些信息。但是实验发现,我们甚至还没输入甜点程序就把它显示出来并立即显示到最后一行。
cin是如何确定已完成字符串输入的呢?由于不能通过键盘输入空字符,因此cin需要用别的方法来确定字符串的结尾位置。cin使用空白(空格、制表符、换行符)来确定字符串的结束位置,这意味着cin在获取字符串数组输入时只读取一个单词。读取该单词后,cin将该字符串放到数组中,并自动在结尾添加空字符。
下一节我们将优化这个情况

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

在上一节的代码中不难发现,每次只读取一个单词通常不是最好的选择。比如程序要求用户输入城市名,用户输入New York。你希望程序能读取并存储完整的城市名而不是New或Sao。要将整条短语而不是一个单词作为字符串输入,需要采用另一种字符串读取方法。==具体的说,需要采用面向行而不是面向单词的方法。==在istream中的类(如cin)提供了一些面向行的类成员函数:getline()和get()。这两个函数都读取一行输入,直到到达换行符。
getline()将丢弃换行符,而get()将换行符保留在输入序列中。下面我们详细介绍它们,首先介绍getline()。
1、面向行的输入:getline()
getline()函数读取整行,它使用通过回车输入的换行符来确定输入结尾。要调用这种方法,可以使用cin.getline()。该函数有两个参数,第一个参数用来存储输入行的数组的名称,第二个参数是要读取的字符数。如果这个参数为20,则函数最多读取19个字符,余下的空间用于存储自动在结尾处添加的空字符。getline()成员函数在读取指定数目的字符或遇到换行符时停止读取。
以下是代码示例:

利用面向行的输入:getline()解决陷阱
#include <iostream>int main() {using namespace std;const int Arsize = 20;char name[Arsize];char dessert[Arsize];cout << "Enter your name:\n";cin.getline(name, Arsize);//get line会在遇到回车换行符时丢弃掉回车换行符,并把回车换行符换成\0放到数组里面cout << "Enter your favorite dessert:\n";//cout是以/0作为结束标志cin.getline(dessert, Arsize);//如果输入的字符超过19个(算上\0一共20个)的话也会跳过这个输入cout << "I have some dessert " << dessert;cout << " for you, " << name << ".\n";return 0;
}

2、面向行的输入:get()
我们来试试另一种解决方法。istream类有另一个名为get()的成员函数,该函数有几种变体。其中一种变体的工作方式与getline()类似,它们接受的参数相同,解释参数的方式也相同,且都读到行尾。但get并不再读取并丢弃换行符,而是将其留在输入队列中。假设我们连续两次调用get():

cin.get(name, ArSize);
cin.get(dessert, ArSize);

由于第一次调用后,换行符将留在输入队列中,因此第二次调用时看到的第一个字符便是换行符。因此get()认为已到达行尾,而没有发现任何可读取内容。如果不借助任何帮助,get()将无法跨过该换行符。
幸运的是,get()有另一种变体。使用不带任何参数的cin.get()调用可读取下一个字符(即使是换行符),因此可以使用它来处理换行符,为读取下一行输入做好准备。也就是说,可以采用下面的调用序列:

cin.get(name, ArSize);
cin.get();
cin.get(dessert, ArSize);

以下代码就使用了这种方式进行优化:

面向行的输入:get()解决陷阱
#include <iostream>int main() {using namespace std;const int Arsize = 20;char name[Arsize];char dessert[Arsize];cout << "Enter your name:\n";cin.getline(name, Arsize).getline(dessert, Arsize);//getline也可以连写,但是连写最多只能连一个//cin.get(name, Arsize);//最多允许19个元素+一个\0,但是get并不会丢弃回车换行符,所以回车换行符会被留在缓存区里面//cin.get();//这代表会读取缓存区里面剩下的内容,捕获并把回车换行符给消耗掉。//cout << "Enter your favorite dessert:\n";//cout是以/0作为结束标志//cin.get(dessert, Arsize).get();//get()也可以在cin后面连续编写cout << "I have some dessert " << dessert;cout << " for you, " << name << ".\n";return 0;
}

2.5 混合输入字符串和数字

混合输入数字和面向行的字符串会导致问题。请看以下示例:

混合字符串
#include <iostream>
int main() {using namespace std;cout << "What year was your house bulit?\n";int year;char ch;//cin >> year;//这里会留下回车换行符,所以需要使用一个cin.get()消耗掉回车换行符//cin.get();//(cin >> year).get();//也可以使用这种写法来消耗掉回车换行符,这是第二种写法(cin >> year).get(ch);//这是第三种写法,这里只是了解一下有这种写法,现在还不需要知道cout << "What is its street address?\n";char address[80];cin.getline(address, 80);cout << "Year bulit: " << year << endl;cout << "Address: " << address << endl;cout << "Done!\n";return 0;
}

如果在一开始没有做出修改的情况下,用户根本没有输入地址的机会。问题在于,当cin读取年份后,将回车符留给了下面的cin.getline(),当cin.getline()看到回车换行符后,将认为这是一个空行,并将一个空字符串赋给address数组。解决方法就是利用cin.get()获取空字符把回车换行符解决掉,但也并不只有这一种方法,另外两种方法我也写在了以上代码中了。

三、String类简介

3.1 string类介绍

要使用string类,必须在程序中包含头文件string。string类位于名称空间std中,因此你必须提供一条using编译指令,或者使用std::string来使用它。
以下代码说明了string对象与字符数组之间的一些相同点和不同点:

string类
#include <iostream>
#include <string>
int main() {using namespace std;char charr1[20];char charr2[20] = "jaguar";std::string str1;//定位一下,std命名空间里面的string类。引用了就不需要std::string str2 = "panther";cout << "Enter another kond of feline: ";cin >> charr1;cout << "Enter another kond 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对象的方式与使用字符数组相同。

  • 可以使用C风格字符串来初始化string对象。
  • 可以使用cin来将键盘输入存储到string对象。
  • 可以使用cout来显示string对象。
  • 可以使用数组表示法来访问存储在string对象中的字符。

3.2 C++字符串初始化

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

char first_date[] = {"Le Chapon Dodu"};
char second_date[] = {"the Elegant Plate"};
string third_date = {"The Bread Bowl"};
string fourth_date {"Hank's Fine Eats"};

3.3 赋值、拼接和附加

使用string类时,某些操作比使用数组时更简单。例如,不能将一个数组赋给另一个数组,但可以将一个string对象赋给另一个string对象:

char charr1[20];
char charr2[20] = "jaguar";
string str1; //创建一个空的string类对象str1
string str2 = "panther";
charr1 = charr2; //无效,无数组分配
str1 = str2; //有效

string类简化了字符串合并操作。可以使用运算符+将两个string对象合并起来,还可以使用运算符+=将字符串附加到string对象的末尾。继续前面的代码,您可以这样做:

string str3;
str3 = str1 + str2;
str1 += str2;

以下代码演示了这些用法:

string类赋值与拼接
#include <iostream>
#include <string>
int main() {using namespace std;string s1 = "penguin";string s2, s3;cout << "you can assign one string object to another: s2 = s1\n";s2 = s1;cout << "s1: " << s1 << ",s2: " << s2 << endl;cout << "you can assign a c-Style string to a string object.\n";cout << "s2 = \"buzzard\"\n";s2 = "buzzard";cout << "s2: " << s2 << endl;cout << "you can concatenate strings:s3 = s1 + s2\n";s3 = s1 + s2;cout << "s3: " << s3 << endl;cout << "you can append strings.\n";s1 += s2;//s1 = s1 + s2;cout << "s1 += s2 yields s1 = " << s1 << endl;s2 += " for a day";//s2 = s2 + " for a day";cout << "s2 += \" for a day \" yeilds s2 = " << s2 << endl;return 0;
}

转义序列\"表示双引号,而不是字符串结尾。

3.4 string类的其他操作

在头文件cstring中有这么两个函数:strcpy()和strcat(),可以使用函数strcpy()将字符串复制到字符数组中,使用函数strcat()将字符串附加到字符数组末尾:

string扩展知识
#include <iostream>
#include <string>
#include <cstring>
#pragma warning(disable:4996)//关闭警告提醒
int main() {using namespace std;char charr1[20];char charr2[20] = "jaguar";string str1;string str2 = "panther";str1 = str2;strcpy(charr1, charr2);//把charr2的东西拷贝到charr1里面//cout << str1 << " " << charr1 << endl;str1 += " paste";strcat(charr1, " juice");//cout << str1 << " " << charr1 << endl;//strcat(charr1, charr2);//cout << charr1 << endl;int len1 = str1.size();//想要计算字符串里有多少个元素,可以使用size方法。计算这个str1字符串里面有多少个字符int len2 = strlen(charr1);//计算我们C风格字符串里面有效字符的长度//上面两种方法都是计算字符长度,但其实是不一样的。// size()方法只能通过其所属类的对象进行调用。strlen()只是一个常规函数,它接受了C风格字符串作为参数,并返回该字符串包含的字符数。cout << "The string " << str1 << " contains " << len1 << " characters.\n";cout << "The string " << charr2 << " contains " << len2 << " characters.\n";cout << sizeof(charr1) << endl;//这个是数组创建的元素总数return 0;
}

3.5 string类I/O

我们可以使用cin和运算符>>来将输入存储到string对象中,使用cout和运算符<<来显示string对象,其句法与处理C风格字符串相同。但每次读取一行而不是一个单词时,使用的句法不同。以下代码将演示这一点:

string类I/O的使用方法
#include <iostream>
#include <string>
#include <cstring>
int main() {using namespace std;char charr[20];//char数组,只有带\0的才是字符串string str;cout << "Length of string in charr before input: " << strlen(charr) << endl;//strlen要计算的话得碰到\0为止,所以会一直找,找到啥时候谁也不知道。cout << "Length of string in charr before input: " << str.size() << endl;//这里输出为0,是因为未被初始化的string类默认为0cout << "Enter a line of text:\n";cin.getline(charr, 20);//用cin这个对象去调用这个getline成员函数cout << "You entered: " << charr << endl;cout << "Enter a line of text:\n";getline(cin, str);//这就是一个普通的函数,可以避免越界的情况出现cout << "You entered: " << str << endl;cout << "Length of string in charr before input: " << strlen(charr) << endl;cout << "Length of string in charr before input: " << str.size() << endl;return 0;
}

在用户输入之前,该程序指出数组charr中的字符串长度为27,这比该数组的长度要大。这里有两个点要说明。首先,未初始化的数组内容是未定义的;其次,函数strlen()从数组的第一个元素开始计算字节数,直到遇到空字符

四、结构简介

4.1 结构介绍

我们知道,数组可以存储多个元素,但所有元素的类型必须相同。也就是说一个数组可以存储20个int。另一个数组可以存储10个float,但同一个数组不能在一些元素中存储int,在另一些元素中存储float。
因此这时我们就需要使用到结构。结构是一种比数组更灵活的数据格式,因为同一个结构可以存储多种类型的数据。结构也是C++ OOP堡垒(类)的基石。学习有关结构的知识将使我们离C++的核心OOP更近。
结构是用户定义的类型,而结构声明定义了这种类型的数据属性。定义了类型后,便可以创建这种类型的变量。因此创建结构包括两步走。首先,定义结构描述——它描述并标记了能够存储在结构中的各种数据类型。然后按描述创建结构变量(结构数据对象)
例如,假设Bloataire公司要创建一种类型来描述其生产线上充气产品的成员。具体的说,这种类型应存储产品名称、容量(单位为立方英尺)和售价。下面的结构描述能够满足这些要求:

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

关键字struct表明,这些代码定义的是一个结构的布局标识符inflatable是这种数据格式的名称,因此这个新类型的名称为inflatable。这样,便可以像创建char或int类型的变量那样创建inflatable类型的变量了。
在这里插入图片描述
定义结构后,便可以创建这种类型的变量了:

inflatable hat;
inflatable mainframe;

4.2 在程序中使用结构

介绍完结构的主要特征后,下面在一个使用结构的程序中使用这些概念。

结构
#include <iostream>
struct inflatable//放在main函数外面是外部声明,在这里我们把这个结构体放在外面或者里面没有任何区别//放在外面其他所有的函数都能使用,放在main函数里面就只能main函数能使用。也就是全局声明和局部声明的区别
{char name[20];float volume;double price;
};
int main() {using namespace std;inflatable guest ={"Glorious Gloria",1.88,29.99};inflatable pal ={"Audacious Arthur",3.12,32.99};//如果大括号内没有写任何东西,就会默认设置为0,还有不允许缩窄转换cout << "Expand your guest list with " << guest.name;cout << " and " << pal.name << "!\n";cout << "you can have both for $";cout << guest.price + pal.price << "!\n";return 0;
}

结构声明的位置很重要。对于上述代码而言,有两种选择。可以将声明放在main()函数中,紧跟在开始括号的后面。另一种选择是将声明放到main()的前面,这里采用的便是这种方式,位于函数外面的声明被称为外部声明。对于这个程序而言,两者并没有什么实际差别。但是对于那些包含两个或更多函数的程序来说,差别很大。外部声明可以被其后面的任何函数使用,而内部声明只能被该声明所属的函数使用。通常应使用外部声明,这样所有函数都可以使用这种类型的结构
在这里插入图片描述
变量也可以在函数内部和外部定义,外部变量由所有的函数共享(这将在第九章做详细说明),C++不提倡使用外部变量,但提倡使用外部声明。另外,在外部声明符号常量通常更合理。
接下来,我们看看初始化方式:

inflatable guest = 
{"Glorious Gloria",1.88,29.99
};

和数组一样,使用由逗号分隔值列表,并将这些值用花括号括起来。该程序中,每个值占一行,但也可以将它们全部放在同一行中。只是应用逗号将它们分开:

inflatable duck = {"Daphne",0.12,9.98};

可以将结构的每个成员都初始化为适当类型的数据。

4.3 C++11 结构初始化

与数组一样,C++11也支持将列表初始化用于结构,且等号(=)是可有可无的:

inflatable duck {"Daphne",0.12,9.98};

其次,如果大括号内未包含任何东西,各个成员都将被设置成零。例如,下面的声明:

inflatable mayor {};

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

可以将成员name指定为string对象而不是字符数组吗?即可以像下面这样声明结构吗?

struct inflatable
{std::string name;float volume;double price;
};

答案是可以的,只要你使用的编译器支持对以string对象作为成员的结构进行初始化。

4.5 其他结构属性

C++使用户定义的类型与内置类型尽可能相似。例如,可以将结构作为参数传递给函数,也可以让函数返回一个结构。另外,还可以用赋值运算符(=)将结构赋给另一个同类型的结构,这样结构中每个成员都将被设置为另一个结构中相应成员的值,即使成员是数组。这种赋值也被叫做成员赋值。下面是代码演示:

其他结构属性
#include <iostream>
struct inflatable//放在main函数外面是外部声明,在这里我们把这个结构体放在外面或者里面没有任何区别//放在外面其他所有的函数都能使用,放在main函数里面就只能main函数能使用。也就是全局声明和局部声明的区别
{char name[20];float volume;double price;
};
int main() {using namespace std;inflatable guest ={"sunflowers",0.20,12.49};cout << "guest: " << guest.name << "for $";cout << guest.price << endl;inflatable choice;choice = guest;//结构赋值或成员赋值cout << "choice: " << choice.name << "for $";cout << choice.price << endl;//这里打印一样的东西,证明了成员赋值return 0;
}

4.6 结构数组

假如一个结构内要包含一个数组。也可以创建元素为结构的数组,方法和创建基本类型数组完全相同。假如我们要创建一个包含了100个inflatable结构的数组,可以这样做:

inflatable gifts[100]; //创建一个inflatable结构的数组,包含100个inflatable元素

要初始化结构数组,可以结合使用初始化数组的规则和初始化结构的规则。这里有点绕,我们直接看代码就好:

inflatable guests[2] = 
{{"Baba", 0.5, 21.99},{"God", 2000, 565.99}
};

也可以按自己喜欢的方式格式化它们。例如,两个初始化都放在同一行,而每个结构成员初始化各占一行。
以下程序是一个使用结构数组的简短示例。

结构数组
#include <iostream>
struct inflatable//放在main函数外面是外部声明,在这里我们把这个结构体放在外面或者里面没有任何区别//放在外面其他所有的函数都能使用,放在main函数里面就只能main函数能使用。也就是全局声明和局部声明的区别
{char name[20];float volume;double price;
};
int main() {using namespace std;inflatable guest[2] ={{"Bambi",0.5,21.99},//guest[0]{"Godzilia",2000,565.99}//guest[1]};cout << guest[0].name << " " << guest[1].name << endl;cout << guest[0].volume + guest[1].volume;return 0;
}

由于guests是一个inflatable数组,因此guests[0]的类型为inflatable,可以使用它和句点运算符来访问相应inflatable结构的成员。

五、指针和自由存储空间

5.1 指针和地址

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

  • 信息存储在何处
  • 存储的值为多少
  • 存储的信息是什么类型

我们使用过一种策略来达到上述目的:定义一个简单变量。声明语句指出了值的类型和符号名,还让程序为值分配内存,并在内部跟踪该内存单元。
接下来我们看看另一种策略,它在开发C++类时非常重要。这种策略以指针为基础,指针是一个变量,其存储的值是值的地址,而不是值的本身。 在讨论指针之前我们先看看如何找到常规变量的地址。只需对变量应用地址运算符(&),就可以获得它的位置;例如,如果home是一个变量,则&home是它的地址。
以下程序演示了这个运算符的用法。

指针和自由存储空间
#include <iostream>
int main() {using namespace std;int updates = 6;cout << "updates values: " << updates << endl;cout << "updates address: " << &updates << endl;int* p_updates;//定义了一个指针,存储的是一个地址p_updates = &updates;//把一个地址赋值给指针,创建一个指针就把一个明确的地址空间赋值给它,这样是最好的cout << "p_updates values: " << *p_updates << endl;//解引用cout << "p_updates address: " << p_updates << endl;*p_updates = *p_updates + 1;cout << *p_updates << " " << p_updates << endl;cout << updates << endl;return 0;
}

从上述代码我们可知,其实int变量updates和指针变量p_updates只不过是同一枚硬币的两面。变量updates表示值,并使用&运算符来获取地址;而变量p_updates表示地址,并使用*运算符来获得值。

5.2 声明和初始化指针

声明指针的时候,计算机需要跟踪指针指向的值的类型。例如char的地址与double的地址看上去没什么差别,但实际上两者使用的字节数是不同的,它们存储值时使用的内部格式也不同。
例如以下示例:

int * p_updates;

可以说,p_updates是指针(地址),而*p_updates是int,而不是指针。
这强调的是:int*是一种类型——指向int的指针。
注意:在C++中,int*是一种复合类型,是指向int的指针
以下程序演示了如何将一个指针初始化为地址:

#include <iostream>
int main(){using namespace std;int higgens = 5;int * pt = &higgens;cout << "Value of higgens = " << higgens << "; Address of higgens = " << &higgens << endl;cout << "Value of *pt = " << *pt<< "; Value of pt = " << pt << endl;return 0;
}

5.3 使用new来分配内存,使用delete释放内存

在C++中,有一个new运算符,可以在运行阶段为一个int值分配未命名的内存,并使用指针来访问这个值。我们只需要告诉new,需要为哪种数据类型分配内存;new将找到一个长度正确的内存块,并返回该内存块的地址。程序员的责任是将该地址赋给一个指针。下面是一个这样的示例:

int * pn = new int;

new int告诉程序,需要适合存储int的内存。new运算符将根据类型来确定需要多少字节的内存。
然后,它找到这样的内存并返回其地址。接下来将地址赋给pn,pn是被声明为指向int的指针。现在pn是地址,而*pn是存储在那里的值。
当然,new运算符也可以为一个数据对象(可以是结构,也可以是基本类型)获得并指定分配内存的通用格式如下:

typeName * pointer_name = new typeName;

我们需要在两个地方去指定数据类型:用来指定需要什么样的内存和用来声明合适的指针。
以下代码将演示如何将new用于两种不同的类型:

使用new来分配内存
#include<iostream>
int main() {using namespace std;int nights = 1001;cout << "night value = ";cout << nights << ": location " << &nights << endl;int* pt = new int;*pt = 1001;cout << "int ";cout << *pt << ": location " << pt << endl;double* pd = new double;*pd = 10000001.0;cout << "double ";cout << *pd << ": location " << pd << endl;cout << "size of pt = " << sizeof pt << endl;//64位系统这里显示的是8,32位显示的是4.因为64/8=8,32/8=4。//还有一个就是编译器里面设置为x64为64位,x86为32位。cout << "size of *pt = " << sizeof *pt << endl;//指向的是int类型,4个字节cout << "size of pd = " << sizeof (pd) << endl;cout << "size of *pd = " << sizeof (*pd) << endl;//指向的是double类型,8个字节delete pt, pd;//new完一定要delete,这两个是搭配使用的,不然会因为程序不断寻找更多内存而终止,发生内存泄漏。return 0;
}

对于指针,需要指出的一点是,new分配的内存块通常与常规变量声明分配的内存块不同。常规变量的值都存储在被称为栈的内存区域中,而new从被称为堆或自由存储区的内存区域分配内存。
所以这时候计算机可能会由于没有足够的内存而无法满足new的请求。在这种情况下new会引起异常。
而对于这种情况,我们就需要用到delete运算符。delete的作用是使得在new使用完内存后,能够将其归还给内存池,这是最有效使用内存的关键。使用delete时,后面要加上指向内存块的指针(最初用new分配的内存块)

int * ps = new int;
...
delete ps;

这将释放ps指向的内存,但不会删除指针ps本身。例如,可以将ps重新指向一个新分配的内存块。所以new和delete一定要配对使用,否则将容易发生内存泄漏的情况。也就是说,被分配的内存再也无法使用了。

5.4 使用new来创建动态数组

如果通过声明来创建数组,则在程序被编译时将为它分配内存空间。不管程序最终是否使用数组,数组都在那里,它占用了内存。在编译时给数组分配内存被称为静态联编,意味着数组是在编译时加入到程序中的。但使用new时,如果在运行阶段需要数组,则创建它;如果不需要,则不创建。还可以在程序运行时选择数组的长度。这被称为动态联编,意味着数组是在程序运行时创建的
下面来看一下关于动态数组的两个基本问题:如何使用C++的new运算符创建数组以及如何使用指针访问数组元素。
1、使用new创建动态数组
在C++中,创建动态数组很容易,只要将数组的元素类型和数目告诉new即可。必须在类型名后面加上方括号,其中包含元素数。例如:

int * psome = new int [10];

new运算符返回第一个元素的地址。在这个例子中,该地址被赋值给指针psome。
当使用完new分配的内存块时,应使用delete释放它们。然而对于使用new创建的数组,应使用另一种格式的delete来释放:

delete [] psome;

方括号告诉程序,应释放整个数组,而不仅仅是指针指向的元素。请注意delete和指针之间的方括号。如果使用new时,不带方括号则使用delete时也不需要带方括号。
总之使用new和delete时,应遵循以下规则:

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

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

type_name * pointer_name = new type_name [num_elements];

这里指针指向的是整个数组的首元素地址。

2、使用动态数组
创建动态数组后,如何使用它呢?首先,我们考虑到创建一个指针时new了一个数组后,这个指针指向的是我们使用new创建的数组的第一个元素。则我们其实只需要把指针当作数组名来使用就可以了。
接下来我们直接看下面的代码演示可能会更清晰明了:

动态数组创建
#include<iostream>
int main() {using namespace std;double* p3 = new double[3];p3[0] = 0.2;p3[1] = 0.5;p3[2] = 0.8;cout << "p3 = " << *p3 << endl;cout << "p3[0] = " << p3[0] << endl;cout << "p3[1] = " << p3[1] << endl;p3 = p3 + 1;//改变了指针指向的位置了cout << "p3[0] = " << p3[0] << endl;//此时因为p3+1的原因,p3[1]变成了p3[0],所以这里变成了0.5cout << "p3[1] = " << p3[1] << endl;p3 = p3 - 1;//返回到最初的位置delete[] p3;//给delete[]提供正确的地址。return 0;
}

上述代码中的其中一个代码行中指出了数组名和指针之间的根本差别:

p3 = p3 + 1;

由于我们没有初始化数组,所以没法修改数组名的值。但指针是变量,我们可以通过指针来修改它的值。但请注意将p3+1的效果是数组下标+1的意思。还记得我们之前说过的吗,指针名其实就是地址。而这个指针指向的类型是double类型。那么地址+1就是在这个double类型下+1,即指向了下一个值。
但要记住的是,我们在delete的时候必须把地址归位到原来new的地址上去,即p3-1。此时我们再使用delete[ ]提供的就是正确的地址。

六、指针、数组和指针算术

6.1 它们之间的关联

指针和数组基本等价的原因就在于指针算术和C++内部处理数组的方式。首先,我们来看看算术。将整数变量+1后其值将+1。但将指针变量+1后,增加的量等于它指向的类型的字节数。且引申出了另一个十分重要的点:C++将数组名解释为地址。我们来看以下代码示例:

指针、数组和指针算术
#include<iostream>
int main() {using namespace std;double wages[3] = { 1000.0,2000.0,3000.0 };short stack[3] = { 3,2,1 };double* pw = wages;//数组名为数组第一个元素的地址cout << "pw = " << pw << ", *pw = " << *pw << endl;pw = pw + 1;cout << "pw = " << pw << ", *pw = " << *pw << endl;short* ps = &stack[0];cout << "ps = " << ps << ", *ps = " << *ps << endl;ps = ps + 1;cout << "ps = " << ps << ", *ps = " << *ps << endl;cout << "stack[0] = " << stack[0] << " stack[1] = " << stack[1] << endl;cout << "stack = " << stack << "  " << &stack[0] << endl;cout << "*(stack + 1) = " << *(stack + 1) << endl;cout << sizeof(wages) << endl;//放在sizeof的时候不会把数组名当作地址cout << sizeof(pw) << endl;cout << "stack = " << stack << "  " << &stack[0] << endl;//这里验证了数组名就是数组第一个元素的地址,代表指向了首部元素地址。cout << "&stack = " << &stack << endl;//这里输出的地址和上面的语句地址是一样的,但是并不和上面语句的意思等价。这里指向的是全部元素的地址//&stack+1是整体加数组长度的字节return 0;
}

在多数情况下,C++将数组名解释为数组第一个元素的地址。因此,下面的语句将pw声明为指向double类型的指针,然后将它初始化为wages——wages数组中第一个元素的地址:

double * pw = wages;

和所有数组一样,wages也存在下面的等式:

wages = &wages[0]; //

当然也有两个例外,第一种情况:对数组应用sizeof运算符时得到的是数组的长度,而对指针应用sizeof的时候得到的是指针的长度,即使指针指向的是一个数组。在这种情况下,C++不会将数组名解释为地址。第二种情况则是对数组取地址时,数组名也不会被解释为地址;数组名被解释为其第一个元素的地址,而对数组名应用地址运算符的时候,得到的则是整个数组的地址。
如下代码所示:

short tell[10];
cout << tell << endl;
cout << &tell << endl;

从数字上说,这两个地址相同;但从概念上来说,&tell[0](即tell)是一个2字节内存块的地址,而&tell是一个20字节内存块的地址。因此表达式tell+1将地址值加2,而表达式&tell+1是将地址加20。换句话说tell是一个short指针(short*),而&tell是一个这样的指针,即指向包含10个元素的short数组(short(*)[10])。
综上可知,我们能够声明和初始化一个这样的指针:

short (*pas)[10] = &tell;

如果省略括号,优先级规则会使得pas和[10]结合,导致pas是一个short指针数组,它包含10个元素,因此括号是必不可少的。由于pas被设置为&tell,因此*pas和tell等价,所以(*pas)[0]为tell数组的第一个元素。所以pas变量的类型为short (*)[10]。

6.2 指针小结

上面介绍了大量的指针知识。下面对指针和数组做一些总结:
1、声明指针
要声明指向特定类型的指针,请使用下面的格式:

typeName * pointerName;

下面是示例:

double * pn;
char * pm;

其中,pn和pm都是指针,而double和char是指向double的指针和指向char的指针。
2、给指针赋值
应将内存地址赋给指针。可以对变量名应用&运算符,来获得被命名的内存的地址。new运算符返回未命名的内存的地址。
下面是一些示例:

double* pn;
double* pa;
char * pc;
double bubble = 3.2;
pn = &bubble;
pc = new char;
pa = new double[30];

3、对指针解引用
对指针解引用意味着获得指针指向的值。对指针应用解引用或间接值运算符(*)来解除引用。因此,如果像上面的例子那样,pn是指向dubble的指针,则*pn是指向的值,即3.2。
下面是一些示例:

cout << *pn;
*pc = 'S';

4、区分指针和指针所指向的值
如果pt是指向int的指针,则*pt不是指向int的指针,而是完全等于一个指向int的类型的变量。pt才是指针。下面是一些示例:

int * pt = new int;
*pt = 5;

5、数组名
在多数情况下,C++将数组名视为数组的第一个元素的地址
下面是是一个示例:

int tacos[10];

一种例外情况是,将sizeof运算符用于数组名用时,此时将返回整个数组的长度(单位为字节)。
6、指针算术
C++允许将指针和整数相加。加1的结果等于原来的地址值加上指向的对象占用的总字节数。还可以将一个指针减去另一个指针,获得两个指针的差。后一种运算将得到一个整数,仅当两个指针指向同一个数组(也可以指向超出结尾的一个位置)时,这种运算才有意义;这将得到两个元素的间隔。
下面是一些示例:

int tacos[10] = {5,2,8,4,1,2,2,4,6,8};
int * pt = tacos;
pt = pt + 1;
int * pe = &tacos[9];
pe = pe - 1; //减1后等价为tacos[8]
int diff = pe - pt; //得出是7

7、数组的动态联编和静态联编
使用数组声明来创建数组时,将采用静态联编,即数组的长度在编译时设置:
使用new[ ]运算符创建数组时,将采用动态联编(动态数组),即将在运行时为数组分配空间,其长度也将在运行时设置。使用完这种数组后,应使用delete[ ]释放其占用的内存:

int size;
cin >> size;
int * pz = new int [size];
...
delete [] pz;

8、数组表示法和指针表示法
使用方括号数组表示法等同于对指针解引用:

tacos[0] means *tacos means the value at address tacos
tacos[3] means *{tacos + 3) means the value at address tacos + 3

数组名和指针变量都是如此,因此对于指针和数组名,既可以使用指针表示法,也可以使用数组表示法。下面是一些示例:

int* pt = new int[10]; //创建一个长度为10的未命名数组,并赋值给指针pt,类型为int
*pt = 5; //把首元素地址设置成5
pt[0] = 6; //重新把首元素地址设置成6
pt[9] = 44; //把第十个元素,即下标为9的元素设置成44
int coats[10]; //创建一个int类型数组coats,长度为10
*(coats + 4) = 12; //数组名为首元素地址,指针coats+4即下标为4的第三个元素赋值为12

6.3 指针和字符串

数组和指针的特殊关系可以扩展到C风格字符串。
注意:在cout和多数C++表达式中,char数组名、char指针以及用括号括起的字符串常量都被解释为字符串第一个字符的地址。
以下代码将演示如何使用不同形式的字符串。

指针和字符串
#include<iostream>
#include<cstring>
#pragma warning(disable:4996)//防止报错
int main() {using namespace std;char animal[20] = "bear";const char* bird = "wren";//* bird不可改变,地址可变,但地址里面的值不可变char* ps;cout << animal << " " << bird << endl;cout << "enter a kind of animal: ";cin >> animal;cout << animal << " at " << (int*)animal <<endl;//强制类型转换ps = animal;//这里只是赋值地址给ps而已cout << ps << " at " << (int*)ps << endl;//cin >> ps;//这样是不允许的,因为ps未进行初始化。ps = new char[strlen(animal) + 1];//开辟了一个新的内存空间,+1是需要补上一个\0strcpy(ps, animal);cout << ps << " at " << (int*)ps << endl;delete[] ps;//new了带[]的,就要delete带[]return 0;
}

6.4 使用new创建动态结构

我们之前说,在运行时创建数组优于在编译时创建数组,对于结构也是如此。需要在程序运行时为结构分配所需的空间,这也可以使用new运算符来完成。通过使用new,可以创建动态结构。
将new用于结构由两步组成:创建结构和访问其成员。要创建结构,需要同时使用结构类型和new。例如,要创建一个未命名的infatable类型,并将其赋值给一个指针,可以这样做:

infatable * ps = new infatable; 

这足以存储infatable结构的一块可用内存地址赋给ps。这种句法和C++内置类型完全相同。
比较麻烦的是访问成员这一步。创建动态结构时,不能将成员运算符句点用于结构名,因为这种结构没有名称,只是知道它的地址。C++专门未这种情况提供了一个运算符(->)。可用于指向结构体的指针,就像点运算符可用于结构名一样
在这里插入图片描述

如果结构标识符是结构名,则使用句点运算符;如果标识符是指向结构的指针,则使用箭头运算符。
以下代码将演示两种访问结构成员的指针表示法:

使用new创建动态结构
#include<iostream>
struct inflatable {char name[20];float volume;double price;
};
int main() {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;//值cout << "Enter price: $";cin >> ps->price;cout << "Name " << (*ps).name << endl;cout << "Volume: " << ps->volume << endl;cout << "price: " << ps->price << endl;delete ps;return 0;
}

6.5 使用new和delete来输入字符串

我们直接看代码:

利用string节省内存
#include <iostream>
#include <cstring>
#pragma warning (disable:4996)
using namespace std;//全局使用std
char* getname(void);
int main() {char* name;name = getname();cout << name << " at " << (int*)name << "\n";delete[] name;name = getname();cout << name << " at " << (int*)name << "\n";delete[] name;return 0;
}char* getname() {char temp[80];cout << "Enter last name: ";cin >> temp;char* pn= new char[strlen(temp) + 1];//pn所指向的内存空间还在,因为这是我们程序员主动去new的strcpy(pn, temp);return pn;
}

在上述代码中,我们利用了getname()来分配内存,而main()释放内存

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

1、自动存储
在函数内部定义的常规变量使用自动存储空间,被称为自动变量,这意味着它们在被调用时自动产生,在该函数结束时消亡。
自动变量一般存在栈中。其中的变量将依次加入到栈中,而在离开代码块时又会按照相反的顺序释放这些变量。这被称为先进后出。
2、静态存储
静态存储是整个程序执行期间都存在的存储方式。使变量成为静态的方式有两种:一种是在函数外面定义它;另一种是在声明变量时使用关键字static。
3、动态存储
new和delete运算符提供了一种比自动变量和静态变量更灵活的方法。它们管理一个内存池,这在C++中被称为自由存储空间或堆。该内存池同用于静态变量和自动变量的内存是分开的。使用new和delete让程序员对程序如何使用内存有更大的控制权。但new和delete的互相影响可能导致占用的自由存储区不连续,这使得跟踪新分配内存的位置更困难。


总结

1、数组、结构和指针是C++的3种复合类型。数组可以在一个数据对象中存储多个同类型的值。通过使用索引或下标,可以访问数组中各个元素。
2、结构可以将多个不同类型的值存储在同一个数据对象里面,可以使用成员关系运算符(.)来访问其中的成员。
3、指针是被设计用来存储地址的变量。我们说,指针指向它存储的地址。指针声明指出了指针指向的对象的类型。对指针应用解除引用运算符,将得到指针指向的位置中的值。
4、new和delete运算符允许显式控制何时给数据对象分配内存,何时将内存归还给内存池。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com