C++ Primer Plus 学习——第四章

复合类型

基于基本整型和浮点类型创建

4.1

数组
一种数据格式,存储多个同类型的值

声明满足以下三点:

  1. 存储在每个元素中值的类型
  2. 数组名
  3. 元素个数

如:

short months[12];// 此种方法的数组长度为常量,无法修改

使用下标或索引进行访问每个元素【从0开始,注意越界问题】

代码4-1

// arrayone.cpp -- small arrays of integers
#include <iostream>
using namespace std;

int main()
{
    int yams[3];
    yams[0] = 7;
    yams[1] = 8;
    yams[2] = 6;

    int yamcosts[3] = {20, 30, 5};

    cout  << "Total yams = " << yams[0] + yams[1] + yams[2] << endl;
    cout << "The package with " << yams[1] << " yams costs " << yamcosts[1] << " Cents per yam." << endl;

    int total = yams[0] * yamcosts[0] + yams[1] * yamcosts[1] + yams[2] * yamcosts[2];
    
    cout << "The total yam expense is " << total << " cents." << endl;
    cout << "\nSize of yams array = " << sizeof(yams) << " bytes.\n" << endl;
    cout << "Size of one element = " << sizeof(yams[0]) << " bytes.\n" << endl;
    return 0;

}

数组初始化规则

    int cards[4] = {1, 2, 3, 4};// 可以
    int cards[4] = {1, 2};// 可以,其他默认为0
    int hands[4];// 可以
    hands[4] = {5, 6, 7, 8};// 不可以,相当于对第五个元素赋值,但只有4个元素,其次,赋初值过多
    hands = cards;// 不可以,不能整体修改,只能分别对元素修改
    int cards[] = {1, 2, 3, 4};// 可以,编译器自动计算个数,不建议
	int cards[4] {1, 2, 3, 4};// 可以,C++11,列表初始化【要注意缩窄转换问题(第三章)】
	int cards[4] {};// 可以,全部为0

C++标准模板库STL提供了一种数组替代品:模板类 vector
C++11新增模板类 array

4.2

字符串
C++处理字符串方法:

  1. C-风格字符串:以空字符【‘\0’,对于ASCII码为0】结尾,用来标记字符串结尾,并不会真正输出出来,但确确实实占用了内存空间
char dog[5] = {'b', 'a', 'x', '', 'i'};// 不是字符串
char cat[5] = {'d', 'a', 'x', 'z', '\0'};// 是字符串,cout处理到'\0'时自动停止
cout << dog << endl;// 会将内存中随后的各字节内容当作dog数组中内容输出,造成输出与数组内容不一致
cout << cat << endl;

将字符数组更好的初始化为字符串方法:
字符串常量/字符串字面值:

char bird[11] = "Mr.Cheeps";// 隐式包含结尾空字符
char fish[] = "Bubbles";

C++输入工具通过键盘输入,将字符串读入char中时自动加上结尾空字符
!!!在确定存储字符串的最小数组时需计算结尾的空字符!!!

‘S’ 与 “S” 不同:前者为字符常量,后者为字符串数组,含有’\0’结束符

  1. 基于string类库

在数组中使用字符串

  1. 将数组初始化为字符串常量
  2. 将键盘或文件输入读入数组中

代码4-2

// strings.cpp -- storing strings in an array
#include <iostream>
#include <cstring>
using namespace std;

int main()
{
    const int Size = 15;
    char name1[Size];
    char name2[Size] = "C++owboy";

    cout << "Howdy! I am " << name2  << " ! What is your name?"<< endl;
    cin >> name1;
    cout << "Well, " << name1 << ", your name has " << strlen(name1) << " letters and is stored." << endl;
    cout << "in an array of " << sizeof(name1) << " bytes." << endl;
    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;
}

字符串输入
代码4-2中存在缺陷,通过精心选择输入被掩盖掉了,下面代码将展现:
代码4-3

// instr1.cpp -- reading more than one string
#include <iostream>
using namespace std;

int main()
{
    const int ArSize = 20;
    char name[ArSize];
    char dessert[ArSize];

    cout << "Enter your name: ";
    cin >> name;// 测试数据输入GG Bond
    cout << "Enter your favorite dessert: ";
    cin >> dessert;
    cout << "\nI have some delicious " << dessert << " for you, " << name << endl;
    return 0;
}

GG Bond通过插入符进入输入流中,GG Bond中的空格符被当作结束符,因此GG被作为第一个输入,Bond被作为第二个输入
cin 方法使用空白(空格、制表符、换行符)作为字符串的结束位置,遇到空白即完成当前读取操作,并自动在结尾添加空字符

每次读取一行字符串输入
对于GG Bond这类数据,可以采用istream中其他方法:
都是面向行的方法:在输入流中读取到换行符截止

