|
前言
个人觉得学习编程最有效的方法是阅读专业的书籍,通过阅读专业书籍可以构建更加系统化的知识体系。 一直以来都很想深入学习一下C++,将其作为自己的主力开发语言。现在为了完成自己这一直以来的心愿,准备认真学习《C++ Primer Plus》。 为了提高学习效率,在学习的过程中将通过发布学习笔记的方式,持续记录自己学习C++的过程。
本篇前言
本章首先将介绍除类以外的所有复合类型,以及将介绍new和delete及如何使用它们来管理数据。另外,还将简要地介绍string类,它提供了另一种处理字符串的途径。
七、指针和自由存储空间
指针是一个变量,其存储的是值的地址,而不是值本身。通过对变量应用地址运算符(&),可以获得常规变量的地址。代码如下:
// address.cpp -- 使用 & 操作符查找地址
#include <iostream>
int main()
{
// 作者:好先生FX
using namespace std;
int age = 18;
cout << &#34;age 变量的值等于&#34; << age << &#34;\n&#34;;
cout << &#34;age 变量的存储地址是&#34; << &age << endl;
return 0;
}运行结果如下:
age 变量的值等于18
age 变量的存储地址是0xf00d5ff69c*运算符称为间接值(indirect value)或解除引用(dereferencing)运算符将其应用于指针,可以得到该地址处存储的值(这和乘法使用的符号相同,C++根据上下文来确定所指的是乘法还是解除引用)。
// pointer.cpp -- 我们的第一个指针变量
#include <iostream>
int main()
{
using namespace std;
int age = 18;// 声明一个变量
int *p_age;// 声明指向int类型的指针
p_age = &age; // 将int类型的地址赋给指针
cout << &#34;age 变量的值等于&#34; << age ;
cout << &#34;,*p_age 的值是&#34; << *p_age ;
cout << &#34;,p_age 的值是&#34; << p_age << endl;
// 使用指针改变变量值
*p_age = *p_age + 1;
cout << &#34;现在 age 变量的值等于&#34; << age << endl;
return 0;
}运行结果如下:
age 变量的值等于18,*p_age 的值是18,p_age 的值是0x20237ffad4
现在 age 变量的值等于19提示:
*运算符两边的空格是可选的,对编译器来说没有任何区别。以下四种写法都是等价的:
int*p_age;
int *p_age;
int* p_age;
int * p_age;1、声明和初始化指针
对每一个指针变量名,都需要使用一个 * ,int *是一种复合类型,是指向int的指针。
int *p1, p2;以上代码表示的是,声明一个指针(p1)和一个int变量(p2)。
double *p_price;以上代码,声明了一个指向double的指针,因此编译器知道*p_price是一个double类型的值。和数组一样,指针都是基 于其他类型的。
虽然p_age和p_price指向两种长度不同的数据类型,但这两个变量本身的长度通常是相同的。
可以在声明语句中初始化指针。在这种情况下,被初始化的是指针,而不是它指向的值。也就是说下面的语句将p_age(而不是*p_age)的值设置为&age:
int age = 18;
int *p_age = &age;2、指针的危险
在C++中创建指针时,计算机将分配用来存储地址的内存,但不会分配用来存储指针所指向的数据的内存。
int *p_age;
*p_age = 18;p_age指向的地方可能并不是要存储18这个值得地方,一定要在对指针应用解除引用运算特(*)之前,将指针初始化为一个确定的、适当的地址。
3、指针和数字
指针不是整型、虽然计算机通常把地址当作整数来处理。
要将数字值作为地址来使用,应通过强制类型转换将数字转换为适当的地址类型,代码如下:
int *p_age;
p_age = (int*)0x3041bffbe4;注意:
p_age是int值的地址并不意味着p_age本身的类型是int。例如,在有些平台中,int类型是个2字节值,而地址是个4字节值。
4、使用new来分配内存
通过new运算符,可以为需要的类型数据分配一个长度正确的内存块,并返回该内存的地址。
为一个数据对象 (术语“数据对象”比“变量”更通用,它指的是为数据项分配的内存块。这里的“对象”不是“面向对象给程”中的对象,而是一样“东西”。) 获得并指定分配内存的通用格式如下:
typeName *pointer_name = new typeName;new typeName告诉程序,需要适合存储typeName类型的内存。
在C++中,值为0的指针被称为空指针。
5、使用delete释放内存
通过delete运算符,可以在使用完内存后,能够将其归还给内存池。
int * ps = new int;
delete ps;上述代码会释放ps指向的内存,但不会删除指针ps本身。一定要配对的使用new和delete;否则将发生内存泄漏(memory leak)。
6、使用new来创建动态数组
使用new和delete时,应遵守以下规则:
- 不要使用delete来释放不是new分配的内存。
- 不要使用delete释放同一个内存块两次。
- 如果使用new[]为数组分配内存,则应使用delete[]来释放。
- 如果使用new为一个实体分配内存,则应使用delete(没有方括号)来释放。
- 对空指针应用delete是安全的。
创建一个包含10个包含int元素的数组然后释放内存,可以通过如下代码:
int * psome = new int [10];
delete [] psome;由于psome指向数组的第一个元素,因此*psome是第一个元素的值。要访问第二个及之后的元素方括号加数字可以通过如下代码:
var p1 = psome[0];//访问第一个元素
var p2 = psome[1];//访问第二个元素
...
var p10 = psome[9];//访问第十个元素对指针psome进行算术运算,可以修改指针指向的地址。例如通过如下代码,可以将psome[0]指向数组的第二个元素:
psome = psome + 1;
var p1 = psome[0];//访问第二个元素八、指针、数组和指针算术
指针和数组基本等价的原因在于指针算术(poimter aritmetic)和 C++内部处理数组的方式。
将整数变量加1后,其值将增加1; 但将指针变量加1后,增加的量等于它指向的类型的字节数。
数组表达式psome[1],C++编评器将该表达式看作是*(psome+1),也就是先对指针进行算术运算移动地址至第二个元素的地址,然后通过*对指针进行解引用得到其中的值。
在多数情况下,C++将数组名视为数组的第1个元素的地址。
一种例外情况是,将sizeof运算符用于数组名用时,此时将返回整个数组的长度(单位为字节)。
创建动态结构时不能将成员运算符句点用于结构名,因为这种结构 没有名称,只是知道它的地址。C++专门为这种情况提供了一个运算符:箭头成员运算符(->)。该运算符由连字符和大于号组成,可用于指向结构的指针,就像点运算符可用于结构名一样。
根据用于分配内存的方法,C++有3种管理数据内存的方式:自动存储、静态存储和动态存储(有时也叫作自由存储空间或堆)。
- 自动存储:在函数内部定义的常规变量使用自动存储空间,被称为自动变量(automatic variable),这意味着它们在所属的函数被调用时自动产生,在该函数结束时消亡。
- 静态存储:是整个程序执行期间都存在的存储方式。使变量成为静态的方式有两种:一种是在函数外面定义它;另一种是在声明变量时使用关键字static。
- 动态存储:new和delete运算符提供了一种比自动变量和静态变量更灵活的方法。它们管理了一个内存池,这在C++中被称为自由存储空间 (free store)或堆 (heap)。该内存池同用于静态变量和自动变量的内存是分开的。
九、类型组合
本章介绍了数组、结构和指针。
从结构开始:
struct Person // 声明结构
{
char name[20];
int age;
};创建这种类型的变量:
Person p1;然后使用成员运算符访问其成员:
p1.name = &#34;张三&#34;;可创建指向这种结构的指针:
Person * pp1 = &p1;该指针设置为有效地址后,就可以使用间接成员运算符来访问成员:
pp1->name = &#34;张三&#34;;创建结构数组:
Person persons[3];然后,可以使用成员运算符访问元素的成员:
persons[0].name = &#34;张三&#34;;由于数组名是一个指针,因此可以直接使用间接成员运算符:
(persons + 1)->name = &#34;李四&#34;;// 与persons[1].name = &#34;李四&#34;;相同可创建指针数组:
Person * ppersons[3] = { &p1 , &p2 , &p3 };通过间接成员运算符访问:
cout << pperson[0]->name << endl;创建指向数组的指针:
const Person ** pppersons = ppersons;C++11提供了可以推断类型的auto关键字,可以更方便地声明指向数组的指针:
auto apppersons = ppersons;由于apppersons是一个指向结构指针的指针,因此*apppersons是一个结构指针,可通过间接成员运算符访问元素:
cout << (*apppersons)->name << endl;
cout << *(apppersons+1)->name << endl;十、数组的替代品
模板类vector和array是数组的替代品。
1、模板类vector
模板类vector类似于string类,也是一种动态数组。可以在运行阶段设置vector对象的长度,可在末尾附加新数据,还可在中间插入新数据。基本上,它是使用new创建动态数组的替代品。实际上,vector类确实使用new和delete来管理内存,但这种工作是自动完成的。
首先,要使用vector对象,必须包含头文件vector。其次,包含头文件 包含在名称空间std中。再次,模板使用不同的语法来指出它存储的数据类型。最后,vector类使用不同的语法来指定元素数。下面是一些示例;
#include <vector>
...
using namespace std;
vector<int> vi;//创建一个int类型长度为0的数组
int n;
cin >> n;
vector<double> vd(n);//创建一个有n个元素的double类型数组由于vector对象可自动调整长度,因此可以将的切始长度设置为零。但要调整长度,需要使用vector包中的各种方法。
一般而言,下面的声明创建一个名为vt的vector对象,它可存储n_elem个类型为typeName的元素:
vector<typeName> vt (n_elem);其中参数n_elem可以是整型常量,也可以是整型变量、
2、模板类array(C++11)
vector类的功能比数组强大,但付出的代价是效率稍低,如果您需要的是长度固定的数组,使用数组是更佳的选择,但代价是不那么方便和安全。基于上述情况,C++11新增了模板类array,它也位于名称空间std中。与数组一样,array对象的长度也是固定的,也使用栈(静态内存分配),而不是自由存储区,因此其效率与数组相同,但更方便,更安全。要创建array对象,需要包含头文件array。array对象的创建语法与 vector稍有不同:
#include <array>
...
using spacename std;
array<int,3> ai;//创建包含5个int类型的array对象
array<double,4> ad = {1.1 , 2.2 , 3.3 , 4.41};创建一个名为arr包含n_elem个类型为typeName的array对象:
array<typeName , n_elem> arr;与vector对象不同的是,n_elem不能是变量。
3、比较数组、vector对象和array对象
- 无论是数组、vector对象还是array对象,都可使用标准数组表示法来访同各个元素。
- array对象和数组存储在相同的内存(即栈)中,而vector对象存储在另个区域(自
- 自存储区或堆)中。
- 可以将一个array对象赋给另一个array对象;而对于数组,必须逐元素复制数据。
|
|