数组
多个相同类型的值。
数组声明三点:类型,数组名,元素数目。
//声明一个数组
short months[12];
//数目可以是整型或const值,也可以是常量表达式。
//赋值
months[0] = 31;
//访问元素
cout << months[0];
//初始化(仅在定义时可用)可以只初始化前两个
int cards[4] = {1,2};
//不特别指定数目初始化
int cards[] = {1,2,3}
//大括号内不包含任何东西,则元素均为0
unsigned int counts[10] = {};
float balances[100]{};
字符串
一系列字符。
空字符’\0’
用引号括起的字符串隐式地包括结尾的空字符。
字符串到char数组中,将自动加上结尾的空字符。
//'S'和"S"不同
//前者为字符串编码的简写,83的另一种写法(ASCII系统);
//后者为字符串,表示两个字符:字符S和\0,且"S"实际上表示的是字符串所在的内存地址。
char shirt_size = 'S'; // this is fine
char shirt_size = "S"; // illegal type mismatch
//因为地址在C++中是一种独立的类型
拼接字符串常量
用空格、制表符、换行符分隔的字符串常量都将自动拼接。前一个字符串末尾的\0将被后面的字符串覆盖。
//三种等效写法
cout << "hello" " world";
cout << "hello world";
cout << "hello"
" world";
在数组中使用字符串
// strings.cpp -- storing strings in an array
#include <iostream>
#include <cstring> // for the strlen() function
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 stored\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 characters of my name:";
cout << name2 << endl;
return 0;
}
运行结果:
Howdy! I'm C++owboy! What's your name?
Peter
Well, Peter, your name has 5 letters and is stored
in an array of 15 bytes.
Your initial is P.
Here are the first 3 characters of my name:C++
4.2.4 输入整行字符串
cin.getline(name, 20); // 丢弃换行符,补上空字符\0
cin.get(name, 20); // 保留换行符在输入队列中,补上空字符\0
利用函数重载的特性!!
不带参的cin.get()可以读取下一个字符(包括换行符),
所以可以用来跳过换行符,读取新行。
cin.get(name, ArSize);
cin.get();
cin.get(dessert, ArSize);
空行问题
get()读取空行后将设置失效位(failbit)。意味着接下来的输入将被阻断,但可以用接下来的命令来恢复输入:
cin.clear();
当输入的字符串比分配的空间长,则getline()和get()将把余下的字符留在输入队列中,而getline()还会设置失效位,并关闭后面的输入。
混合输入字符串和数字
两种解决回车被作为下一个输入的问题:
//方法1
cin >> year;
cin.get();
//方法2,表达式返回cin对象,调用拼接起来
(cin >> year).get();
C++程序常使用指针(而不是数组)来处理字符串。
string类简介
一种处理字符串的途径。
#include <string>
string str1; // 长度为0
string str2 = "panther";
cin >> str1; // 长度适应输入
使用列表初始化string类
char first_data[] = {"Le Chapon Dodu"}
string third_data = {"The Bread Bowl"};
string fourth_data {"Hank's Fine Eats"};
赋值
string str1;
string str2 = "panther";
str1 = str2;
拼接
string str3;
str3 = str1 + str2;
附加
//将C-风格字符串附加到string末尾
string s2 = "buzzard";
s2 += "for a day";
str1 += str2;
获取字符串长度
#include <string>
str1.size();
其他形式的字符串面值
wchar_t title[ ] = L"Chief Astrogator";
char16_t name[ ] = u"Felonia Ripova";
char32_t car[ ] = U"Humber Super Snipe";
Unicode字符集,UTF-8编码方案:
MSVC默认GBK编码,g++默认UTF-8编码
在字符串前加u8
#include <iostream>
using namespace std;
int main()
{
const char* a = "\u4f60";
const char* b = u8"好";
const char* c = "啊";
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
原始(raw)字符串
保持原样,不进行转义
cout << R"Jim "King" Tutt use "\n" instead of endl." << endl;
输出:
Jim "King" Tutt uses \n instead of endl.
定界符:
默认定界符:R"内容"
自定义定界符:R"+内容+"
R与其他前缀结合:
Ru或UR,在前在后都可。
结构简介
存储多个不同类型的值。
//在C++中可省略struct关键字
//结构声明
struct people
{
char name[20];
std::string character;
int age;
float height;
double weight;
};
//可通过.来访问成员
people peter =
{
"peter",
"outstanding, deteminate",
24,
177.5,
60.0
};
peter.age
//访问类成员变量是从访问结构成员变量方式衍生而来的。
//结构可在函数外或函数内进行声明
成员赋值(memberwise assignment)
将一个结构赋值给另一个结构。
同时定义、创建结构变量:
struct people
{
char name[20];
std::string character;
int age;
float height;
double weight;
} Peter, Anne;
定义、创建、初始化结构变量
struct people
{
char name[20];
std::string character;
int age;
float height;
double weight;
} Anne =
{
"Anne",
"wild",
25,
158,
55,
};
可以定义没有名称的结构,但是后续没法创建该类型变量。
可以创建元素为结构的数组,方法和创建基本类型数组完全相同。
people wang[2]=
{
{"Peter", "clam", 24, 177, 60},
{"Anne", "wild", 25, 158, 55}
};
4.4.6 结构中的位字段
struct torgle_register
{
unsigned int SN : 4;
unsigned int : 4;
bool goodIn : 1;
bool goodTorgle : 1;
}
torgle_register tr = {14, true, false};
...
if (tr.goodIn)
...
共用体(union)
可以存储不同的数据类型,但任何时刻只能有其中一种数据类型。
union one4all
{
int int_val;
long long_val;
double double_val;
};
one4all pail;
pail,int_val = 15;
cout << pail.int_val;
pail.double_val = 1.38;
cout << pail.double_val;
匿名共用体(anonymous union)
没有名称,成员位于相同地址处的变量。
struct widget
{
char brand[20];
int type;
union
{
long id_num;
char id_char[20];
};
}
widget prize;
...
if (prize.type == 1)
cin >> prize.id_num;
else
cin >> prize.id_char;
// id_num和id_char被视为prize的两个成员。
共用体多用于节省内存。
枚举(enum)
提供了另一种创建符号常量的方式,这种方式可以替代const。还允许定义新类型。
enum spectrum {red, orange, yellow, green, blue, violet, indigo, ultraviolet}
// 这条语句完成两项工作:
// 让spectrum成为新类型的名称;spectrum被称为枚举(enumeration)
// 将red、orange、yellow等作为符号常量,它们对应整数值0~7。这些常量叫做枚举量。
spectrum band;
band = red;
对于枚举,只定义了赋值运算符,没有为枚举定义算数运算符。
枚举的取值范围
上限:比最大值再大一些的2的幂减一,如:101 -->128-1,即127
下限:最小值若大于等于0,则为0,否则如上限计算,但符号为负号。
4.7 指针和自由存储空间
指针:将数据所处位置告诉计算机的变量。
运算符被称作间接值(indirect value)或解除引用(dereferencing)运算符。将其用于指针,可以得到该地址处存储的值(C++根据上下文来判断是乘号还是解除引用运算符)。
假设manly是一个指针,则manly表示的是一个地址,而manly表示存储在该地址处的值。
int updates = 6;
int *p_updates;
p_updates = &updates;
可以像用int变量那样使用*p_updates。
4.7.1 声明和初始化指针
计算机需要跟踪指针指向的值的类型。指针声明必须指定指针指向的数据的类型。
int * p_updates;
表明* p_updates的类型为int。
p_updates是指向int的指针,或int *
int*是一种复合类型。*号两边空格可有可无。
4.7.2 指针的危险
一定要在对指针应用解除引用运算符(*)之前,将指针初始化为一个适当的地址,这是关于使用指针的金科玉律。
4.7.3 指针和数字
int强转指针
int * pt;
//pt = 0xB8000000; // type mismatch
pt = (int*) 0xB8000000; // types now match
4.7.5 使用new来分配内存
new运算符会寻找一块大小合适的内存来,存放数据。
int * pn = new int; // 分配内存空间
// typeName * pointer_name = new typeName;
*pn = 100; // 赋值
对于指针,与常规变量存储在栈(stack)中不同,new从被称为堆(heap)或自由存储区(free store)的内存区域分配内存。
4.7.5 使用delete释放内存
将内存归还给内存池。
int * ps = new int; // allocate memory with new
... // use the memory
delete ps; // free memory with delete when done
不会删除指针ps,一定要配对使用new和delete,否则将发生内存泄漏(memory leak)。
不能重复释放;不能释放不是由new来分配的内存。
int * ps = new int; // OK
delete ps; // OK
delete ps; // not ok now
int jugs = 5; // ok
int * pi = &jugs; // ok
delete pi; // not allowed, memory not allocated by new
对空指针使用delete是安全的。
一般来说,不要创建两个指向同一个内存块的指针,因为这将增加错误地删除同一个内存块两次的可能性。
4.7.6 使用new来创建动态数组
静态联编(static binding):编写程序时就决定是否分配内存及大小。
动态联编(dynamic array):在运行时决定是否分配内存及大小。
使用new创建动态数组
int * psome = new int [10]; // get a block of 10 ints
new创建数组释放方法
delete [] psome;
即:如果使用new时带方括号,则使用delete时也应带方括号。
int * pt = new int;
delete [] pt; // effect is undefined, don't do it
short * ps = new short[500];
delete ps; // effect is undefined, don't do it
为数组分配内存的通用格式:
type_name * pointer_name = new type_name [num_elements]
使用动态数组
创建:
int * psome = new int [10]; // get a block of 10 ints
将指针当作数组来使用:
psome[0] // 代表第一个元素
psome[1] // 代表第二个元素
指针和数组名的根本差异:
p3 = p3 + 1; // okay for pointers, wrong for array names
不能修改数组名的值;
但指针加1后将指向下一个元素的位置,再减一后,才能给delete [ ]提供正确的地址。
指针、数组和指针算术
指针和数组基本等价的原因在于指针算术(pointer arithmetic),和C++内部处理数组的方式。C++将数组名解释为地址。
// addpntrs.cpp -- pointer addition
#include <iostream>
int main()
{
using namespace std;
double wages[3] = {10000.0, 20000.0, 30000.0};
short stacks[3] = {3, 2, 1};
// Here are two ways to get the address of an array
double * pw = wages; // name of an array = address
short * ps = &stacks[0]; // or use address operator
// with array element
cout << "pw = " << pw << ", *pw = " << *pw << endl;
pw = pw + 1;
cout << "add 1 to the pw pointer: \n";
cout << "pw = " << pw << ", *pw = " << *pw << "\n\n";
cout << "ps = " << ps << ", *ps = " << *ps << endl;
ps = ps + 1;
cout << "add 1 to the ps pointer:\n";
cout << "ps = " << ps << ", *ps = " << *ps << "\n\n";
cout << "access two elements with array notation\n";
cout << "stacks[0] = " << stacks[0]
<< ", stacks[1] = " << stacks[1] << endl;
cout << "access two elements with pointer notation\n";
cout << "*stacks = " << *stacks
<< ", *(stacks + 1) = " << *(stacks + 1) << endl;
cout << sizeof(wages) << " = size of wages array\n";
cout << sizeof(pw) << " = size of pw pointer\n";
return 0
};
4.8.1 程序说明
- 多数情况下,C++将数组名解释为数组第1个元素的地址。
- 将指针变量加1后,其增加的值等于指向的类型占用的字节数。
- 可以看出:*(stacks + 1)和stacks[1]是等价的。
- 通常,使用数组表示法时,C++都执行下面的转换:
arrayname[i] becomes * (arrayname + i)
如果使用的是指针,而不是数组名,则C++也将执行同样的转换:
pointername[i] becomes * (pointername + i)
- 很多情况下,可以相同的方式使用指针名和数组名。对于它们,可以使用数组方括号表示法,也可以使用解除引用运算符(*)。在多数表达式中,它们都表示地址。区别之一是,可以修改指针的值,而数组名是常量。
pointername = pointername + 1; // valid
arrayname = arrayname + 1; // not allowed
- 另一个区别是,对数组应用sizeof运算符得到的是数组的长度,而对指针应该sizeof得到的是指针的长度。因为在这种情况下,C++不会将数组名解释为地址。
- 数组名被解释为其第一个元素的地址,而对数组名应用地址运算符时,得到的是整个数组的地址,如下例:
short tell[10]; // tell an array of 20 bytes
cout << tell << endl; // displays &tell[0]
cout << &tell << endl; // displays address of whole array
cout << tell + 1 << endl;
cout << &tell + 1 << endl;
由于,表达式tell + 1将地址加2,而表达式&tell + 1将地址加20
故,输出如下:
00F3FE54 // tell,第一个元素的起始地址
00F3FE54 // &tell,整个数组的起始地址
00F3FE56 // tell + 1,以元素为单位增加地址
00F3FE68 // &tell + 1,以数组为单位增加地址
使用括号手动改变结合顺序:
short (*pas)[20] = &tell; // pas point to array of 20 shorts
- 如果省略括号,pas将优先与[20]结合,导致pas是一个short指针数组。所以这里的括号是必不可少的。
如下例:
// addpntrs.cpp -- pointer addition
#include <iostream>
int main()
{
using namespace std;
short tell[10]; // tell an array of 20 bytes
cout << tell << endl; // displays &tell[0]
cout << &tell << endl; // displays address of whole array
short (*pas) [10] = &tell;
short* pas2[10] = &tell; // not allowed
short* pas2[10] = tell; // not allowed
short(*pas)[10] = tell; // not allowed
return 0;
}
- pas的类型为short (*) [20]
- pas被设置为&tell,因此*pas与tell等价
总之,使用new来创建数组以及使用指针来访问不同的元素很简单。只要把指针当作数组名对待即可。
4.8.2 指针小结
1. 指针声明
typeName * pointerName;
2. 指针赋值
// 方式一:对变量取地址
double * pn;
double bubble = 3.2;
pn = &bubble; // assign address of bubble to pm
// 方式二:使用new
pc = new char; // assign address of newly allocated char memory to pc
pa = new double[30] //assign address of 1st element of array of 30 double to pa
3. 对指针解除引用
对指针解除引用意味着获得指针指向的值。
- 对指针应用解除引用运算符(*)来解除引用,如下例:
cout << *pn;
*pc = 'S';
- 数组表示法,间接值运算符来解除引用,如下例:
pn[0]与*pn是一样的
Warning: 绝不要对未被初始化为适当地址的指针解除引用。
4. 区分指针和指针所指向的值
pt是int的指针,则*pt等同于int。
5. 数组名
- 在多数情况下,C++将数组名视为数组的第一个元素的地址,如下例:
int tacos[10]; // now tacos is the same as &tacos[0]
- 例外情况为,将sizeof运算符用于数组名时,此时将返回整个数组的长度(单位为字节)。
6. 指针算术
- C++允许将指针和整数相加。加1的结果等于原来的地址值加上指向的对象占用的总字节数。
- 还可以将一个指针减去另一个指针,获得两个指针的差。该运算得到一个整数,仅当两个指针指向同一个数组(可以超出结尾的一个位置)时,这种运算才有意义。这将得到两个元素的间隔。
例:
int tacos[10] = {5,2,8,4,1,2,2,4,6,8}
int * pt = tacos; // suppose pf and tacos are the address 3000
pt = pt + 1; // now pt is 3004 if a int is 4 bytes
int *pe = &tacos[9]; // pe is 3036 if an int is 4 bytes
pe = pe - 1; // now pe is 3032, the address of tacos[8]
int diff = pe - pt; // diff is 7, the separation between
// tacos[8] and tacos[1]
7. 数组的动态联编和静态联编
- 静态联编:使用数组声明来创建数组时,为,静态联编,即数组的长度在编译时设置。
- 动态联编:使用new[ ]运算符创建数组时,采用动态联编(动态数组),即在运行时为数组分配空间,其长度也将在运行时设置。使用完这种数组后,应使用delete [ ]释放其占用的内存。
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]; // pt points to block of 10 ints
*pt = 5; // set element number 0 to 5
pt[0] = 6; // reset element number 0 to 6
pt[9] = 44; // set tench element (element number 9) to 44
int coats[10];
*(coats + 4) = 12; // set coats[4] to 12
4.8.3 指针和字符串
数组和指针的特殊关系可以扩展到C-风格字符串。请看下面的代码:
char flower[10] = "rose";
cout << flower << "s are red\n";
- 如果给cout提供一个字符的地址,则它将从该字符开始打印,直到遇到空字符为止。
- 可以将指向char的指针变量作为cout的参数,因为它也是char的地址。
- 在C++中,用引号括起的字符串像数组名一样,也是第一个元素的地址。
- 在cout和多数C++表达式中,char数组名、char指针以及用引号括起的字符串常量都被解释为字符串第一个字符的地址。
- 不同编译器实现可能不同,如不同代码位置的字符串常量是否在同一内存地址中。
- 可以确定的是const修饰的字符串常量,没法被修改。
- 请不要使用字符串常量或未被初始化的指针来接收输入。为避免这些问题,可以使用std::string对象,而不是数组。
- 在将字符串读入程序时,应使用已分配的内存地址。该地址可以是数组名,也可以是使用new初始化过的指针。
- cout对于char*的特殊处理,见下例:
ps = animal;
...
cout << animal << " at " << (int *) animal << enld;
cout << ps << " at " << (int *) ps << endl;
输出如下:
fox at 0x0065fd30
fox at 0x0065fd30
解释:如果给cout提供一个指针,它将打印地址。但是如果指针的类型为char *,则cout将显示指向的字符串。
- 如果要显示的是字符串的地址,则必须将这种指针强制转换为另一种指针类型,如int*(上述代码就是这么干的)。
获得字符串的副本
-
分配内存来存储字符串:声明一个数组或使用new来完成,如:
ps = new char[strlen(animal) + 1]; // get new storage
获取字符串长度后,加1是为了给空字符留出一个位置。
-
将animal数组中的字符串复制到新分配的空间中。
strcpy(ps, animal); // copy string to new storage
注意,将animal赋给ps,只会修改存储在ps中的地址,从而失去访问新分配内存的唯一途径。
防止字符串超长复制的方法
strcpy(food, “banana”)方法在字符串过长时会导致覆盖其他内存的内容。char food[10] = "0123456789"; strncpy(food, "banana-balabala", 9); // 该方法可以限制最大复制的字符个数。 food[9] = '\0'; // 如果过长则要手动加空字符,否则它将自动添加空字符
4.8.4 使用new创建动态结构
在运行时为结构分配所需的空间。
将new用于结构由两步组成:创建结构和访问其成员。
创建:
inflatable * ps = new inflatable;
访问:箭头成员运算符(->)
用于指向结构的指针。就像点运算符可用于结构名一样。如下例:
inflatable * ps = new inflatable;
ps -> price // 被指向的结构的price成员。
// 另一种访问方法
(*ps).price
new创建的结构删除方法:
inflatable * ps = new inflatable;
delete ps;
4.8.5 自动存储、静态存储和动态存储
- 自动存储
- 在函数内部定义的常规变量使用自动存储空间,被称为自动变量(automatic variable)。
- 所属的函数被调用时自动产生,在函数结束时消亡。
- 自动变量通常存储在栈中。这意味着执行代码时,其中的变量将依次加入到栈中,而在离开代码块时,将按相反的顺序释放这些变量。即后进先出(LIFO)。
- 静态存储
整个程序执行期间都存在的存储方式。
使变量成为静态的两种方式:1. 在函数外定义它。2. 在声明变量时使用关键字static。 - 动态存储(自由存储空间,堆)
free store,heap。该内存池同用于静态变量和自动变量的内存是分开的。在栈中,占用的内存是连续的,但new和delete的方式,使得自由存储区不连续,跟踪新分配的内存更困难。
类型组合
数组、结构和指针各种方式组合它们。
数组的替代品
模板类vector和array是数组的替代品。
4.10.1 模板类vector
模板类vector类似于string类,也是一种动态数组。可在运行阶段设置vector对象的长度,可在末尾附加新数据,还可在中间插入新数据。基本上,它是使用new创建动态数组的替代品。自动完成new和delete来管理内存。
声明:
#include <vector>
int main()
{
using namespace std;
int n;
cin >> n;
vector<double> vd(n);
}
// 声明模板:
vector<typeName> vt(n_elem);
// n_elem可以是整型常量,也可以是整型变量。
4.10.2 模板类array(c++11)
长度固定,比数组方便、安全,比vector效率高。
创建:
#include <array>
...
using namespace std;
array<int, 5> ai;
array<double, 4> ad = {1.2, 2.1, 3.43, 4.3};
// 声明模板
array<typeName, n_elem> arr;
与创建vector不同,n_elem不能是变量。
在C++11中,可将列表初始化 ({ })用于vector和array对象。但在98中的vector不可以。
比较数组、vector对象和array对象
- array对象和数组存储在相同的内存区域(即栈)中,而vector对象存储在另一个区域(自由存储区或堆)中。
- 可以将array对象赋给另一个array对象,但对于数组,必须逐元素复制数据。
- 为防止数组越界操作,可以使用vector和array对象的成员函数at()。代价是运行更慢。还可以用它们包含的成员函数begin()和end()来确定边界。
总结
数组、结构和指针是C++的3种复合类型。
数组:存储多个同类型的值。
结构:存储多个不同类型的值。
共用体:存储一个值,类型为可选类型的某一种。
指针:存储地址的变量。声明时指出指向的对象的类型。对指针应用解除引用运算符,将得到指针指向的位置中的值。
字符串:以空字符结尾的一系列字符。strlen()返回长度,strcpy()复制字符串。
string:一种更友好的字符串处理方法。
new运算符:在运行时分配内存。返回的是内存地址。可以用(*)和(->)访问值。
指针和数组紧密相关。
自动变量:函数内的变量。
静态变量:函数外的变量或static声明的变量。
new和delete显式控制内存分配、释放。
模板类vector:动态数组替代品。
模板类array:定长数组替代品。
复习题
编程练习
#include<iostream>
#include<array>
#include<cstring>
#include<vector>
int main()
{
using namespace std;
//数组
char actor[30];
short betsie[100];
float chuck[13];
long double dipsea[64];
// 模板类
array<char, 30> actors;
int a_i[5] = { 1,3,5,7,9 };
float even = chuck[0] + chuck[12];
cout << even << endl;
char ac[13] = "cheeseburger";
// strlen("cheeseburger")+1
cout << strlen(ac) << endl;
string s = "Waldorf Salad";
cout << s << endl;
struct fish
{
char cls[20];
int weight;
float lenght;
};
fish crop = { "golden_crop", 4, 5.6 };
cout << crop.cls << endl;
enum Response
{
Yes = 1, No=0, Maybe=2
};
Response res = Yes;
cout << res << endl;
double ted = 1.2;
double* p_d = &ted;
cout << *p_d << endl;
float treacle[10] = {1,2,3,4,5,6,7,8,9,10};
float(*pt)[10] = &treacle;
//float(*pt)[10] = &treacle;
cout << (*pt)[0] << " " << (*pt)[9] << endl;
int n;
cin >> n;
int * a_f = new int[n];
cout << a_f[0] << endl;
vector<int> a_v(n);
cout << a_v[0] << endl;
fish * p_sliver_fish = new fish;
//*p_sliver_fish->cls = "Sliver";
char sliver_cls[20] = "init_sliver";
cin >> sliver_cls;
//strcpy(p_sliver_fish->cls, sliver_cls);
strcpy_s(p_sliver_fish->cls, sliver_cls); // safe
p_sliver_fish->lenght = 1.6;
p_sliver_fish->weight = 3;
cout << "* p_sliver_fish" << endl;
cout << p_sliver_fish->cls << endl;
cout << p_sliver_fish->lenght << endl;
cout << p_sliver_fish->weight << endl;
//(*p_sliver_fish)->cls;
const int num_s = 10;
std::vector<string> vs[num_s];
std::array<string, num_s> as;
return 0;
}