cin.getline(name, 20);// 丢弃换行符
// name:存储输入行的数组名称
// 20:读取的字符数,但始终在最后保留1位添加空白符,所以此时最多读取19个字符
cin.get();// 保留换行符

代码4-4

// instr2.cpp -- reading more than one word with getline()
#include <iostream>
using namespace std;

int main()
{
    const int ArSize = 20;
    char name[ArSize];
    char dessert[ArSize];

    cout << "Enter your name: ";
    cin.getline(name, ArSize);// 测试数据输入GG Bond
    cout << "Enter your favorite dessert: ";
    cin.getline(dessert, ArSize);
    cout << "\nI have some delicious " << dessert << " for you, " << name << endl;
    return 0;
}

代码4-5

// instr3.cpp -- reading more than one word with get()
#include <iostream>
using namespace std;

int main()
{
    const int ArSize = 20;
    char name[ArSize];
    char dessert[ArSize];

    cout << "Enter your name: ";
    cin.get(name, ArSize);// 测试数据输入GG Bond
    // 由于保留换行符,输入流中存在一个换行符
    // 如果没有下方这一行语句,那么cin.get(dessert, ArSize);
    // 会由于接收到换行符认为输入结束,导致未读取到相应内容
    cin.get();// 消除结尾换行符的影响,可以注释这条语句对比差异

    // cin.get(name, ArSize).get();// 此外,也可采用此种写法
	// 是因为cin.get()方法返回一个cin对象,又可以继续调用此类对象的内部方法【cin.getline()同理】
	
    cout << "Enter your favorite dessert: ";
    cin.get(dessert, ArSize);
    cout << "\nI have some delicious " << dessert << " for you, " << name << endl;
    return 0;
}

空行和其他问题:
当getline()和get()读取空行:
最初做法:下一条输入语句将在前一条getline()或get()结束读取的位置开始读取
当前做法:当get()读取空行后设置失效位,接下来的输入将被阻断,可以使用cin.clear();恢复输入

其他问题:
输入字符串比分配空间长,若getline()或get()读取已满,但还有余下的字符在输入队列中,而getline()会设置失效位,关闭后面的输入

混合输入字符串和数字
代码4-6

// numstr.cpp -- following number input with lineinput
#include <iostream>
using namespace std;

int main()
{
    cout << "What year was your house built?: ";
    int year;

    cin >> year;
    //cin.get();// 消除换行符

    // (cin >> year).get();// 或者拼接调用
    cout << "What is its street address?: ";
    char address[80];
    
    cin.getline(address, 80);
    cout << "\nYear built: " << year << endl;
    cout << "Street address: " << address << endl;
    cout << "Done!" << endl;
    return 0;
}

问题:cin >> year;产生的换行符留在输入队列中,cin.getline(address, 80);读取到换行符截止
采取注释中解决方法

4.3

string类简介
string类定义隐藏了字符串的数组性质,可以如同普通变量一样处理字符串
代码4-7

// strtype1.cpp -- using the C++ string class
#include <iostream>
#include <string>
using namespace std;

