本篇目录
一、指针
- 程序员眼中的内存如下
1.1 地址
- 释义:每一个格子代表1字节,即8bit,可以存储一个字母,每一个格子都有一个编号,即地址,上图中简略表示成格子下面的数字,通常为16进制,类似
0x7fffde585878
这样,CPU会根据这个编号通过一定的方式调用内存中指定位置的数据(这些都是高级语言虚拟出来的,硬件中并不存在)
1.2 指针类型
- 释义:一种数据类型,例如
int*
表示整型指针类型,跟int,float等为同一等级 - 初始值:指针本身是一个对象,生命周期内可指向不同的对象,无须在定义时赋初值,但在块作用域内为不确定值,建议赋初值
nullptr
1.3 指针变量(通常也叫指针)
- 常规使用
// 定义、初始化整型变量num int num = 3; // 定义多个整型指针变量pint int *pint, *pint_temp; // 赋值整型指针变量,&为提取变量地址,然后存到pint这个整型指针变量里 pint = # // 以下三种写法都可:定义、初始化指针变量pint int* pint = # int *pint = # // 常用 int * pint = #
普通变量地址
:int默认为4个字节,系统会在内存中取出4个字节组成块
来存放整型数据3
,并以该字节块首字节
的地址作为num的地址取用数据
:*pint
,取用时,会根据指针判断数据类型及num的地址,然后直接读取出对应的内存字节块内的数据并返回pint叫法
:pint为指向num的指针(变量),通常变量两个字省略,pint也称为一级指针,保存变量num的地址
- void指针
- 概述:void为特殊的指针类型,可存放任意对象地址
- 功能:只可比较(==或!=),作为函数的输入输出,赋给另外一个void指针,不可提取指针所指的对象
// 定义:此处obj可以是任何类型 void *pv = &obj;
1.4 指向指针的指针
- 定义:若一个指针(变量)存储了一个指针(变量)的地址,就称这个指针为指向指针的指针,也称为二级指针,保存的是一个指针(变量)的地址
- 代码释义
int var,var_0; // 一级指针,保存一个整型变量的地址 int *ptr; // 二级指针,保存一级指针的地址 // 变量pptr前面的修饰都是为了解释说明变量pptr // 它指向的必须是一个指针,它存的也必须是指针的地址 int **pptr; var = 3000; // 使用取地址运算符&,提取变量var的地址 ptr = &var; // 使用解除引用运算符*,提取指针指向的变量的值 var_0 = *ptr; // 使用取地址运算符&,提取指针变量ptr的地址 pptr = &ptr;
1.5 const与指针
1.5.1 常量指针
- 特点:指针可变更,指针指向的对象不可变更
- 定义:
const int * pt;
强调*pt
是固定的 - 示例
int age = 29; int weight = 160; // 意义:对于pt来说,pt认为*pt是一个常量,即不可以通过pt指针这个途径改变age的值 const int * pt = &age; // 以下可以,pt的声明并不意味着它指向的值实际上一定是一个常量 age = 33; // 以下可以,pt可以指向另一个变量 pt = &weight; // 以下不可以,不可用通过pt指针这个途径修改age值 *pt = 33; // 对比记忆 // 普通常量声明及定义 const int age = 29; // 以下初始化不可以,因为*pt为非常量 // 若成立,代表可以用pt修改age的值,与上一句矛盾 int *pt = &age;
- 函数中应用
// 函数原型:也叫函数声明 int arr(const int * pt); int week[4] = {1,3,5.7}; // 将数组指针传给函数体,但是函数体不可以通过这个指针修改数组里的数据 arr(week);
1.5.2 指针常量
- 特点:指针pt是固定的,即指针中存的变量地址是固定的,但变量值*pt是可变的
- 定义:
int * const pt;
强调pt
是固定的 - 示例
int age = 29; int weight = 160; // 意义:对于pt来说,pt认为pt是一个常量,即不可以改变pt指向的内存地址 int * const pt = &age; // 不可以,pt只能指向age对应的内存地址 pt = &weight; // 可以,可以通过pt这个途径改变age的值 *pt = 33;
二、 存储方式
2.1 栈式存储方式
- 特点
特点 解释 静态联编 变量(普通变量、数组、结构体等)在程序编译时就加入内存中,不管程序是否使用 - 写法
// 值300存储在栈中 int num = 300; // 指针(变量)ptr存储在栈的内存区域中 int * ptr = #
2.2 堆式存储方式
- 特点
特点 解释 动态联编 变量(普通变量、数组、结构体等),程序运行阶段,若需要则在内存中创建它,否则不创建 多次操作 不可释放已经释放的内存块,也不可将两个指针指向同一个内存块 空指针 nullptr
,使用nullptr会导致程序崩溃,但是delete nullptr;
不会有任何影响释放堆内存 delete专注的是释放指向的内存空间:
即ptr_1 = ptr; delete ptr_1;
语句,跟delete ptr;
功能一样,注意不可释放两次内存 - 写法
// new关键字:指针(变量)ptr的值存储在栈的内存区域中,其指向堆的内存区块 int * ptr = new int; // 将值300存储在堆中,不可用ptr = &num这种赋值,因为这种的num放在栈中 *ptr = 300; // ...... // 释放指针所指向的内存空间,否则内存泄露 delete ptr;
三、应用
个人理解:
- 涉及到指针定义、初始化的语句,如:int * pt = &x;
- 等号左侧:除指针pt外,其他所有都是在解释变量pt,即它指向的
数据类型
,以及能存储的数据类型
- 等号右侧:是进行初始化工作,即提取x的地址值放到变量pt里
- 等号左右:若类型不同,则会进行隐式转换或报错
3.1 数组与指针
- 常规数组及指针
int a[3] = {1,3,5}; // 返回首个元素的首地址,注意:sizeof(a[0])等于4 int * pt1 = &a[0]; // 返回整个数组的首地址,注意:sizeof(a)等于12 int * pt2 = &a[3]; // 输出0x7fff7aa19bfc,此为数组的首地址 cout << &a << endl; // 输出0x7fff7aa19bfc,此为首元素的地址 cout << a << endl; // 输出1,*解的是首元素的地址 cout << *a << endl;
注:上式指针pt1内存储的地址等于pt2,但pt1中存储的类型为数组特定的元素,pt2中存储的类型为整个数组,从sizeof的结果可见
- 数组指针:又名指向数组的指针
int a[3] = {1,3,5}; // 优先级() > [] > * int (*pt)[3]; // pt为指向含有三个整型元素的数组的指针 pt = &a; // int (*pt)[3] = &a;等同于上面两句 // 输出:0x7fff7aa19bfc cout << a << endl; // 输出:0x7fff7aa19bfc cout << *pt << endl; // 输出:1 cout << **pt << endl;
- 指针数组:数组的每个元素的数据类型都是指针
int x = y = z = {}; // 优先级() > [] > *,数组的每个元素数据类型由 int * 确定 int * pt[3]; // x等于*pt[0] pt[0] = &x; pt[1] = &y; pt[2] = &z; // 输出:0x7fffb3b2ca5c cout << pt[0] << endl; // 输出:0x7fffb3b2ca58 cout << pt[1] << endl; // 输出:0x7fffb3b2ca54 cout << pt[2] << endl;
3.2 动态数组(堆式存储)
- 写法
// 创建动态数组,psome指向此数组的第一个元素 int * psome = new int[3]; // psome[0] 等同于 *psome psome[0] = 10; // psome[1] 等同于 *(psome+1) psome[1] = 100; // psome[2] 等同于 *(psome+2) psome[2] = 1000; // for循环功能,遍历数组元素 for (int i = 0; i < 3; i++) { // 此处psome[0] 也可提取 psome指针指向的值 cout << *psome << endl; // 将指针向下移动一个,注意:打印完psome[2]时,psome又加了一次 // 这种写法不好,容易把指针移丢,导致内存泄漏 psome += 1; } // 将指针移动回动态数组首位置,为下面释放内存做准备 psome = psome - 3; // 释放psome指针指向的整个数组的内存空间(3~5行), // 且指针psome需指向数组首位置 delete [] psome;
3.3 字符串
-
赋值
// 方法1:多次调用pchar,系统会创建多个副本 // 所以编译器会强制用const关键字对字符串只读使用 const char * pchar = "help"; // 方法2:数组式赋值,不可越界,最多存9个可视化字符 char pchar[10] = "help"; // 若指针指向字符串,则打印字符串,否则打印地址 cout << pchar << endl; >>>help
-
指针运算
// 初始化:直接赋值 char pchar[10] = "1234567890"; // 创建动态字符串(同数组) char * pchar_0 = new char[5]; char * pchar_1 = new char[5]; // 浅拷贝:同类型赋值,仅拷贝地址,两个指针指向同一处内存 // 且pchar_0失去对new的堆内存区间的控制 pchar_0 = pchar; // 深拷贝:将pchar的值拷贝到pchar_1指向的堆内存区间,最大长度4位 strncpy(pchar_1, pchar, 4); // 第5位用\0补齐 pchar_1[5] = '\0'; // 打印字符串:指针pchar在cout语句中解释成字符串的值 cout << pchar << endl; // 打印字符串首地址:指针指向的字符串的首地址 //(int* 为隐式强制类型转换) cout << (int*) pchar_0 << endl;
3.4 动态结构
- 定义结构体
// 定义结构模板sth struct sth { char name[20]; int volumn; double price; };
- 定义结构实例
// 定义、初始化结构(栈) sth stlist = {}; // 定义、初始化结构(堆) // 将创建的内存块的首地址赋值给变量(或叫指针)plist sth * plist = new sth;
- 输入
// 动态结构项赋值(堆),只能收到19个,末尾加'\0' cin.get(plist->name,20); // 常规结构项赋值(栈) cin >> stlist.volumn; // 动态结构体赋值(堆) cin >> (*plist).volumn; // 指针式动态结构体赋值(堆) cin >> plist->price;
- 输出
cout << (*plist).name << endl; cout << stlist.volumn << endl; cout << plist->price << endl;
- 释放堆内存
// 释放堆内存块 delete plist;