提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
例如:第一章 Python 机器学习入门之pandas的使用
第四章 数组
4.1 数组
4.2 字符串
4.3 string简介
4.4 结构简介
4.5 共用体
4.6 枚举
4.7 指针和自由存储空间
4.8 指针、数组和指针算术
4.9 类型组合
4.10 数组的替代品
提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档
4.1 数组
提示:这里可以添加本文要记录的大概内容:
要创建数组,可使用声明语句。数组声明应指出以下三点
- 存储在每个元素中的值的类型
- 数组名
- 数组中的元素数
例如,创建一个名为months的数组,该数组有12个元素,每个元素都可以存储一个short类型的值
short months[12]
声明数组的通用格式
typeName arryName[arrySize]
4.1.1 实例
include <iostream>
using namespace std;
int main{
// 数组初始化定义的方式1
int yams[3];
yams[0] = 7;
yams[1] = 8;
yams[2] = 6;
// 数组初始化定义的方式2
int yamscosts[3] = {20, 30, 5};
cout << "total yams = ";
cout << yams[0] + yams[1] + yams[2] << endl;
cout << "The pacckage with" << yams[1] << "yams cost";
cout << yamscosts[1] << "cents per yam.\n"
int total = yams[0] * yamscost[0] + yams[1] + yamscosts[1]
total = total + yams2] * yamscosts[2];
cout << "The toatal yam expense is" << total << "cent.\n";
// 计算数组所占的内存大小
cout << "\nSize of yams arry = " << sizeoff yams;
cout << "Size of one element = " << sizeof yams[0];
// or cout << "Size of one element = " << sizeof yams / sizeof (short)
return 0;
}
4.1.2 数组的初始化规则
只有在定义数组时才能进行初始化,,此后就不能再使用了也不能将一个数组赋给另一个数组。
int cards[4] = {3, 4, 5, 6}; // 正确
int hand [4]; // 正确
hand[4] = {3, 4, 5, 6} ; // 错误
hand = cards; // 错误
初始化数组时,提供的值可以少于数组的元素数目,其他 元素编译器会将其置为0。
float hoteTips[5] = {1, 3};
如果初始化数组时,方括号([ ])内为空,c++编译器将自动计算元素个数。但是,不推荐这样使,因为自己可能再数组中遗漏一个值
short thins[] = {11, 23, 24};
4.1.3 C++ 11中数组的初始化方法
- 初始化数组时,可以省略等号
double earning[3] {1.2e3, 1.3e4, 1.5e7};
- 可以不在{ }中包含任何元素,这将把所有元素均设置为0
unsigned int counts[10] = {}; // 数组内所有元素均为0
- 列表初始化时禁止缩窄,即取值类型大的不可以缩小为小的。
// int类型占4个字节,取值范围在-2,147,483,648~2,147,483,647之间。
// long类型占8个字节,取值范围在9,223,372,036,854,775,808~9,223,372,036,854,775,807之间。
long pilfs[] = {23, 34, 3.5}; // double > long, not allowed
// char类型的取值范围 -128~127
char silfs[4] = {'f', 'g', 'h', 1122011 }; // not allowed because 1122011超出了char类型的取值范围
4.2 字符串
字符串是存储再内存中的连续字节中的一系列字符,可以将其存储在char数组中。
c-风格字符串具有一种重要的特性:以空字符结尾,空字符可以被写作“\0”,其ASCII为0,用来标记字符串的结尾。如下列两个声明:
char dog[4] = {'b', 'a', 'c', 'd'}; // not a string
char cat[4] = {'b', 'a', 'c', '\0'}; // is a string
为避免大量使用单引号,有一种更好的将字符串数组初始化为字符串的方法,即使用双引号将字符串括起来,这种字符串被称为字符串常量或字符串字面值。如下所示:
char bird[11] = "Mr. Cheeps";
char fish[] = "Bubbles"; //" [] " 编译器会自动计算数组元素个数
注意:在确定存储字符串所需的最短数组时,别忘了将结尾的空字符计算在内,即字符串元素为9,则需定义数组元素大小为10.
此外,字符串常量(使用双引号)不能与字符常量(使用单引号)进行互换,字符串常量(如 ’ S ’ )是字符串编码的简写表示。在ASCII系统上, ’ S '是83的另一种写法。
4.2.1 拼接字符串常量
有时候,字符串很长,无法放到一行。c++允许拼接字符串字面值,即将两个用引号括起来的字符串合并为1个。事实上,任何两个由空白(空格、制表符或换行符)分割的字符串常量都将拼接成一个,谢列语句都是等效的。
cout << " I'd give my right arm to be " " a great violinist. \n ";
cout << " I'd give my right arm to be a great violinist. \n ";
cout << " I'd give my right ar"
"m to be a violinist. \n";
4.2.2 在数组中使用字符串
将字符串存储到数组中,最常用的方法有两种:
- 将数组初始化为字符串常量
- 将键盘或文件输入读入到数组中
如下程序:
#include <iostream>
#include <cstring> // 使用strlen()函数需要引用的头文件
using namespce std;
int main()
{
const int Size = 15;
char name1[Size]; // 不直接初始化(空数组),通过键盘输入初始化字符串常量
char name2[Size] = " C+owboy"; // 直接使用双引号括起来,初始化为字符串常量
cout << " Howdy, I'm " << name2;
cout << " What is your name? \n;
cin >> name1;
cout << " well, << name1 << "your name has ";
cout << strlen(name1) << " letters and stored\n"; // 使用strlen()函数计算数组中字符串的长度
cout << in a array of << sizeof(name1) << " bytes\n"; // sizeof()计算整个数组的长度
cout << "Your initial is: " << name1[0] << ".\n";
return 0;
}
上面的程序中strlen()只计算可见的字符串的长度,不把空字符计算在内,即对于Basing,返回值是6,而不是7。
4.2.3 字符串的输入
以下程序中体现了一个缺陷,揭示了字符串输入的技巧。
#include <iostream>
using namespace std;
int main()
{
const int ArSize = 20;
char name[ArSize];
char dessert[ArSize];
cout << "Enter your name:\n";
cin >> name;
cout << "Enter your favourite dessert:\n";
cin >> dessert;
cout << "I have some delicious" << dessert;
cout << " for you, " << name << ".\n";
return 0;
}
程序输出
Enter your name:
Alistair Dreeb
Enter your favourite dessert:
I have some delicious Dreeb for you, Alistair
- 可以发现,程序没有对输入甜点提示,就把它显示出来,然后立即显示最后一行。这是由于cin使用空白(空格、制表符或换行符)来确定字符的结束位置,所以cin在获取字符数组的输入时只能读取一个单词。读取该单词后,cin将字符串放到数组中,并将它放到数组中,并自动在结尾添加空字符。
- 因此,这个例子实际结果是,将Alistair 作为第一个字符串,将其放到name数组中。这把Dreeb 留在输入队列中。当cin在输入队列搜索用户喜欢的甜点时,他发现了Dreeb ,因此读取Dreeb 将它放到dessert数组中。
4.2.4 每次读取一行字符串输入
- 面向行的输入:getline()
getline()函数读取整行,它通过使用回车键的换行符来确认输入结尾,但不保存换行符(即将换行符丢弃)。可通过调用cin.getline()来使用这种方法。该函数有两个输入参数:数组名成,读取的字符数(如果参数为20,则最多只能读取19个字符,因为每个字符串包含一个空字符),
cin.getline(name, 20)
- 面向行的输入:get()
get()与getline()类似,接收的参数也相同,区别在于get()不在读取并丢弃换行符,而将其留在输入队列中
cin.get(name, ArSize);
cin.get(dessert, ArSize);
假设两次调用get()将出现问题。因为第二次调用get()时看到的第一个便是换行符,因此get()认为已经到达结尾,可通过再次调用不带任何参数的get()来解决此类问题:
cin.get(name, ArSize);
cin.get();
//第二种使用拼接的方式
//cin.get(name, ArSize).get();
cin.get(dessert, ArSize);
- 空行和其他问题
当getline()或get()读取空行时,最初做法是:下一条输入语句将在前一条getline()或get()结束的位置开始读取,当前做法是:当get() 【不是getline()】读取空行时将设置失效位,接下来的输入将会被阻断。
此外,如果输入的字符数比指定的多,getline()和get()会将剩余的字符留在输入队列,但是getline()还会设置失效位。
4.2.5 混合输入字符串和数字
当混合输入字符串和数字时,将会导致一些问题。通过下列程序进行说明:
#include <iostream>
using namespace std;
int main()
{
cout << "What years was your house built?\n";
int year;
cin >> year;
cout << "What is its street address?\n";
char address[80];
cin.get line(address, 80);
cout << "Year built: " << year << endl;
cout << "address is: " << adress << endl
return 0;
}
程序运行结果
What years was your house built?
1966
What is its street address?
Year built: 1966
address is:
上述程序的问题在于,用户根本没有输入地址的机会,问题在于,当cin读取年份时,将回车键生成的换行符留在了输入队列,后面cin.get line看到换行符后,认为是一个空行,并将一个空字符串赋值给address数组。
解决办法是在读取地址之前先读取并丢弃换行符:
// 方法1
cin >> year;
cin.get() // or cin.get(ch) ,需提前声明数组ch[]
// 方法2
(cin >> year).get() // or (cin >> year).get(ch)
4.3 string 类简介
使用string类,必须在程序中包含头儿年间string(#include )。但是由于string类位于std空间,也可以std::string来引用它。
string对象和字符数组之间的主要区别是,可以将string对象声明为简单变量,而不是数组。类程序设计能够自动处理string的大小。例如,str1声明创建一个长度为0的string对象,但程序将输入读取到str1中时,将自动调整str1的长度。
string str1;
string str2 = "pandas";
4.3.1 赋值、拼接、附加
使用string类时,某些操作比数组更简单。如,不能将一个数组赋给另一个数组,而一个string对象可以赋给另一个string对象。
char char1[20];
char char2[20] = "jajure";
string str1;
string str2 = "panther";
char1 = char2; //错误
str1 = str2; //正确
string类可以使用运算符 “+” 将两个对象合并起来,还可以使用 “+=” 将字符串附加到string对象的末尾。
string str3;
str3 = str1 + str2;
str1 += str2;
4.3.2 string类的其他操作
在没有新增string类之前,对于字符串的赋值操作,程序员使用C语言库中的函数来进行这项工作。头文件cstring提供了这些函数。例如使用函数strcpy()将字符串赋值到字符串数组中,使用函数strcat()将字符串附加到字符串数组的末尾。
strcpy(char1, char2) // coppy char2 to char1
strcat(char1, char2) // append contents of char2 to char1
另外,使用字符数组时,存在目标数组过小,无法存储指定信息的危险,如下程序
char site[10] = "house";
strcpy(site, " of pancakes"); // 会报内存问题
其中函数strcpy()尝试将12个字符( of pancakes)全部复制到数组site中,将覆盖相邻的内存,导致程序终止。而string类具有自动调节大小的功能,可以避免此类问题。
下面是确定字符串数组中的两种方法:
int len1 = str.size(); // 利用string类中的size()方法获取字符串数
int len2 = strlen(char1); // 利用strlen()方法获取字符串数
4.3.3 string类 I/O
使用下面一段程序进行说明每次读取一行而不是一个单词时,使用的句法不同:
#include <iostream>
#include <string>
#include <cstring> // C-style
using namespace std;
int main()
{
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;
cout << "Enter a line of text:\n";
// 这是将一行输入读取到数组中的代码,函数getline()时istream类的一个类方法,第一个参数表示
// 数组名,第二个参数表述数组长度,使用getline()可以避免超越数组边界
cin.getline(charr, 20);
cout << "Enter a line of text:\n";
// 这是将一行输入读取到数组中的代码。没有使用句点表示法没说明getline()不是类方法,他将cin
// 作为输入参数,指出去哪里去查找输入,因为string对象可以根据字符串长度自动调整大小,故没有
// 指定长度大小。
getline(cin, str);
return 0;
}
4.4 结构简介
结构是用户定义的类型,而结构声明定义了这种类型的数据属性。定义类型后,便开始创建类型的变量。因此创建结构包含两步:
- 定义结构描述:标记了能够存储在结构中的各种数据类型。
- 创建结构变量(结构数据对象)
如下例代码:
// 定义机构声明
struct inflatable{
char name[20];
float volume;
double price;
}
//创建结构对象
//c++中允许省略关键字struct
inflatable hat;
inflatable mainframe;
// c语言中不可省略关键字struct
//struct inflatable hat;
//struct inflatable mainframe;
由于hat的类型为inflatable,因此可以使用成员运算符(.)来访问各个成员,如访问结构中的price成员,可以使用hat.price
4.4.1 在程序中使用结构
下面一段程序展示了如何定义结构体并使用以及初始化结构体的方式:
#include <iostream>
using namespace std;
struct inflatable{
char name[20];
float volume;
double price;
}
int main(){
inflatable guest =
{
"ykw", // name
1.88, // volume
29.99 // price
}
return 0;
}
注意,结构声明的位置很重要。放在main()外和mian()函数内,外部声明可以被其他的任何函数使用,而内部声明只能被该声明所属的函数使用。对于结构变量也可以在函数内部和外部进行定义,不过一般不提倡使用外部声明。
注意初始化时,和数组类似,内部成员使用逗号进行隔开,当然也可以放在同一行中,在上述程序中每个元素单独放了一行,更加清晰明了。
同时,结构可以将string类作为成员,如:
#include <iostream>
using namespace std;
struct inflatable{
std::string name;
float volume;
double price;
}
4.4.2 其他结构属性
可以将结构作为参数传递给函数,也可以让函数返回一个结构。另外,还可以使用赋值运算符(=)将结构赋给另一个同类型的结构,这样结构中的每个成员都将被设置为另一个结构中相应的成员的值,即使成员是数组也会如此。这种赋值操作被称为成员赋值。
#include <iostream>
using namespace std;
struct inflatable{
char name[20];
float volume;
double price;
}
int main(){
inflatable guest =
{
"ykw", // name
1.88, // volume
29.99 // price
}
inflatable choice = guest;
return 0;
}
上面代码中结构数据对象choice里面的元素和值与guest是相同的。
此外,可以将定义结构体和创建结构变量的工作同时进行,只需要将变量名放在结构体结束括号的后面即可,如下展示:
struct inflatable{
"ykw", // name
1.88, // volume
29.99 // price
} guest, choice;
基于上述操作也可以在创建结构变量的时候同时对结构变量进行初始化,不过将定义结构和变量声明分开,更清晰明了哦:
struct inflatable{
"ykw", // name
1.88, // volume
29.99 // price
} guest = {
"perk"
1.99,
3.99
};
上面的实例我们在定义结构体时,同时声明了结构体的名称。此处我们要说这个名称也是可以省略的,不过此时结构变量position访问它的成员可以使用(position.x),但是由于这种类型没有名称,无法后续继续创建这种类型的变量,所以还是使用原来的创建结构体的方式要更舒适哦:
struct {
int x,
int y
}position;
4.4.3 结构数组
基于上面小姐创建的inflatable结构体,它包含一个数组(name)。可以创建元素为结构的数组,方法与创建基本类型数组的方式相同。例如,创建一个包含100个inflatable结构的数组,可以这样做:
// gifts是一个inflatable数组,其中每个元素(gifts[0],gifts[1]...)都是inflatable对象,可以与组成运算符一起使用访问对象成员
inflatable gifts[100];
cout << gifts[1].name;
加入要初始化结构数组,可以基于初始化普通数组的方式,如
inflatable gifts[2] = {
{"ykw", 1.88, 29.99},
{"zcc", 3.44, 7.00}
};
4.5 共用体
共用体(union)是一种数据格式,它与结构体类似,但是区别是共用体只能同时存储其中的一种数据类型,而结构体能够同时存储int、long、和double。也就是说共用体可以存储int、long、和double类型的数据,且开辟内存时以最大的数据类型开辟内存空间,然后使用的时候只能同时使用一种数据类型。
union one4all{
int int_val;
long long_val;
double double_val
};
根据上述说明,所以共用体的one4all变量可以来存储int、long、和double,条件是不同的时间进行。成员名称标识了变量的容量,由于共用体一次只能存储一个值,因此它必须要有足够的空间来存储最大的值,所以共用体的长度为其最大成员的长度。
one4all pail;
pail.int_val = 14;
pail.double_val =1.38; // 此时共用体存储的是double类型,而原来的int类型的值此时已经丢失
共用体的用途之一是:当数据使用两种及以上类型时(但不会同时使用),可以节省空间。
匿名共用体,同匿名结构体类似,可以没有名称,其成员将成为位于相同地址处的变量。
4.6 枚举
c++的enum工具提供了另一种创建符号常量的方式(枚举),这种方式可以替代const。枚举允许定义新类型,但必须严格按照限制执行。其使用与结构类似,如:
enum spectrum {red, orange, yellow, green, blue, violet, indigo, ultraviolet};
这条语句中完成两项工作:
- 让spectrum 成为新类型的名称;spectrum 被称为枚举,就像struct变量被称为结构一样。
- 将red, yellow, green等作为符号常量,他们的值对应整数值0~7。这些常量叫做枚举量。
默认情况下,将整数赋值给枚举量,第一个枚举量的枚举值是0,依次加1类推。
可以使用枚举名来声明这种类型的变量,接上面面的枚举语句:
spectrum band;
在不进行强制转换时,只能将定义枚举时的枚举量赋值给这种枚举的变量。
band =blue;
// 使用强制转换
band = spectrum (3); // 括号中的值有范围限制,后面进行说明
注意枚举量是整型,可以被提升为int型,但是int型不能自动转为枚举型所以有:
int color = blue; // 正确的,因为blue在枚举中默认值为4
band = 3; // 错误 ,3不是枚举型
color = 3+red; //正确的,color是int型
band = color; // 错误,因为color是int型
4.6.1 设置枚举的值
可以使用赋值运算符显示的设计枚举量的值,注意指定的值必须为整数。如:
enum bits {one = 1, two = 2, four = 4, eight = 8};
4.6.2 枚举值的范围
通过以下代码实例说明枚举值的取值范围如何确定;
enum bits {one = 1, two = 2, four = 4, eight = 8};
bits myflag;
// 下面的代码合法
myflag = bits(6); //因为6在枚举定义的取值范围
以这个枚举定义为例,取值范围的确定方式如下:
- 找出上限。需要知道枚举量的最大值,找到大于这个值的最小的2次幂,将它减去1。如上例,枚举量最大值为8,则为23 。大于这个值的最小的2次幂则为2^4 ,所以上限为15。
- 找出下限。需要知道枚举量的最小值,如果最小值不小于0,则下限为0,否则,采用与寻找最大值的方式进行寻找,不过要加上负号。