int main()
{
    char charr1[20];
    char charr2[20] = "jaguar";
    string str1;
    string str2 = "panther";

    cout << "Enter a kind of feline: ";
    cin >> charr1;
    cout << "Enter another kind 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对象与字符数组主要区别:string对象可声明为简单变量,而不是数组
类设计使其可以自动处理string大小

C++11字符串初始化

char first_date[] = {"Le Chapon Dodu"};
char second_date[] {"Le Chapon Dodu"};
string third_date = {"Le Chapon Dodu"};
string fourth_date {"Le Chapon Dodu"};

赋值、拼接和附加

char charr1[20];
char charr2[20] = "jasdsd";
// 对于以上两个char 类型数组,无法相互间赋值,即:
// charr1 = charr2;// 为非法语句,而两个string可以相互赋值,即:
//str1 = str2;
//string可以直接拼接
string str3 = str1 + str2;
// 或附加
str1 = str1 + str2;

代码4-8

// strtype2.cpp -- assigning, adding, and appending
#include <iostream>
#include <string>
using namespace std;

int main()
{
    string s1 = "penguin";
    string s2,s3;

    cout <<"Your can assign one string object to another: s2 = s1 " << endl;
    s2 = s1;

    cout << "s1: " << s1 << endl;
    cout << "s2: " << s2 << endl;
    cout << "You can assign a C-style string to a string object." << endl;
    cout << "s2 = \"buzzard\"" << endl;
    s2 = "buzzard";

    cout << "s2: " << s2 << endl;
    cout << "You can concatenate strings: s3 = s1 + s2 " << endl;
    s3 = s1 + s2;

    cout << "s3: " << s3 << endl; 
    cout << "You can append strings." << endl;
    s1 += s2;

    cout << "s1 + s2 yields s1 = " << s1 << endl;
    s2 += " for a day";

    cout << "s2 + \" for a day\" yields s1 = " << s2 << endl;

    return 0;
}

string类其他操作

// 头文件cstring中
strcpy(charr1, charr2);// 将charr2内容复制给charr1
strcat(charr1, charr2);// 将charr2内容追加到charr1后面

代码4-9

// strtype3.cpp -- more string class features
#include <string>
#include <iostream>
#include <cstring>
using namespace std;

int main()
{
    char charr1[20];
    char charr2[20] = "jaguar";
    string str1;
    string str2 = "panther";

    str1 = str2;
    strcpy(charr1, charr2);

    str1 += " paste";
    strcat(charr1, " juice");

    int len1 = str1.size();
    int len2 = strlen(charr1);

    cout << "The string " << str1 << " contains " << len1 << " characters." << endl;
    cout << "The string " << str2 << " contains " << len2 << " characters." << endl;

    return 0;
}

!!!要注意到字符数组存在所分配空间小于所需存储的指定信息大小的危险!!!
可以使用

strncpy(charr1, charr2, count);// count为目标数组最大允许长度
strncat(charr1, charr2,count);

string类 I/O
对于单词:都是使用cin cout处理,与C语言风格相同
对于行:句法不同,如下
代码4-10

// strtype4.cpp -- line input
#include <iostream>
#include <string>
#include <cstring>
using namespace std;

int main()
{
    char charr[20];
    string str;

    cout << "Length of string in charr before input is " << strlen(charr) << endl;// [这个数字是随机的,因为未初始化的数据第一个空字符的出现位置是随机的]
    cout << "Length of string in str before input is " << str.size() << endl;// 未被初始化的string对象长度被自动设置为0
    cout << "Enter a line of text: " << endl;
    cin.getline(charr, 20);// 调用istream类的getline成员函数,从cin读取最多19个字符到charr数组
    cout << "You entered: " << endl;
    cout << charr << endl;
    cout << "Enter another line of text: " << endl;
    getline(cin, str);// // 调用全局的getline函数,从cin读取一行文本到str字符串,string对象无长度限制
    cout << "You entered: " << endl;
    cout << str << endl;
    cout << "Length of string in charr after input is " << strlen(charr) << endl;
    cout << "Length of string in str after input is " << str.size() << endl;

    return 0;
}

getline函数有两种常见的用法:
一种是作为istream类的成员函数(如cin.getline):需要一个字符数组(或指针)和一个整数作为参数,整数表示要读取的最大字符数(不包括终止的空字符\0)

另一种是作为全局函数:接受两个参数:一个istream对象引用和一个string对象引用

要注意istream类中并无处理string对象的方法,对string对象的处理使用string类的友元函数

其他形式字符串常量

wchar_t title[] = L"GG Bond";
char16_t name[] = u"GG Bond";
char32_t car[] = U"GG Bond";
//对于Unicode字符编码,使用前缀 u8

C++11新增原始字符串,即所有字符仅表示自身,无其他含义:

cout << R"(Jim "King" Tutt uses "\n" instead of endl.)" << '\n';

输入原始字符串,回车键不仅会移入下一行,还会在原始字符串中添加回车字符
若原始字符串中出现 )" 符号编译器会认为字符串到此结束
但是起始于结束标志可以自行更改(即在 " ( 以及 ) " 之间添加新标志【除空格、左右括号、斜控制字符外】):

cout << R"+*("(Jim "King" Tutt)" uses "\n" instead of endl.)+*" << '\n';

可与其他前缀结合使用:Ru, UR等

4.4

结构体
结构:用户自定义类型,由用户设计满足所需的数据结构【C++允许声明结构变量时省略关键字 struct】
如:

struct inflatable// 使用方法上相当于int float这些类型,只不过为自定义类型,其对数据的容纳更加灵活,
//在C++中是允许省略的,将会生成一个没有名称的结构类型,不建议使用
{
	char name[20];
	float volume;
	double price;
}/* pal1, pal2 = {"GG Bond", 3.12, 322.99}, pal3;
// 甚至可以直接在后面创建结构变量,以及完成赋值, 但不建议这么做*/;

inflatable hat;// C++
inflatable test[2] = 
{
	{"GG Bond", 3.12, 322.99},
	{"JJ Bond", 3.12, 322.99}
};// 自定义类型结构数组,操作与数组类似,单独对每一个元素修改
// 理解:test[2]本身是一个数组,但是每个元素的类型结构为自定义类型结构
struct inflatable goose;// C 
//使用 hat.name, hat.volume进行访问结构成员

代码4-11

// structur.cpp -- a simple structure
#include <iostream>
//#include <string>
using namespace std;

