第 4 章复合类型
影响最为深远的复合类型是类, 它是将学习的 OOP 的堡垒。
C + + 还支持几种更普通的复合类型, 它们都来自 C 语言。 例如,
- 数组可以存储多个同类型的值。
- 一种特殊的数组可以存储字符串( 一系列字符)。
- 结构可以存储多个不同类型的值。
- 而指针则是一种将数据所处位置告诉计算机的变量。
本章将介绍所有这些复合类型( 类除外), 还将介绍 new 和 delete 及如何使用它们来管理数据。 另外, 还将简要地介绍 string 类, 它提供了另一种处理字符串的途径。
数组
数组( array) 是一种数据格式, 能够存储多个同类型的值。
事实上, 可以将数组中的每个元素看作是一个简单变量。
数组之所以被称为复合类型, 是因为它是使用其他类型来创建的( C 语言使用术语“ 派生类型”, 但由于 C + + 对类关系使用术语“ 派生”, 所以它必须创建一个新术语)。
有效下标值的重要性: 编译器不会检查使用的下标是否有效。 例如, 如果将一个值赋给不存在的元素 months[ 101], 编译器并不会指出错误。 但是程序运行后, 这种赋值可能引发问题, 它可能破坏数据或代码, 也可能导致程序异常终止。 所以必须确保程序只使用有效的下标值。
根据C语言的经验, 数组名实际是指向数组第一个元素的指针.C++的数组和C语言的数组类似.
如果只对数组的一部分进行初始化, 则编译器将把其他元素设置为 0。 因此, 将数组中所有的元素都初始化为 0 非常简单— 只要显式地将第一个元素初始化为 0, 然后让编译器将其他元素都初始化为 0
#include <iostream>
using namespace std;
void print_array(int a[],int a_size);
int main() {
int i[] = { 1,2,3,4 };
int j[4];
// 初始化数组时, 可省略等号( = )
int k[4] { 1,2 };
// 可不在大括号内包含任何东西, 这将把所有元素都设置为零
int m[5]{};
int temp;
print_array(i,sizeof(i)/sizeof(int)); // 1 2 3 4
print_array(j, sizeof(j) / sizeof(int));; // -858993460 -858993460 -858993460 -858993460
print_array(k, sizeof(k) / sizeof(int)); // 1 2 0 0
print_array(m, sizeof(m) / sizeof(int)); // 0 0 0 0 0
return 0;
}
void print_array(int a[],int a_size) {
//打印整数数组
int temp;
for (temp = 0; temp < a_size; temp++) cout << a[temp] << ' ';
cout << endl;
}
C + + 标准模板库( STL) 提供了一种数组替代品— 模板类 vector, 而 C + + 11 新增了模板类 array。 这些替代品比内置复合类型数组更复杂、 更灵活, 本章将简要地讨论它们, 而第 16 章将更详细地讨论它们。
字符串
字符串是存储在内存的连续字节中的一系列字符。 C + + 处理字符串的方式有两种。
- 第一种来自 C 语言, 常被称为 C-风格字符串( C-style string)。
- 另一种基于 string 类库的方法。
存储在连续字节中的一系列字符意味着可以将字符串存储在 char 数组中, 其中每个字符都位于自己的数组元素中。
C-风格字符串具有一种特殊的性质: 以空字符( null character) 结尾, 空字符被写作\0
, 其 ASCII 码为 0, 用来标记字符串的结尾。
空字符对 C-风格字符串而言至关重要。 例如, C + + 有很多处理字符串的函数, 其中包括 cout 使用的那些函数。 它们都逐个地处理字符串中的字符, 直到到达空字符为止。
有一种更好的、 将字符数组初始化为字符串的方法— 只需使用一个用引号括起的字符串即可, 这种字符串被称为字符串常量( string constant) 或字符串字面值( string literal),用引号括起的字符串隐式地包括结尾的空字符, 因此不用显式地包括它 .
应确保数组足够大, 能够存储字符串中所有字符— 包括空字符。 使用字符串常量初始化字符数组是这样的一种情况, 即让编译器计算元素数目更为安全。 让数组比字符串长没有什么害处, 只是会浪费一些空间而已。这是因为处理字符串的函数根据空字符的位置, 而不是数组长度来进行处理。 C + + 对字符串长度没有限制。
使用 标准 C 语言库函数 strlen( )来确定字符串的长度。sizeof 运算符指出整个数组的长度: 15 字节, 但 strlen( )函数返回的是存储在数组中的字符串的长度, 而不是数组本身的长度。 另外, strlen()不把空字符计算在内。
#include <iostream>
using namespace std;
void print_array_char(char a[],int a_size);
int main() {
char c1[10] = { 'h','e','l','l','o' }; // 不是字符串string
char c2[10] = { 'h','e','l','l','o','\0',' ','Y','o','u'}; // 是字符串
// 用字符串直接初始化字符串数组
char c3[] = "Hello world!";
print_array_char(c1, sizeof(c1) / sizeof(char)); // h e l l o
print_array_char(c2, sizeof(c2) / sizeof(char)); // h e l l o Y o u
print_array_char(c3, sizeof(c3) / sizeof(char)); // H e l l o w o r l d !
cout << c1 << endl; // hello
cout << c2 << endl; // hello
cout << c3 << endl; // Hello world!
// 字符串长度不包括字符串结尾字符 , 一般和字符数组长度不相等
cout << "c1,c2,c3 的长度:" << strlen(c1) << " " << strlen(c2) << " " << strlen(c3) << endl; //c1,c2,c3 的长?5 5 12
// 字符与字符串
char c4[] = "s";
char c5 = 's'; // 单个字符
cout << sizeof(c4) / sizeof(char) << endl; // 2, 包括 s,\0
cout << sizeof(c5) / sizeof(char) << endl; // 1
// 字符串拼接
cout << "part1" "part2" << endl; //part1part2
cout << "part1"
"part2" << endl; //part1part2
// cin初始化数组
char name[50];
cout << "What's your name?\n";
cin >> name;
cout << "Hello," << name << ",nice to meet you!\n";
/*
What's your name?
Jerry
Hello,Jerry,nice to meet you!
*/
char name2[] = "Hello Kitty";
name2[7] = '\0';
cout << name2 << endl; // Hello K
cout << sizeof(name2) << endl; // 12
cout << strlen(name2) << endl; // 7
}
void print_array_char(char a[],int a_size) {
//打印整数数组
int temp;
for (temp = 0; temp < a_size; temp++) cout << a[temp] << ' ';
cout << endl;
}
由于不能通过键盘输入空字符, 因此 cin 需要用别的方法来确定字符串的结尾位置。 cin 使用空白( 空格、 制表符和换行符) 来确定字符串的结束位置, 这意味着 cin 在获取字符数组输入时只读取一个单词。 读取该单词后, cin 将该字符串放到数组中, 并自动在结尾添加空字符。
istream 中的类( 如 cin) 提供了一些面向行的类成员函数: getline( )和 get( )。 这两个函数都读取一行输入, 直到到达换行符。 然而, 随后 getline( )将丢弃换行符, 而 get( )将换行符保留在输入序列中。
面向行的输入
getline( )函数读取整行, 它使用通过回车键输入的换行符来确定输入结尾。 要调用这种方法, 可以使用 cin.getline( )。 该函数有两个参数。 第一个参数是用来存储输入行的数组的名称, 第二个参数是要读取的字符数。 如果这个参数为 20, 则函数最多读取 19 个字符, 余下的空间用于存储自动在结尾处添加的空字符。 getline( )成员函数在读取指定数目的字符或遇到换行符时停止读取。
istream 类有另一个名为 get( )的成员函数, 该函数有几种变体。 其中一种变体的工作方式与 getline( )类似, 它们接受的参数相同, 解释参数的方式也相同, 并且都读取到行尾。 但 get 并不再读取并丢弃换行符, 而是将其留在输入队列中。没有丢弃的换行符将作为下一次读取的输入的开头, 而不是第一次调用的输入的结尾.
get( )有另一种变体。 使用不带任何参数的 cin.get( )调用可读取下一个字符( 即使是换行符), 因此可以用它来处理换行符, 为读取下一行输入做好准备。
如果使用的是 cin.get( name, ArSize), 则编译器知道是要将一个字符串放入数组中, 因而将使用适当的成员函数。 如果使用的是 cin.get( ), 则编译器知道是要读取一个字符。 第 8 章将探索这种特性— 函数重载。
为什么要使用 get( ), 而不是 getline( )呢? 首先, 老式实现没有 getline( )。 其次, get( )使输入更仔细。 例如, 假设用 get( )将一行读入数组中。 如何知道停止读取的原因是由于已经读取了整行, 而不是由于数组已填满呢? 查看下一个输入字符, 如果是换行符, 说明已读取了整行; 否则, 说明该行中还有其他输入。 第 17 章将介绍这种技术。
getline( )使用起来简单一些, 但 get( )使得检查错误更简单些。 可以用其中的任何一个来读取一行输入; 只是应该知道, 它们的行为稍有不同。
#include <iostream>
using namespace std;
int main() {
char longName[50];
cout << "Your long name?" << endl;
cin.getline(longName, 50);
cout << "Hello," << longName << ",nice to meet you!\n";
/*
Your long name?
This is my LONG NAME
Hello,This is my LONG NAME,nice to meet you!
*/
char longName2[50];
cout << "Input another long name?" << endl;
// 第一次用cin.get(VarName,strLength) 调用的结果是换行符留在了输入队列中
// 但换行符没有作为变量的一部分, 而会作为下一次输入的变量的一部分
cin.get(longName2, 50);
cout << "Hello," << longName2 << ",nice to meet you!\n";
// 再次调用cin.getline(), 会将上次输入队列的换行作为输入,不会给机会输入名字
char longName3[50];
cout << "Input another long name? " << endl;
cin.getline(longName3, 50);
cout << "Hello," << longName3 << ",nice to meet you!\n";
/*
Input another long name?
This is my LONG NAME2
Hello,This is my LONG NAME2,nice to meet you!
Input another long name?
Hello,,nice to meet you!
*/
char longName4[50];
cout << "Input long name4? " << endl;
cin.getline(longName4, 50);
cout << "Hello," << longName4 << ",nice to meet you!\n";
// 把之上的输入流中的换行符吸收掉
cin.get();
char longName5[50];
cout << "Input long name5? " << endl;
cin.getline(longName5, 50);
cout << "Hello," << longName5 << ",nice to meet you!\n";
/*
Input long name4?
LN4
Hello,LN4,nice to meet you!
Input long name5?
LN5
Hello,LN5,nice to meet you!
*/
// 串联
cout << "please input multi lines:\n";
char longLine1[200];
char longLine2[200];
cin.getline(longLine1, 200).getline(longLine2, 200);
cout << longLine1 << endl;
cout << longLine2 << endl;
/*
please input multi lines:
Line1dddddddddddddddddddddd
Line2dddddddddddddddddddddd
Line1dddddddddddddddddddddd
Line2dddddddddddddddddddddd
*/
}
小结:
- getline(Variable Name, Length) 是会清理输入队列末尾的换行符的
cin>>VariableName
不会清理换行符- get() 可以用于吸收一个字符,所以可以用来清理输入队列中的换行符
- 当 get( )( 不是 getline( )) 读取空行后将设置失效位( failbit)。 这意味着接下来的输入将被阻断, 但可以用下面的命令来恢复输入:cin.clear() .cin.clear()并不是清理输入队列.
#include <iostream>
using namespace std;
int main() {
char Name[50];
int age;
char address[100];
cout << "Your name?\n";
cin.getline(Name, 50);
cout << "Your age?\n";
cin >> age; // 数值输入的回车对应的换行符会保留在输入队列中,需要抛弃掉
cin.get(); //吸收掉输入队列中的最后一个换行符,达到抛弃之前输入的换行符的目的
cout << "Your address?\n";
cin.getline(address, 50);
cout << Name<< " "<< age<< " "<<address<<endl;
/*
Your name?
Jerry
Your age?
20
Your address?
my address
Jerry 20 my address
*/
}
string 类简介
ISO/ ANSI C + + 98 标准通过添加 string 类扩展了 C + + 库, 因此现在可以 string 类型的变量( 使用 C + + 的话说是对象) 而不是字符数组来存储字符串。
string 类使用起来比数组简单, 同时提供了将字符串作为一种数据类型的表示方法。 要使用 string 类, 必须在程序中包含头文件 string。 string 类位于名称空间 std 中。
string 类定义隐藏了字符串的数组性质, 让您能够像处理普通变量那样处理字符串。
与使用数组相比, 使用 string 对象更方便, 也更安全。 从理论上说, 可以将 char 数组视为一组用于存储一个字符串的 char 存储单元, 而 string 类变量是一个表示字符串的实体。
#include <iostream>
#include <string>
using namespace std;
int main() {
//可以使用 C - 风格字符串来初始化 string 对象。
string c1 = "Hello World!";
// 可以使用 cin 来将键盘输入存储到 string 对象中。
string c2, c3,c4;
cout << "Input a string with blank middle:\n";
getline(cin, c2);
cin.clear();
cout << "Input a string with blank middle:\n";
getline(cin,c3, '\n');// 把停止识别的符号设置为‘\n’(即换行符),否则遇到空格就停止输入
// 可以使用 cout 来显示 string 对象。
cout << "c2:" << c2 << endl;
//可以使用数组表示法来访问存储在 string 对象中的字符。
cout << "c2[0]:" << c2[0] << endl;
cout << "c3:" << c3 << endl;
//////////////////
cout << "Input a string with blank middle:\n";
cin >> c4;
cout << "c4:" << c4 << endl;
// 类设计让程序能够自动处理 string 的大小。
c4 = "Long Line ddddddddddddddddddddddddddddddd";
cout << "c4:" << c4 << endl;
}
/*
Input a string with blank middle:
Line One
Input a string with blank middle:
Line Two
c2:Line One
c2[0]:L
c3:Line Two
Input a string with blank middle:
Line Three
c4:Line
c4:Long Line ddddddddddddddddddddddddddddddd
*/
字符串:c风格和c++风格
- 数组名称对应的是指针不是对象, 数组字符串之间不能直接赋值.
- string 类型的字符串变量对应的是对象不是数组, 可以相互赋值.
- 处理 string 对象的语法通常比使用 C 字符串函数简单, 尤其是执行较为复杂的操作时。
- 另外, 使用字符数组时, 总是存在目标数组过小, 无法存储指定信息的危险,string 类具有自动调整大小的功能, 从而能够避免这种问题发生。 C 函数库确实提供了与 strcat( )和 strcpy( )类似的函数— strncat( )和 strncpy( ), 它们接受指出目标数组最大允许长度的第三个参数, 因此更为安全, 但使用它们进一步增加了编写程序的复杂度。
- 函数 strlen( )是一个常规函数, 它接受一个 C-风格字符串作为参数, 并返回该字符串包含的字符数。 函数 size( )的功能基本上与此相同, 但句法不同size( )是一个类方法。
#include <iostream>
#include <string>
#include <cstring> //strcpy, strcat, string.h 的c++版本
using namespace std;
int main()
{
char c1[50] = "Hello world!";
char c2[50];
string str1 = "Hello world!";
string str2,str3;
// c2=c1;//error: invalid array assignment
str2 = str1;
str3 = str2 + " There";
cout << str2<<endl; //Hello world!
cout<<str3<<endl; //Hello world! There
strcpy(c2,c1); // 复制字符串数组c1 到c2
cout<<c2<<endl; //Hello world!
strcat(c2,c1); // 连接字符串c1到c2
cout<<c2<<endl; //Hello world!Hello world!
cout<<strlen(c1)<<endl; //12
// cout<<strlen(str1)<<endl; // error: cannot convert 'std::__cxx11::string' {aka 'std::__cxx11::basic_string<char>'} to 'const char*'
cout<<str1.size();//12
return 0;
}
代码片段:
#include <iostream>
#include <string>
#include <cstring> //strcpy, strcat, string.h 的c++版本
using namespace std;
int main()
{
char c1[50];
string s1;
cout << "c1 初始化之前的长度:" << strlen(c1) << endl;
cout << "s1 初始化之前的长度:" << s1.size() << endl;
/*
字符串数组初始化之前的内容是不确定的,对应的 strlen 的返回值也不同
c1 初始化之前的长度:4
s1 初始化之前的长度:0
*/
cout << "输入c1字符数组:\n";
// A long sentence, in which the writer delays the core to the middle of the sentence
cin.getline(c1, 50); // 需要指定最大长度,避免超越边界. 最多只能存49个字符
cin.clear();
cout << "输入s1字符串:\n";
getline(cin, s1); // cin 的调用方式不同, 也不需要指定最大长度
cout << "c1:" << c1 << endl;
cout << "s1:" << s1 << endl;
cout << "c1 最新的长度:" << strlen(c1) << endl;
cout << "s1 最新的长度:" << s1.size() << endl;
/*
第一组输出: 如果超越了最大边界, 后续的输入会比较奇怪
输入c1字符数组:
A long sentence, in which the writer delays the core to the middle of the sentence
输入s1字符串:
c1:A long sentence, in which the writer delays the c
s1:ore to the middle of the sentence
c1 最新的长度:49
s1 最新的长度:33
*/
/*
第二组输入: A short sentence
输入c1字符数组:
A short sentence
输入s1字符串:
A short sentence
c1:A short sentence
s1:A short sentence
c1 最新的长度:16
s1 最新的长度:16
*/
/*
string类的输入:
- getline(cin,VarName): 可以处理空格
- cin>> VarName : 只能输入一个单词
*/
cout << "getline(cin,VarName)输入s1字符串,带空格:\n";
getline(cin, s1);
cout << "s1:" << s1 << endl;
cout << "cin>> VarName输入s1字符串,带空格:\n";
cin >> s1;
cout << "s1:" << s1 << endl;
/*getline(cin,VarName)输入s1字符串,带空格:
Hello World!
s1:Hello World!
cin>> VarName输入s1字符串,带空格:
Hello World!
s1:Hello
*/
return 0;
}
字符串字面值
- char
- wchar_t,char16_t , char32_t。 对于这些类型的字符串字面值, C + + 分别使用前缀 L、 u 和 U 表示
- Unicode 字符编码方案 UTF-8。在这种方案中, 根据编码的数字值, 字符可能存储为 1 ~ 4 个八位组。 C + + 使用前缀 u8 来表示这种类型的字符串字面值。
- 原始( raw) 字符串: 在原始字符串中, 字符表示的就是自己, 例如, 序列
\n
不表示换行符, 而表示两个常规字符— 斜杠和 n, 因此在屏幕上显示时, 将显示这两个字符。 另一个例子是, 可在字符串中使用", 而无需使用繁琐的\"
。 当然, 既然可在字符串字面量包含", 就不能再使用它来表示字符串的开头和末尾。 因此, 原始字符串将"( 和)"
用作定界符, 并使用前缀 R 来标识原始字符串 - 可将前缀 R 与其他字符串前缀结合使用, 以标识 wchar_t 等类型的原始字符串。 可将 R 放在前面, 也可将其放在后面, 如 Ru、 UR 等。
#include <iostream>
#include <string>
#include <cstring> //strcpy, strcat, string.h 的c++版本
#include <wchar.h>
#include <locale.h>
using namespace std;
int main()
{
setlocale(LC_ALL, "");
wchar_t w_c[] = L"Hello world!";
char16_t c16t_c[] = u"Hello world!";
char32_t c32t_c[] = U"Hello world!";
char utf8_c[] = u8"Hello world!";
cout<<w_c<<endl;
cout<<c16t_c<<endl;
cout<<c32t_c<<endl;
cout<<utf8_c<<endl;
// 原始字符串将"( 和)" 用作定界符, 并使用前缀 R 来标识原始字符串
cout<<R"("Hello \n")"<<endl;
// 0x61fe00
// 0x61fde0
// 0x61fda0
// Hello world!
// "Hello \n"
// 原始字符串语法允许您在表示字符串开头的" 和( 之间添加其他字符, 这意味着表示字符串结尾的" 和) 之间也必须包含这些字符。 因此, 使用 R" +*( 标识原始字符串的开头时, 必须使用) +*" 标识原始字符串的结尾。
// 貌似只能在 " 和括号之间添加一个字符
cout<<R"-(Hello)-"<<endl; //Hello
return 0;
}
结构
结构是一种比数组更灵活的数据格式, 因为同一个结构可以存储多种类型的数据, 这使得能够将有关篮球运动员的信息放在一个结构中, 从而将数据的表示合并到一起。 如果要跟踪整个球队, 则可以使用结构数组。 结构也是 C + + OOP 堡垒( 类) 的基石。 学习有关结构的知识将使我们离 C + + 的核心 OOP 更近。
结构是用户定义的类型, 而结构声明定义了这种类型的数据属性。 定义了类型后, 便可以创建这种类型的变量。 因此创建结构包括两步。
- 首先, 定义结构描述— 它描述并标记了能够存储在结构中的各种数据类型。
- 然后按描述创建结构变量( 结构数据对象)。
结构声明的位置很重要。 有两种选择。 - 可以将声明放在 main( )函数中, 紧跟在开始括号的后面。 另一种选择是将声明放到 main( )的前面.位于函数外面的声明被称为外部声明。
对于那些包含两个或更多函数的程序来说, 差别很大。 外部声明可以被其后面的任何函数使用, 而内部声明只能被该声明所属的函数使用。 通常应使用外部声明, 这样所有函数都可以使用这种类型的结构
用已经存在的结构体变量赋值给结构体数组或者其他结构体变量, 会复制值给对方, 并不是把自己的指针给对方.
#include <iostream>
#include <string>
using namespace std;
struct student {
int id;
string fullname;
int age;
int high;
}; // 结构体定义是语句, 需要加分号
int main()
{
// C + + 允许在声明结构变量时省略关键字 struct:
struct student Jerry;
student Tom {2,"Tom The Cat",2,30}; // 省略了 =
Jerry={
1,
"Jerry The Mouse",
2,
5
};
cout<<Tom.fullname<<",age:"<<Tom.age<<endl;
// Tom The Cat,age:2
// 结构体数组
student friends[3] = {Tom,Jerry};
for (int i=0;i<sizeof(friends)/sizeof(student);i++){
cout<<friends[i].fullname<<",age:"<<friends[i].age<<endl;
}
// Tom The Cat,age:2
// Jerry The Mouse,age:2
// ,age:0
// 用一个结构体给另一个结构体赋值
student Tom2 = Tom;
friends[2]=Tom2;
Tom.fullname ="Tom Original";
Tom2.age=3;
for (int i=0;i<sizeof(friends)/sizeof(student);i++){
cout<<friends[i].fullname<<",age:"<<friends[i].age<<endl;
}
// Tom The Cat,age:2
// Jerry The Mouse,age:2
// Tom The Cat,age:2
cout<<Tom.fullname<<",age:"<<Tom.age<<endl;
// Tom Original,age:2
cout<<Tom2.fullname<<",age:"<<Tom2.age<<endl;
//Tom The Cat,age:3
return 0;
}
结构中的位字段
与 C 语言一样, C + + 也允许指定占用特定位数的结构成员, 这使得创建与某个硬件设备上的寄存器对应的数据结构非常方便。 字段的类型应为整型或枚举( 稍后将介绍), 接下来是冒号, 冒号后面是一个数字, 它指定了使用的位数。 可以使用没有名称的字段来提供间距。 每个成员都被称为位字段( bit field)。
位字段通常用在低级编程中。
#include <iostream>
#include <string>
using namespace std;
int main()
{
// 位定义的每个变量结尾需要有分号
struct struct_bits {
unsigned int id: 4; // 4 bit
unsigned int : 2; // 两位没有变量名, 用于分隔
bool good: 1; // 1位
};
struct_bits s1 = {20, true}, s2 = {21, false};
cout << sizeof(s1) << endl; //8
cout <<s1.id<<" "<<s1.good<<endl; // 4 1
cout <<s2.id<<" "<<s2.good<<endl; // 5 0
return 0;
}
共用体
- 共用体( union) 是一种数据格式, 它能够存储不同的数据类型, 但在某一个时间点只能存储其中的一种类型。
- 共用体成员名称标识了变量的容量。 由于共用体每次只能存储一个值, 因此它必须有足够的空间来存储最大的成员, 所以, 共用体的长度为其最大成员的长度。
- 共用体的用途之一是, 当数据项使用两种或更多种格式( 但不会同时使用) 时, 可节省空间。
- 共用体常用于( 但并非只能用于) 节省内存。 当前, 系统的内存多达数 GB 甚至数 TB, 好像没有必要节省内存, 但并非所有的 C + + 程序都是为这样的系统编写的。 C + + 还用于嵌入式系统编程, 如控制烤箱、 MP3 播放器或火星漫步者的处理器。 对这些应用程序来说, 内存可能非常宝贵。 另外, 共用体常用于操作系统数据结构或硬件数据结构。
#include <iostream>
#include <string>
using namespace std;
int main()
{
union var4all {
int id_int;
// string id_string[50];// 不能用字符串对象
char id_string[50];
};
var4all myid;
myid.id_int = 1;
cout << "my id:" << myid.id_int << endl;
cout << "my id:" << myid.id_string << endl;
// my id:1
// my id: // 这里看到的是控制字符 SOH,Start Of Head
char temp[] = "myStringID";
int i;
for(i = 0; i < sizeof(temp) / sizeof(char); i++) {
myid.id_string[i] = temp[i];
}
myid.id_string[i] = '\0';
cout << "my id:" << myid.id_int << endl;
cout << "my id:" << myid.id_string << endl;
// my id:1951627629
// my id:myStringID
// 程序员必须自己确定目前哪个共用体变量在使用
struct animal {
string name;
string type;
// 匿名共用体, 可以直接调用其中定义的变量
union {
int legs_num; // 腿的数量
double wing_len; // 翅膀的长度
};
};
animal animals[2];
animals[0].name = {"Page"};
animals[0].type = "pig";
animals[1].name = {"Kowalski"};
animals[1].type = "Penguin";
for(int i = 0; i < 2; i++) {
if (animals[i].type == "pig") {
animals[i].legs_num = 4;
}
else {
animals[i].wing_len = 10;
}
cout << "animal name:" << animals[i].name << ", animals legs num:" << animals[i].legs_num;
cout << ", animal length of winds:" << animals[i].wing_len << endl;
// 错误的使用共用体变量的输出
// animal name:Page, animals legs num:4, animal length of winds:1.97626e-323
// animal name:Kowalski, animals legs num:0, animal length of winds:10
}
for(int i = 0; i < 2; i++) {
if (animals[i].type == "pig") {
cout << "animal name:" << animals[i].name << ", animals legs num:" << animals[i].legs_num << endl;
}
else {
cout << "animal name:" << animals[i].name << ", animal length of winds:" << animals[i].wing_len << endl;
}
}
// animal name:Page, animals legs num:4
// animal name:Kowalski, animal length of winds:10
return 0;
}
枚举
- C + + 的 enum 工具提供了另一种创建符号常量的方式, 这种方式可以代替 const。 它还允许定义新类型, 但必须按严格的限制进行。 使用 enum 的句法与使用结构相似。
- 在默认情况下, 将整数值赋给枚举量, 第一个枚举量的值为 0, 第二个枚举量的值为 1, 依次类推。 可以通过显式地指定整数值来覆盖默认值,实际上可以随意指定整数值
- 在不进行强制类型转换的情况下, 只能将定义枚举时使用的枚举量赋给这种枚举的变量
- 对于枚举, 只定义了赋值运算符。 具体地说, 没有为枚举定义算术运算.有些实现并没有这种限制, 这有可能导致违反类型限制。
- 枚举的规则相当严格。 实际上, 枚举更常被用来定义相关的符号常量, 而不是新类型。 例如, 可以用枚举来定义 switch 语句中使用的符号常量
- 枚举类型有对应的整数, 但最好不要当整数来用
- 取值范围的定义如下。
- 首先, 要找出上限, 需要知道枚举量的最大值。 找到大于这个最大值的、 最小的 2 的幂, 将它减去 1, 得到的便是取值范围的上限。 例如, 前面定义的 bigstep 的最大值枚举值是 101。 在 2 的幂中, 比这个数大的最小值为 128, 因此取值范围的上限为 127。
- 要计算下限, 需要知道枚举量的最小值。
- 如果它不小于 0, 则取值范围的下限为 0;
- 否则, 采用与寻找上限方式相同的方式, 但加上负号。 例如, 如果最小的枚举量为 − 6, 而比它小的、 最大的 2 的幂是 − 8( 加上负号), 因此下限为 − 7。
- 选择用多少空间来存储枚举由编译器决定。 对于取值范围较小的枚举, 使用一个字节或更少的空间; 而对于包含 long 类型值的枚举, 则使用 4 个字节。
- C + + 11 扩展了枚举, 增加了作用域内枚举( scoped enumeration), 第 10 章的“ 类作用域” 一节将简要地介绍这种枚举。
#include <iostream>
#include <string>
using namespace std;
int main()
{
enum color {red,green,yellow,blue,violet}; // 定义了5种颜色为枚举类型 , 没有 =
color myFavoriteColor=blue;
cout<<"My favorite color:"<<myFavoriteColor<<endl; //3
// myFavoriteColor=4; // error: invalid conversion from 'int' to 'main()::color' [-fpermissive]
// myFavoriteColor++; // error: invalid conversion from 'int' to 'main()::color' [-fpermissive]
myFavoriteColor=color(4); // 强制类型转换
cout<<"My favorite color:"<<myFavoriteColor<<endl; //4
// 指定编号
enum nums {one=1,two=2,three=3,four=4,five=5,six=100,seven};
nums myFavoriteNum =five;
cout<<"My favorite num:"<<myFavoriteNum<<endl; //5
myFavoriteNum =six;
cout<<"My favorite num:"<<myFavoriteNum<<endl; //100
myFavoriteNum =seven; //自动加1
cout<<"My favorite num:"<<myFavoriteNum<<endl; //101
// 枚举值的取值范围对应的是一个整数上下限范围, 并不是枚举定义中列出的值
myFavoriteNum =nums(120); //
cout<<"My favorite num:"<<myFavoriteNum<<endl; //120, 合法
}
© Licensed under CC BY-NC-SA 4.0别向医生和律师提供错误的消息。—— 本杰明·富兰克林