指针
基本使用
- 指针是一个值为内存地址的变量.
语法: 数据类型 * 指针变量名;
int * ptr_num;
char* ptr_name;
float* money_ptr;
double* p_price;
- 注意:
int* p
的写法偏向于地址, 即p就是一个地址变量, 表示一个十六进制的地址int *p
的写法偏向于值,*p
是一个整型变量, 能够表示一个整型值- 声明中的
*
和使用中的*
含义完全不一样.
取地址符&
int num = 1024;
int* ptr_num;
//取num变量的地址复制给ptr_num
ptr_num = #
间接运算符*
int num = 1024;
int* ptr_num;
ptr_num = #
*ptr_num = 1111; //等价于 num = 1111;
#include <iostream>
using namespace std;
int main()
{
double num = 1024.5;
//定义一个指针, 并指向num变量
double* ptr_num = #
cout << "ptr_num的值: " << ptr_num << " " << &num << endl;
cout << "ptr_num指向空间的值:" << *ptr_num << endl;
char ch = 'a';
char* ptr_ch = &ch;
cout << "ptr_ch的值: " << (void *)ptr_ch << " " << "ptr_ch指向空间的值:" << *ptr_ch << endl;
return 0;
}
空指针
- 空指针不指向任何对象, 在试图使用一个指针之前可以首先检查是否为空
- 尽量在定义了对象之后再定义指向它的指针
int *ptr1 = nullptr; //等价于 int *ptr1 = 0;
int *ptr2 = 0;
//需要包含cstdlib
int *ptr3 = NULL; //等价于 int *ptr3 = 0;
void * 指针
- 一种特殊的指针类型, 可以存放任意对象的地址
void *
指针存放一个内存地址, 地址指向的内容是什么类型不能确定void *
类型指针一般用来: 和别的指针比较, 作为函数的输入和输出, 赋值给另一个void *
指针
double obj_num = 3.14;
double *ptr_obj = &obj_num;
cout << boolalpha;
void *vptr_obj = &obj_num;
cout << (ptr_obj == vptr_obj) << endl;
小结
- 指针是一个变量, 存储的是另一个对象的内存地址
- 如果一个变量存储了另一个对象的地址, 则称该变量指向这个对象
- 指针变量可以赋值, 指针的指向在程序执行中可以改变
- 指针变量的命名规则和其他变量的命名规则一样, 不能与现有变量同名
- 若指针已声明指向某种类型数据的地址, 则不能用于存储其他类型数据的地址
引用
- 为对象起的另外一个名字, 引用即别名
- 引用并非对象, 只是为已经存在的对象起的别名
- 引用只能绑定在对象上, 不能与字面值或某个表达式的计算结果绑定在一起
- 引用必须初始化, 所以使用引用之前不需要测试其有效性, 因此使用引用可能会比使用指针效率高.
int value = 1024;
int& ref_value = value;
//错误, 引用必须初始化
int& ref_value2;
指针与引用
-
引用对指针进行了简单的封装, 其底层仍然是指针
-
获取引用地址时, 编译器会进行内部转换
指针与数组
-
数组, 存储在一块连续的内存空间中
-
数组名就是这块连续内存空间的首地址
#include <iostream>
using namespace std;
int main()
{
double score[] {11, 22, 33, 44, 55};
double * ptr_score = score;
// 40 4
cout << sizeof(score) << ' ' << sizeof(ptr_score) << endl;
}
指针的算术运算
- 一个类型为T的指针的移动, 以sizeof(T)为移动单位
小结
- 数组名就是这块连续内存单元的首地址
- int num[50]; num是数组名, 也是数组的首地址
- num的值与&num[0]的值相同
- 数组的第i+1个元素的地址可表示为 num+i 或者 &num[i+1]
- 数组的第i+1个元素的值可表示为 num[i] 或者 *(num+i)
- 指针变量可以指向数组元素, int* ptr_num = &num[4] 或者 int * ptr_num = num + 4.
动态分配内存
使用new分配内存
- 指针的真正用武之地是在运行阶段分配未命名的内存以存储值
- 在这种情况下, 只能通过指针来访问内存
使用delete释放内存
- 与new配对使用
- 不要释放已经释放的内存
- 不能释放声明变量分配的内存
//在运行阶段为int值分配未命名的内存, 使用指针来访问这个值
int* ptr_int = new int;
//释放由new分配的内存
delete ptr_int;
//注意: 不要创建两个指向同一内存块的指针, 有可能误删两次
int * ptr = new int;
int * ptr1 = ptr;
delete ptr;
delete ptr1;
动态分配数组
-
使用new创建动态分配的数组, new运算符会返回第一个元素的地址
-
使用delete[]释放内存, []表示释放整个数组
int * ptr_int = new int; short * ptr_short = new short[500]; delete ptr_int; delete [] ptr_short;
-
注意:
- 不要使用delete释放不是new分配的内存
- 不要使用delete释放同一内存两次
- 如果使用new[]为数组分配内存, 则对应delete[]释放内存
- 对空指针使用delete是安全的
#include <iostream>
using namespace std;
int main()
{
int num[5]; //栈内存
double* nums = new double[5]; //堆内存
// 20 4
cout << sizeof(num) << ' ' << sizeof(nums) << endl;
}
程序的内存分配
- 栈区
- 由编译器自动分配释放, 一般存放函数的参数值, 局部变量的值
- 操作方式类似数据结构中的栈, 先进后出
- 堆区
- 一般由程序员分配释放, 若程序不释放, 程序结束时可能会由操作系统回收
- 注意, 与数据结构中的堆是两回事, 分配方式类似链表
- 全局区(静态区)
- 全局变量和静态变量存储在一起的
- 程序结束后由系统释放
- 文字常量去
- 常量字符串就存放在这里, 程序结束由系统释放
- 程序代码区
- 存放函数体的二进制代码
//栈区
int num;
//栈区
char str[] = 'hello';
//栈区
char* ptr;
//hello以及 在常量去, ptr2在栈区
char* ptr2 = "hello";
//全局初始化区
static inr num2 = 100;
//分配的内存在堆区, 但是ptr本身在栈区
ptr = new char[10];
小结
-
指针是一个变量, 存储另一个对象的内存地址
-
指针的声明由基本类型, 星号*和变量名组成
-
为指针赋值, 赋值运算符右侧必须是一个地址
- 如果是普通变量需要在前面加取地址运算符&
- 如果是另一个指针变量或者一个数组, 不需要加&
-
运算符*用于返回指针指向地址的内存地址中存储的值
-
使用指针可以访问一维数组和二维数组的元素
//二维数组的创建和遍历
#include <iostream>
using namespace std;
int main()
{
//使用指针创建二维数组
int arrays[5][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
{10, 11, 12},
{13, 14, 15},
};
int (*p)[3] = arrays;
for(int i = 0; i < 5; i++){
for(int j = 0; j < 3; j++){
// cout << *(*(p+i) + j) << ',';
cout << *(*(p+i)+j) << ',';
}
cout << endl;
}
}