struct inflatable
{
    char name[20];
    //string name;// 可以使用string对象
	float volume;
	double price;
};

int main()
{
    inflatable guest = 
    {
        "Glorious Goria",
        1.88,
        299.99
    };
    inflatable pal = {"GG Bond", 3.12, 322.99};

    cout << "Expand your guest list with " << guest.name << " and " << pal.name << "!\n";
    cout << "You can have both for $" << guest.price + pal.price << "!\n";
    
    return 0;
}

结构声明位置很重要:

  1. 放在main()函数中最开始位置,也可以是其他函数【类似于局部变量,属于内部声明,无法在函数外部使用】
  2. 放在main()前面【类似于全局变量,属于外部声明,可以被后面任何函数使用】

C++11结构初始化

inflatable pal {"GG Bond", 3.12, 322.99};
inflatable pal {};

!!!不允许缩窄转换!!!

其他结构属性
成员赋值:使用 “=” 将结构赋给另一个同类型结构,自身的成员值也被赋给另一个结构
代码4-12

// assign_st.cpp -- assigning structures
#include <iostream>
#include <string>
using namespace std;

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

int main()
{
    inflatable bouquet = {"sunflowers", 0.20, 12.49};
    inflatable choice;

    cout << "bouquet: " << bouquet.name << endl;
    cout << "price: $" << bouquet.price << endl;

    choice = bouquet;

    cout << "choice: " << choice.name << endl;
    cout << "price: $" << choice.price << endl;

    return 0;
}

与C结构不同,C++结构不仅支持成员变量,还有成员函数【一般用于类中】

代码4-13

// arrstruc.cpp -- an array of structures
#include <iostream>
#include <string>
using namespace std;

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

int main()
{
    inflatable guests[2] = 
    {
	    {"GG Bond", 3.12, 322.99},
	    {"JJ Bond", 3.12, 322.99}
    };

    cout << "The guests " << guests[0].name << " and " << guests[1].name << endl;
    cout << "have a combined volume of " << guests[0].volume + guests[1].volume << " cubic feets." << endl;
    
    return 0;
}

结构中的位字段
与C语言一样,C++允许指定占用特定位数的结构成员,使得创建与某个硬件设备的寄存器对应数据结构非常方便
字段类型应为 整型或枚举 : 数字【指定使用的位数】
每个成员称为位字段

struct torgle_register
{
	unsigned int SN : 4;
	unsigned int : 4;// 未命名,不使用,起占位作用,方便后续扩展
	bool goodIn : 1;
	bool goodTorgle : 1;
}

torgle_register tr = {14, true, false};// 初始化,可以使用 tr.goodIn 访问位字段

4.5

共用体
其为一种数据格式,可以存储不同类型数据,但只能同时存储为其中一种,长度为最大成员长度,
与结构体类似,存在区别,适用于某个数据存在不同类型存储需求,
常用于节省内存【在嵌入式中更明显】、操作系统数据结构、硬件数据结构
如:

union one4all
{
	int int_val;
	long long_val;
	double double_val;
};

// 对于其中的变量每次只能使用一种
one4all pail;
pail.int_val = 5;// 存储一个int类型数据
cout << pail.int_val << endl;
pail.double_val = 1.50;// 存储一个double类型数据,此时int类型数据丢失
cout << pail.double_val << endl;// 正常输出
cout << pail.int_val << endl;// 无法正常输出/输出值不对应

长度检验代码

    cout << sizeof(pail) << endl;
    cout << sizeof(pail.int_val) << endl;
    cout << sizeof(pail.long_val) << endl;
    cout << sizeof(pail.double_val) << endl;

对于匿名共用体

union // 即没有名称,嵌套在struct中使用,
//此时共用体成员被视作为结构体变量的成员变量,
//且占用同一个内存地址,即每次仅有一个变量存活,需自行判断为哪一个
{
	int int_val;
	long long_val;
	double double_val;
};

4.6

枚举
可代替const使用,仅有赋值运算,无算术运算

enum spectrum {red, orange, yellow, blue, violet};// spectrum可以省略
spectrum band;// 声明变量
band = blue;// 枚举变量只能被赋值为枚举量,即red, orange, yellow, blue, violet其中一个,
//band = 2000;// 无法赋值,其他任何数据都无法完成赋值
//cout << band << endl;
  1. 使 spectrum 成为新类型名称,并称为枚举
  2. 将 red, orange, yellow, blue, violet 作为符号常量,并自动对应整型 0-4,并将这些数值赋给这些符号常量【red为0, orange为1等】,这些常量称为枚举量
    也可以显式的赋值:
enum spectrum {red=1, orange=2, yellow=3, blue=4, violet=5};
enum spectrum {red, orange=2, yellow, blue=99, violet};// 只赋值一部分
//此时,violet将会自动赋值为100,后面永远比前面加1
enum spectrum {red, orange=0, yellow, blue=1};// 前两个值相同,后两个值也相同

