第04章_复合类型
<c++ primer plus>第六版
4 复合类型
4.1 数组
声明数组的格式:
typeName arrayName[arraySize]; //声明一个数组, 内容未定;
typeName arrayName[arraySize] = {20, 30}; //声明一个数组, 并初始化, 初始化的数目可以小于等于arraySize, 未声明的元素初始化为0.
typeName arrayName[] = {1, 2, 3}; //让编译器自动计算数组大小.
//ok with c++11
double a0[4] {1.2e4, 1.6e4, 1.1e4, 1.7e4}; //省略等号
double a1[4] {}; //大括号内为空, 所有元素初始化为0
其中arraySize, 只能是整型常数, const值, 或常量表达式, 不能是变量(程序运行时设置的值). 使用new运算符可以避开些限制.
例:
float loans[20];
数组长度: sizeof(arrayName)/sizeof(typeName)
4.2 字符串
C-风格字符串:
char dog[8] = {'f', 'a', 't', 'e', 's', 's', 'a', '\0'}; // a string, 必须以空字符'\0'结尾.
char fish[] = "Bubbles"; //字符串常量, 隐式地包括结尾的空字符'\0'.
拼接字符串常量
cout << "A colorful" " day.\n"; // 由空白分隔的两个字符串合并, 拼接时不会添加空格.
cout << "A colorful"
" day.\n" // 换行符也可以作为拼接空白符.
#include <iostream>
#include <cstring> //包含strlen()函数
int main(){
using namespace std;
char name0[] = "HanMeimei"; //name0包含9个字面字符
char name1[5];
cout << "name0 is " << name0 << endl; //字符串常量可以直接写在cout中.
cout << "name0 length is " << strlen(name0) << endl; //9, 获取字符串长度, 字面长度, 不包括结尾的'\0'.
cout << "name0 size is " << sizeof(name0) << endl; //10, 获取字符串字节数, 包括结尾的'\0'.
name0[3] = '\0'; // 字符串中间设置为'\0', 将强行截取字符串
cout << "name0 is " << name0 << endl; // Han, 之后的部分被截掉了.
cout << "name0 length is " << strlen(name0) << endl; //3, 获取字符串长度, 看到'\0'即止.
cout << "name0 size is " << sizeof(name0) << endl; //10, 数组仍然占10bit.
cout << "Enter name1: ";
cin >> name1; //从cin读入到字符串常量.
cout << "name1 is " << name1 << endl;
}
按行读入
cin.getline(name, 20) // 把一行读入到name数组中(需要这行不超过19个字符).
4.3 string类
存储字符串, 不再使用字符数组, 而是使用string类的对象, string类使用比较简单, 隐藏了字符串的数组性质.
string str1 = "panther";
string str2 = "123";
str2 = str1;
#include <iostream>
#include <string>
int main(){
using namespace std;
string str1 = "Hello";
string str2 = " World.";
string str3 = "tmp";
str3 = str1 + str2; //允许使用+来连接字符串, 允许把一个字符串赋值给另一个字符串.
cout << str1 << str2 << endl;
cout << str3 << endl;
cout << str3.size() << endl; //获取string的长度
}
wchar_t title[] = L"Chief Astrogator"; // w_char string
char16_t name[] = u"Felonia Ripova"; // char_16 string
char32_t car[] = U"Humber Super Snipe"; // char_32 string
4.4 结构体
struct inflatable{
char name[20];
float volume;
double price;
};
结构体数组
inflatable gifts[100]; // gifts本身是一个数组.
4.6 枚举
enum spectrum {red, orange, yellow, blue, violet, indigo, ultraviolet};
spectrum band; //枚举变量
band = blue; //对变量赋值
cout << blue << endl; // 3, blue对应的值是3
4.7 指针和自由存储空间
计算机存储数据时要跟踪3个属性:
- 存储地址
- 存储值
- 数据类型
&: 地址运算符, 在变量前加上&可以获得它的地址;
*: 解除引用运算符, 将其应用于指针, 可以得到该指针指向的地址处存储的值.
声明指针: type * var;
*两边的空格是可选的
int ptr; //强调 ptr是个int类型的指针;
int ptr; //强调 int 是一种类型: 指向int的指针.
int * p1, p2; //p1是指针, p2是int变量. 所以感觉还是 int *p1, p2比较符合直观.
初始化指针:
int higgens = 5;
int* pt = &higgens; //将指针pt初始化为higgens的地址, 而不是给*pt(pt指向的内容)赋值.
long* fellow;
*fellow = 223322; //危险, fellow是个指针, 但不知道它指向哪里.
pt = 0xB8000000; //类型错误, 计算机并不知道0xB8000000是个地址.
pt = (int *) 0xB8000000; //正确, 告诉编译器0xB8000000是个地址.
4.7.4 使用new分配内存
指针可以在运行阶段分配未命名的内存以存储值:
typeName * pointer_name = new typeName;
int* pn = new int; //为一个int值分配未命名的地址. new运算符是c++的运算符.
//内存块的差别
new: 从堆(heap)或自由存储区(free store)的内存区域分配内存.
常规变量声明: 从栈(stack)的内存区域分配内存.
4.7.5 使用delete释放内存
int* ps = new int;
delete ps; //delete后要加上指针. 释放ps指向的内存, 但不会删除指针ps本身, ps可以再指向新分配的内存.
注意:
- 要配对使用new和delete, 否则将发生内存泄漏(memory leak);
- 不能释放已经释放的内存块, 这样结果不确定;
- 不能用delete来释放声明变量所获得的内存;
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.
4.7.6 使用new来创建动态数组
通过声明创建数组, 在程序编译时就会为他分配内存空间, 不管最终是否使用数组, 它都会占用内存. 称为静态联编.
通过new创建数组, 则在编译时不分配内存空间, 运行时, 如果需要数组则创建, 如果不需要数组则不创建, 且可以在程序运行时选择数组长度. 称为动态联编. 这种数组称为动态数组.
int * psome = new int[10]; //创建一个指针, 指向第一个元素的地址.
delete [] psome; //需要使用delete释放, 方括号表示要释放整个数组, 而不仅仅是指针指向的元素.
创建动态数组的语法:
type_name * pointer_name = new type_name [num_element];
delete [] pointer_name;
使用动态数组:
int * psome = new int [10]; //创建
#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[0]=" << p3[0] << endl;
cout << "p3[1]=" << p3[1] << endl;
p3 += 1; //指针增加(对数组名不能做加法), 增加量为它指向类型的字节数.
cout << "p3[0]=" << p3[0] << endl; //指向原本的[1]
cout << "p3[1]=" << p3[1] << endl; //指向原本的[2]
delete [] p3; //释放内存
return 0;
}
#include<iostream>
int main(){
using namespace std;
double wages[3] = {10.0, 20.0, 30.0};
short stacks[3] = {3, 2, 1};
double * pw = wages; //指向数组的指针, 数组名解释为数组第一个元素的地址(wages=&wages[0]).
short * ps = &stacks[0]; //数组的第0元素的地址, 也是指向数组的指针.
cout << " pw=" << pw << endl;
cout << "*pw=" << *pw << endl;
cout << " ps=" << ps << endl;
cout << "*ps=" << *ps << endl;
pw += 1;
ps += 1;
cout << " pw=" << pw << endl;
cout << "*pw=" << *pw << endl;
cout << " ps=" << ps << endl;
cout << "*ps=" << *ps << endl;
cout << "stacks[0]=" << stacks[0] << endl;
cout << "stacks[1]=" << stacks[1] << endl;
cout << "*stacks =" << *stacks << endl;
cout << "*(stacks+1)=" << *(stacks+1) << endl; //等价于stacks[1], c++会将stacks[1]转换为*(stacks+1)
cout << "sizeof(wages)=" << sizeof(wages) << endl; //数组的长度(字节数)
cout << "sizeof(pw )=" << sizeof(pw) << endl; //指针的长度.
}
//声明指针
double * pn;
char * pc;
//给指针赋值
double * pn;
double * pa;
char * pc;
double bubble = 3.2;
pn = &bubble; //将地址赋值给指针.
pc = new char; //把一个通过new分配的char的地址赋值给pc.
pa = new double[30]; //30个元素的array, 把第一个地址赋值给pa.
//对指针解除引用(获取其指向的值)
cout << *pn << endl; //打印指针指向的值
*pc = 'S'; //更改指针指向内存的内容
//数组名
int tacos[10]; // 数组名即第一个元素的地址: &tacos[0], 只有一个例外, 见下一行.
sizeof(tacos); // sizeof(数组名), 返回的是整个数组的长度, 而不是第一个元素的长度.
//指针算术(加, 减)
int tacos[10] = {5,2,8,4,1,2,2,4,6,8};
int * pt = tacos;
pt = pt + 1;
int * pe = &tacos[9];
int diff = pe - pt;
//数组的动态联编和动态联编
int tacos[10]; //通过声明来创建的数组, 采用静态联编, 数组长度在编译时设置;
int size;
int * pz = new int[size]; //通过new创建数组, 采用动态联编, size可以在运行时设置
...
delete [] pz; //使用new创建的数组需要delete.
//数组表示法和指针表示法
//方括号数组表示法等同于对指针解除引用.
tacos[0]; // *tacos
tacos[3]; // *(tacos+3);
int * pt = new int[10];
*pt = 5 // 第0个元素设置为5
pt[0] = 6; //第0个元素设置为6
pt[9] = 44; //第9个元素设置为6
int coats[10];
*(coats+4) = 12 // 设置costs[4]为12;
//指针与字符串
4.8.4 使用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: ";
cin.get(ps->name, 20);
cout << "Enter volume : ";
cin >> (*ps).volume;
cout << "Enter price : ";
cin >> ps -> price;
cout << endl;
cout << "Name :" << (*ps).name << endl; // 使用 (*指针).成员 的方式访问
cout << "Volume:" << ps -> volume << endl; // 使用 指针->成员 的方式访问
cout << "Price :" << ps -> price << endl;
delete ps;
return 0;
}
4.8.5 自动存储, 静态存储, 动态存储
-
自动存储(栈):
在函数内部定义的常规变量使用自动存储空间, 称为自动变量, 函数结束时消亡.
如果函数需要返回复合类型时, 需要使用new创建的指针, 否则函数结束后返回值也没了.
自动变量是局部变量, 作用域为包含它的代码块.
自动变量通常存储在栈中. 先进后出LIFO. 程序执行过程中栈将不断变大和缩小. -
静态存储:
在整个程序执行期间都存在的存储方式.
变量成为静态有两种方式: 1) 在函数外定义它; 2) 声明变量时使用static;
与自动存储的区别主要在于变量的寿命. -
动态存储(自由存储区/堆):
new和delete运算符提供了比自动变量和动态变量更灵活的方法.
new和delete管理了一个内存池, 在c++中称为自由存储空间(free store)或堆(heap).
new和delete允许在一个函数中分配内存, 而在另一个函数中释放它. 从而数据的生命周期不完全受程序或函数的寿命控制.
new和delete让程序员对程序如何使用内存有更大的控制权(内存管理也更复杂了).
new和delete相互作用, 使栈中的内存占用不连续.
4.9 类型组合
struct antarctical_years_end
{
int year;
}
antarctical_years_end s01, s02, s03;
s01.year = 1998; //使用成员运算符
antarctical_years_end * pa = &s2; //指针
pa->year = 1999; //使用间接成员运算符
antarctical_years_end trio[3]; //结构数组
trio[0].year = 2003; //trio[0]是结构, 而不是指针.
(trio+1)->year = 2004; //数组名是指针, 等价于trio[1].year
const antarctical_years_end * arp[3] = {&s01, &s02, &s03};//指针数组, 元素都是指针
cout << arp[1]->year; //arp[1]是个指针, 所以使用间接成员运算符.
const antarctical_years_end **ppa = arp; // 指向上述数组的指针, 写法易出错;
auto ppb = arp; //使用automatic type deduction
cout << (*ppa)->year; //ppa是指向结构指针的指针, *ppa是一个结构指针, 可以使用间接成员运算符.
cout << (*(ppb+1))->year;
4.10 数组的替代品
模板类vector和array是数组的替代品;
4.10.1 模板类vector
vector类似于string类, 也是一种动态数组.
可以在运行阶段设置vector对象长度, 可以在末尾附加新数据, 可以在中间插入新数据.
vector类使用new和delete来处理内存, 但这种工作是自动完成的.
语法:
#include<vector>
using namespace std;
vector<typeName> vt(n_elem); //n_elem可以是整型常量, 也可以是整型变量.
4.10.2 模板类array(c++11)
vector类功能比array强大, 但效率较低.
array的长度(与数组一样)是固定的, 使用栈(静态内存分配), 不在自由存储区.
array与数组的效率相同, 差别是array更方便, 更安全.
语法:
#include<array>
using namespace std;
array<typeName, n_ele> arr; //n_elem不能是变量.
4.10.3 比较: 数组, vector对象, array对象
- 都可以使用标准数组的表示法来访问: arr[idx];
- 数组和array对象存储在栈中, vector对象存储在堆(自由存储区)中;
- 可以把一个array赋值给另一个array, 但数组必须逐元素复制.
- 三者索引号都可以越界, 比如a1[-2], 表示*(a1-2), 会将地址指到数组外面, 但c++不检查这种越界错误.
- vector和array对象解决越界方法是, 使用成员函数at(), a1.at(1), 程序运行时会捕获非法索引, 从而默认将程序中断, 代价是运行时间长.
- vector和array对象还包含成员函数begin()和end(), 用于确定边界, 以免无意间越界.
4.11 总结
- 数组, 结构, 指针, 是c++的3种复合类型.
- 数组: 在一个数据对象中存储多个同种类型的值, 使用索引或下标可以访问数组各元素.
- 结构: 在一个数据对象中存储多个不同类型的值, 使用成员关系运算符(.)访问成员.
- 共用体: 可以存储一个值, 但这个值可以是不同类型.
- 指针: 一个变量, 用来存储地址. 对指针使用"解除引用运算符", 将得到指向的位置中的值.
- 字符串: 以空字符结尾的一系列字符. string支持使用赋值运算符来复制字符串(char数组需要使用strcpy())
- new运算符: 程序运行时为数据对象请求内存, new返回获得的内存地址, 可以将地址赋值给一个指针.
- 模板类vector是动态数组的替代品.
- 模板类array是定长数组的替代品.