早期C++只能使用int类型进行赋值枚举量,现在可以使用long或者long long进行赋值

枚举量为整型,可以提升为int,但int不能转换为枚举类型

band = blue;// 可以
band = 3;// 不可以
band = 3 + blue;// 不可以,但等式右边成立,只是不能将int赋给枚举类型
cout << 3 + blue << endl;// 可以,输出为3+3=6
cout << 3 + band << endl;// 也可以
// 也可以通过强制类型转换进行赋值
band = spectrum(3);// 前提是3这个整型在spectrum中有效存在,若使用999则报错,spectrum中没有对应枚举量

枚举的取值范围

enum spectrum {red, orange=2, yellow, blue=99, violet};

范围:
找出最大和最小:100和0
上限:大于最大值的最小的2**n【2的N次方】减1,这里为2**7 - 1 = 128 -1 = 127
下限:如果最小值不小于0,则下限为0
若小于0,以最小值为-6为例,则为 -2**n + 1,这里为 -2**3 + 1 = -8 +1 = -7

此时下面这句是合法语句:虽然不是枚举值,但是在范围中

band = spectrum(97);

存储枚举所需空间由编译器决定

  1. 取值范围较小的,使用一个字节或更少
  2. long类型的枚举使用4个字节

4.7

指针和自由存储空间
存储数据3种基本属性:

  1. 存在何处
  2. 存储值为多少
  3. 存储类型
    取址符:&
    代码4-14
// address.cpp -- using the & operator to find address
#include <iostream>
using namespace std;

int main()
{
    int donuts = 6;
    double cups = 4.5;

    cout << "Donuts value: " << donuts << endl;
    cout << "Donuts Address: " << &donuts << endl;
    cout << "cups value: " << cups << endl;
    cout << "cups Address: " << &cups << endl;

    return 0;
}

面对对象编程与传统过程性编程区别:
OOP强调在运行阶段【非编译阶段】进行决策
提供灵活性:如分配数组内存【C++中:charr[20] 20个长度在编译时就分配好内存空间了,此为编译阶段决策,称为静态联编】时,OOP将这类决策推迟到运行阶段进行,减少内存浪费,要多少取多少,在运行阶段确定数组长度,即运行阶段决策,称为动态联编
运行阶段:程序正在运行
编译阶段:编译器将程序组合起来

指针:地址视为指定的量,值视为派生量【与存储数据策略相反】

声明和初始化指针
" * " 运算符:间接值/解引用运算符
代码4-15

// pointer.cpp -- our first pointer variable
#include <iostream>
using namespace std;

int main()
{
    int updates = 6;
    int * p_updates;// * p_updates是指向int类型,p_updates的类型是指向int的指针(int*)
    p_updates = &updates;

    cout << "Values: updates = " << updates << endl;
    cout << "Values: *p_updates = " << *p_updates << endl;

    cout << "Addresses: &updates: " << &updates << endl;
    cout << "p_updates: " << p_updates << endl;

    *p_updates = *p_updates + 1;// 通过地址修改所指向的内容,会对原有的内容进行覆盖
    cout << "Now updates = " << updates << endl;

    return 0;
}
double* t1;
int* t2;
char* t3;
// 这些变量自身的长度相同[存放指向的地址],只是指向的数据类型长度不同

代码4-16

// init_ptr.cpp -- Initialize a pointer
#include <iostream>
using namespace std;

int main()
{
    int higgens = 5;
    int* ptr = &higgens;

    cout << "Value of higgens: " << higgens << endl;
    cout << "Addresses of higgens : " << &higgens << endl; 
    cout << "Value of *ptr: " << *ptr << endl;
    cout << "Value of ptr: " << ptr << endl;

    return 0;
}

指针的危害
声明指针的同时完成初始化【给予一个确切的地址】,避免野指针问题
编译器只会给变量分配储存地址的内存,而不会分配所指向的内存地址

int* ptr;// 如果不初始化【赋予指向的内存地址】,指针有可能指向任意位置,如果这个地址被其他或者系统所使用,则有可能引发错误
*ptr = 0721;// 对未知地址进行修改
cout << *ptr1 << endl;// 有可能会报错,若指向无关紧要地址,则会产生输出

指针和数字

int* ptr;
//ptr = 0xB8000000;// 类型不匹配
ptr = (int *)0xB8000000;// 进行类型转换,可以赋值【但不一定能将这块地址内的内容输出】
// 在某些平台中,int为2字节,地址为4字节

使用new分配内存
指针初始化为变量地址
变量:编译时分配的有名称的内存
C语言中使用 malloc() 函数分配内存
C++中除此之外还可以使用new运算符

int* ptr = new int;// 在运行阶段为int值分配一个未命名的内存,并使用指针访问这块地址,同样也可以为结构的自定义类型(如: inflatable)
//inflatable* ptr = new inflatable;
//new执行流程:确定所需字节内存、寻找满足的内存、返回其地址
int higgens = 5;
int* ptr = &higgens;// 作用上等同,但这句发生在编译阶段

代码4-17

// use_new.cpp -- using new operator
#include <iostream>
using namespace std;

int main()
{
    int nights = 1001;
    int* p = new int;
    *p = 1001;

    cout << "nights value : " << nights << " location: " << &nights << endl;
    cout << "*pt(new int) : " << *p << " location: " << p << endl;

    double* pd = new double;
    *pd = 100000001.0;

    cout << "*pd(new double) : " << *pd << " location: " << pd << endl;
    cout << "location of pointer pd: " << &pd << endl;// 指针自身的地址
    cout << "size of p: " << sizeof(p) << endl;
    cout << "size of *p: " << sizeof(*p) << endl;
    cout << "size of pd: " << sizeof(pd) << endl;
    cout << "size of *pd: " << sizeof(*pd) << endl;

    return 0;
}

地址本身只是所存储数据的开始地址,对于存储了什么类型的数据,多长的数据无法知道,所以必须加上类型

普通变量存储在栈区
new获取的内存地址为堆区/自由存储区
对于内存四区概念后续展开

内存耗尽时,new请求无法满足,较老的实现中,new会返回0,而C++中值为0的指针被成为空指针【并不是野指针】,
空指针常用于表示运算符或函数失败

使用delete释放内存
!!!与new配套使用,尽量放在同一个函数中使用!!!

int* p = new int;
int* pm = p;// 对于指向同一块地址的两个指针
delete pm;// 同样可以对那块地址实现释放,但下面的语句将会出错,不能对一块地址释放两次,所以应该尽量避免多个指针指向同一块地址
delete p;// 使用地址对内存进行释放,仅释放所指向的内存,指针本身并未被释放,仍可以指向其他地址

new创建动态数组
对于大型数据【数组、字符串、结构】应当使用new

int* ptr = new int [10];// 申请10个长度的int数组,返回数组开头第一个元素的地址
delete [] ptr;// 释放数组内存

对空指针使用delete是安全的

对动态数组的使用:
与数组基本相同,使用下标访问元素
【普通数组名即为自身数组的首元素地址,但类似于常量,无法修改,只能使用下标访问元素】
【但动态数组除下标外,还可以通过首元素地址增减来访问其他元素,如:
double类型的指针+1即为对所存的地址数值上增加8个字节 [ 即下一个double类型数据的地址 ]】
【C和C++内部都使用指针处理数组】

代码4-18

// arraynew.cpp -- using the new operator for arrays
#include <iostream>
using namespace std;

int main()
{
    double* p3 = new double[3];
    p3[0] = 0.2;
    p3[1] = 0.5;
    p3[2] = 0.8;

    cout << "p3[0] is " << p3[0] << " p3[1] is " << p3[1] << " p3[2] is " << p3[2] << endl;
    p3 = p3 + 1;// 使地址向后移动一个int长度:4字节,当前p3的地址相当于之前的p3[1]地址
    cout << "now p3[0] is " << p3[0] << " p3[1] is " << p3[1] /*<< " p3[2] is " << p3[2]// 此时p3[2]的地址已经超出申请的地址范围,不能保证其能够正常输出*/ << endl;
    p3 = p3 - 1;// 将地址回退,方便后续正常释放内存
    delete [] p3;

    return 0;

}

4.8

指针、数组和指针算术
指针与数组基本等价原因:指针算术与C++内部处理数组方式
代码4-19

// addpntrs.cpp -- pointer addition
#include <iostream>
using namespace std;

int main()
{
    double wages[3] = {10000.0, 20000.0, 30000.0};
    short stacks[3] = {3, 2, 1};

    double* pw = wages;// 数组名为首元素地址
    short* ps = stacks;

    cout << "pw = " << pw << " *pw = " << *pw << endl;
    pw = pw + 1;
    //wages = wages + 1;// 错误,数组名为常量,无法修改,但等式右边可单独使用,如下:
	//
    cout << "add 1 to the pw pointer" << endl;
    cout << "pw = " << pw << " *pw = " << *pw << endl << endl;
    cout << "ps = " << ps << " *ps = " << *ps << endl;
    ps = ps + 1;

    cout << "add 1 to the ps pointer" << endl;
    cout << "ps = " << ps << " *pw = " << *ps << endl << endl;

    cout << "access two elements with array notation" << endl;
    cout << "stacks[0] = " << stacks[0] << " stacks[1] = " << stacks[1] << endl;
    cout << "access two elements with pointer notation" << endl;
    cout << "*stacks = " << *stacks << " *(stacks + 1) = " << *(stacks + 1) << endl;

    cout << sizeof(wages) << " = size of wages array" << endl;// 结果为整个数组长度,此时数组名不会被解释为首元素地址
    cout << sizeof(pw) << " = size of pw pointer" << endl;// 为指针自身长度
    
    return 0;
}

对于指针或者数组,C++都会执行一次转换:
array[i] ——> *(array + i)

short tell[10];
cout << tell << endl;// 首元素地址,tell理解为short指针
cout << &tell << endl;// 整个数组的地址,&tell理解为short数组指针

这两个输出在数值上相同,但在概念上:
第一个为2字节内存块地址
第二个为20字节内存卡地址

对应指针声明:

short *p = tell;
short (*pas)[20] = &tell;

指针和字符串

char flower[10] = "rose";
cout << flower << "s are red" << endl;// 会从首元素地址打印对应元素,依次往后,直到遇到空字符【这里为flower[4]开始为空字符】结束
// 在C++中,引号括起的字符串如数组名一样,为首元素的地址,对于"s are red",只是将地址发送给cout,并非发送整个字符串

代码4-20

// ptrstr.cpp -- using pointer to strings
#include <string.h>
#include <iostream>
using namespace std;

int main()
{
    char animal[20] = "bear";
    const char* bird = "wren";// 常量指针:禁止修改所指向地址中的内容
    // bird = &animal[0];// 修改指向,不会报错
    // *bird = "bear";// 修改所指向地址的内容会报错
    char* ps = NULL;

    cout << animal << " and " << bird << endl;
    // cout << ps << endl;// 会出现意想不到的错误
    cout << "Enter a kind of animal: ";
    cin >> animal;

    ps = animal;// 使用赋值运算符,复制的为地址
    cout << ps << "!\n" << "Before using strcpy():\n" << animal << " at " << (int *) animal << endl;
    cout << ps << " at " << (int *) ps << endl;

    ps = new char[strlen(animal) + 1];// 获取所需长度,并添加空字符的长度

    strcpy(ps, animal);// 传入被复制的字符串地址animal和目标存入地址ps,创建副本,地址发生变化
    cout << "After using strcpy():\n" << animal << " at " << (int *) animal << endl;
    cout << ps << " at " << (int *) ps << endl;
    delete [] ps;

    return 0;
}

对于字符串初始化应使用"=",以外应使用strcpy(),strncpy()等函数操作

动态结构
代码4-21

// newstrct.cpp -- using new with a structure
#include <iostream>
//#include <string>
using namespace std;

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

int main()
{
    inflatable* ps = new inflatable;
    cout << "Enter name: ";
    cin.get(ps->name, 20);// 标识符为指向结构的指针(ps为指针),则使用箭头运算符
    cout << "Enter volume: ";
    cin >> (*ps).volume;// 标识符为结构名(*ps解引用后为结构名),则使用句点运算符
    cout << "Enter price: $";
    cin >> ps->price;
    cout << "Name: " << (*ps).name << endl;
    cout << "Volume: " << ps->volume << " cubic feet" << endl;
    cout << "Price: $" << ps->price << endl;
    delete ps;

    return 0;
}

假设读取100个字符串,最大字符串包含79个字符,
使用char数组来存储:需要100个数组,每个数组长度80【外加一个结尾空字符】,共需8000个字节【产生极大的浪费】
另一种:创建一个数组,包含100个指向字符串的指针,使用new分配每个字符串所需的空间【极大的节省空间】
代码4-22

// delete.cpp -- using the delete operator
#include <iostream>
#include <cstring>
using namespace std;

char* getname(void);

int main()
{
    char* name;

    name = getname();
    cout << name << " at " << (int *) name << endl;
    delete [] name;// 添加[]是因为所返回的地址后面还有数据,直到遇见结束标志

    name = getname();
    cout << name << " at " << (int *) name << endl;// 两次地址输出不一定相同
    delete [] name;

    return 0;
}

char* getname()
{
    char temp[80];// 80: 外加结尾空字符,局部变量,此函数运行结束被释放
    cout << "Enter last name: ";
    cin >> temp;
    char* pn = new char[strlen(temp) + 1];// 重新申请恰好合适的空间
    strcpy(pn, temp);// 将temp内的内容存入pn中,【除了初始化,涉及字符串操作使用strcpy等函数】

    return pn;
}

全程为地址进行传递,避免使用大量的数据在函数间传递,减少了对内存总量的申请和占用

分配内存方法

  1. 自动存储【存储在栈中,采用后进先出策略进行添加和释放】
    函数内部定义的常规变量【自动变量】,即局部变量,仅存活在所被定义的函数中,离开即销毁

  2. 静态存储
    整个程序执行期间都存在,其一在函数外部定义,即全局变量,其二使用static

static double fee = 56.25;// 在C++2.0版本或ANSI C后期支持初始化自动数组和自动结构
  1. 动态存储【存储在堆区,与自动变量和静态变量在内存中地址分开,不在同一区域】
    即new和delete的配套使用,生命周期取决于程序员,但可能导致对堆的占用不连续,在跟踪新分配内存位置有些困难
    若只new不使用delete,可能会导致内存泄漏【即仅申请新内存,不释放旧内存,导致内存耗尽,程序崩溃,使用C++智能指针更有效】

  2. 线程存储【C++11新增】

4.9

类型组合
将数组,结构,指针等结合使用:

// mixtypes.cpp -- some type combination
#include <iostream>
using namespace std;

struct yearstruct
{
    int year;
};

int main()
{
    yearstruct y01, y02, y03;
    y01.year = 1998;
    // cout << &y01 << endl;
    yearstruct* pa = &y02;
    pa->year = 1999;
    yearstruct trio[4];
    trio[0].year = 2000;// 结构体名称使用句点
    (trio+1)->year = 2001;// 指针使用箭头
    const yearstruct* arp[3] = {&y01, &y02, &y03};// 创建指针数组,存放指向不同地址的指针
    // cout << arp[0]->year << endl;// 可以使用指针元素获取成员变量
    const yearstruct** ppa = arp;// 创建指向指针数组的指针
    cout << (*ppa)->year << endl;// 使用指针数组指针获取成员变量【ppa为二级指针,解引用后为指针数组,!!!一定要加括号区分优先级!!!,即一级指针,即可获取成员变量】
    cout << (*(ppa+1))->year << endl;
    auto ppb = arp;// 如果忘记arp类型,可以使用auto的推断功能,C++11

    return 0;

}

4.10

数组替代品
模板类vector:一种动态数组,类似于string类,存储在堆区
代码示例

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    vector<int> numbers;// 会在插入或添加值时自动调整长度,所以可声明为0长
    cout << numbers.capacity() << endl;

    // cout << "number 长度: " << sizeof(numbers)/sizeof(numbers[0]) << endl;

    int n = 5;// n可以为整型变量,或整型常量

    vector<double> vd(n);// 表示创建n长度double数组
    cout << vd.capacity() << endl;
    return 0;

}

模板类array :array对象长度固定,使用栈存储,效率与数组相同
代码示例

#include <iostream>
#include <array>
using namespace std;

int main()
{
    array<int, 5> ai;
    array<double, 5> ad={1.0, 2.0, 3.0, 4.0, 5.0};

    const int n = 4;// 必须加const关键词
    array<int, n> aai;// n必须为常量,无法像vector那样键盘输入
    
    return 0;

}

代码4-24

// choices.cpp -- array variation
#include <iostream>
#include <vector>
#include <array>
using namespace std;

int main()
{
    double a1[4] = {1.2, 2.4, 3.6, 4.8};
    vector<double> a2(4);// C98 STL 没有简单的初始化方法
    a2[0] = 1.0/3.0;
    a2[1] = 1.0/3.0;
    a2[2] = 1.0/7.0;
    a2[3] = 1.0/9.0;

    array<double, 4> a3 = {3.14, 2.72, 1.62, 1.41};
    array<double, 4> a4;
    a4 = a3;// 可以对象间整体复制,而数组无法此操作

    cout << "a1[2]: " << a1[2] << " at " << &a1[2] << endl;
    cout << "a2[2]: " << a2[2] << " at " << &a2[2] << endl;
    cout << "a3[2]: " << a3[2] << " at " << &a3[2] << endl;
    cout << "a4[2]: " << a4[2] << " at " << &a4[2] << endl;
    
    a1[-2] = 20.2;// 找到a1指向的地址,向前移动2个double长度,并进行修改
    // 对于超界错误C++不做检查
    // 或者使用成员函数at(),运行时捕获超界,但运行时间更长
    // cout << "a2[0]: " << a2[0] << endl;
    // a2.at(0) = 20.2;
    // cout << "a2[0]: " << a2[0] << endl;
    // a2.at(-1) = 20.2;// 检测到超界自动退出
    // 此外可以使用begin(),end()来确认边界,避免无意间超界
    // cout << "*a5.begin(): " << *a5.begin() << endl;
    // cout << "*a5.end(): " << *a5.end() << endl;// 最后一位为空字符,日常使用应当截止到end()的前一位

    cout << "a1[-2]: " << a1[2] << " at " << &a1[-2] << endl;
    cout << "a3[2]: " << a3[2] << " at " << &a3[2] << endl;
    cout << "a4[2]: " << a4[2] << " at " << &a4[2] << endl;
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值