指针概念
指针(pointer)是一个值为内存地址的变量(或者数据对象)
使用指针来读取数据,在重复性操作的状况下,可以明显改善程序性能,例如在遍历字符串,查取表格,源控制表格及树状结构上。对指针进行复制,之后再解引用指针以取出数据,无论在时间或空间上,都比直接复制及访问数据本身来的经济快速。
基本用法
datatype * pointer_name;
int year = 6;
int* ptr_year = &year;
//
double day = 7.0;
double* ptr_day = &day;
在声明里的 * 号和在声明后使用中的 * 号代表的符号完全不同
所以我们在声明和定义的时候尽量分成两种写法
但在以下的声明里是完全一样的,只是要分成两种理解更好.
int* p; //p就是一个地址变量,表示一个十六进制地址
其意思为定义一个指针类型的int
int *p; //*p是一个整型变量,能够表示一个整型值.
为什么需要给指针定义特定数据类型
因为指针所指的是内存的一块空间,在声明的时候和数组一样需要明确知道具体长度,而指针指向的变量的数据类型不同,空间长度就不同,比如:int型需要4个字节的空间,long需要8个字节的空间。所以我们需要给指针定义合适的数据类型.
而在对指针的某些操作中,只有知道指针的数据类型,我们才能根据不同的类型操作不同长度的连续空间.
比如
int a= * p
即取值操作就要知道p所指向的空间权里存放的变量的类型,根据不同的类型,*操作会读取不同长度的连续空间,例如:int 4字节;
取地址符&
int num = 0;
int * ptr_num = & num;
间接运算符
int num = 0;
int * ptr_num = # // &读作ampersand
*ptr_num = 1; // *与ptr_num中间没空格 就如上面所述 此处非定义
//此处为 num = 1;
示例
double num = 1;
double * ptr_num = #
cout << "打印num的指针所指向的值"<< *ptr_num << endl;
cout << "打印num的指针的值"<< ptr_num << endl;
输出的地址乱码
char ch = 'a';
char * ptr_ch = &ch;
cout << ptr_ch << '\n' << *ptr_ch << endl;
在C里面定义字符串的句式为
char * string = "what?"
也就是输出的方式(地址)已经被改成字符串了
所以是以字符串的类型打印字符
所以我们要强转成void *类型 (任意类型)
cout << (void *)ptr_ch << endl;
null pointer 空指针
空指针不指向对象,使用指针之前我们可以检测指针是否为空.
当我们给指针初始化时不指向对象时,会默认指向一个系统分配的值.(野指针)
尽量等定义了对象之后再定义指向它的指针.
指针需要及时释放,也需要正确释放.
用法
以下三句话同样效果
int *ptr1 = nullptr; //等价于 int *ptr 1 = 0;
int *ptr2 = 0; //将ptr2初始化为字面常量0;
//需要包括cstdlib
int *ptr3 = NULL; //等价于 int *ptr3 = 0;
void *指针
void *指针是一种特殊的指针类型,可以存放任意对象的地址.
void *指针存放了一个内存地址,地址指向的内容是什么类型是不能确定.
当我们尝试使用void *去修改其指向的对象的值时,系统会报错
void *不是一个对象型指针
void *作用
①一般用来和别的指针比较,
②或者作为函数的输入和输出;
③赋值给另一个void *指针.
小结
①指针也是变量,但是变量是储存着另一个对象的内存地址,这个行为也称为指针变量指向这个对象.
②指针变量可以赋值,指针所指对象在初始化之后可以通过程序改变
③指针变量的命名和其他变量的命名规则一样(作为区分一般在前面加上p 或者 ptr 或者 pointer)
④指针可以是所有基本数据类型、数组和其他所有高级数据结构的地址
⑤若指针已经申明是一种数据类型的地址,那它就不能用于存储其他类型的地址
⑥指针只有赋值后才能使用(指向地址)
引用(reference)
给对象起了另一个小名(引用就是别名)
int int_value = 1;
int& refValue = int_value; //refValue指向int_value
int& refValue2; //错 引用必须初始化.要赋值
注意:
①引用只能绑定在对象上,不能与字面值或某个表达式的计算结果绑定在一起(如 double & dou = 10.0;)
所以指向常量的引用是非法的;(但可以使用 const double& dou = 10.0;)
②引用之前不需要测试有效性.所以引用比使用指针效率高.
③引用并不是一个对象,只是一个别名(未分配内存).
引用和指针的区别
引用其实还是指针,是底层被指针封装后的产物.
获取引用地址时,编译器会进行内部转换
指针和数组
double score[] {0,1,2,3,4};
double * ptr_score = score;
cout << sizeof(score) << "\t" << sizeof (ptr_score) << endl;
cout << &score[0] << "\t" << ptr_score << endl;
将显示如下界面
可以得出以下两个结论:
①数组在内存里是一串连续的地址,而数组名(指针的名字,不带号)就是这块连续内存空间的首地址
②数组里因为存放着5个double类型的数据,所以是58 = 40
而ptr_score是地址,是指针,在32位的编译器里只占4个字节,哪怕声明是double 也只占4个字节.
指针的算术运算
指针的递增和递减(++ --)
也称之为指针的平移
移动的单位是以sizeof(T)的长度来移动.
假如在double数组指针里
0xfafed0 到 0xfafed8 之间移动了一个double数据的长度 即8个字节
指针加减整数值
小tips:
double score[] {0,1,2,3,4};
double * ptr_score = score;
cout << *(ptr_score - 7) << endl;
++num是非法的
所以我们使用指针来*++ptr_num
数组名和首地址
数组名就是这块连续内存单元的首地址
int num[50]; //num是数组名,也可以理解成数组的首地址
num的值与&num[0]的值是相同的
数组的第i+1元素可以表示为:
&num[i + 1] 或 num + i
num[i + 1] 或 *(num + i + 1)
为指向数组的指针赋值:
int * ptr_num = num; 或int * ptr_num = &num[0];
指针变量可以指向数组元素
int * ptr_num = &num[4]; 或 int * ptr_num = num + 4;
动态分配内存(面向对象)
使用new分配/delete释放内存
指针真正的用武之地是在运行阶段来分配未命名的内存来出储存值,而这块内存的访问就只能通过指针.
1.在运行阶段为int值分配一个未命名的内存
2.使用内存来访问(指向)这个值(右->左)注意不要创建两个指向同一块内存的指针,可能误删除两个指针.
int *p = new int;
3.释放内存
delete ptr_int;
指针指向另外的指针,原本的内存没有释放,称之为野指针/内存泄漏
内存的释放用三点注意事项
与new配对使用(不要去释放不是new创建的内存空间)
不释放已经释放的内存(重复释放可能导致被覆盖的内存区域被误释放.)
不释放声明变量分配的内存
delete在C++里不是删除,是释放内存.就好比清空了回收站依旧可以通过软件找回来,只要不覆盖(再次清空回收站),就不是真正意义上的删除.
使用new创建动态分配的数组
int * intArray = new int[10];
delete [] intArray;
使用New为数组分配内存 也要使用delete []释放内存
补充
栈区(stack)
由编译器自动分配释放,一般存放函数的参数值、局部变量的值等
操作方式类似数据结构的栈-先进后出
堆区(heap)
一般由程序员分配释放,若程序不释放,程序结束时可能由操作系统回收
注意:与数据结构中的堆是两回事,分配方式类似链表
全局区(静态区-static)
全局变量和静态变量是储存在一起的
程序结束后由系统释放
文字常量区
常量字符串就放在这里,程序结束由系统释放
程序代码区
存放函数体的二进制代码
int num = 90; //左边栈区 右边常量区
double * dArray = new double[10];// 左边还是栈区 右边是堆区 堆区里的内存的地址被dArray指向
//因为栈区的高效率 所以多采用指针和引用,而事实上执行程序的是堆区.
用指针来创造二维指针
new int[10]是计算机中的一维,
而new int [5][3]是二维
(*p2)
为降维操作,小括号优先级最高.
int *是int 指针类型 像
int [][]
可以理解成前面的[]被换成了(*p2)
输出二维数组
利用数组地址的相邻性我们可以输出数组
p2 + i 与 (p2 + i)的值相同
而(*(p2+i))显示的是1,4,7,11,14
总结
指针是一个变量,储存另一个变量(对象)的内存地址
指针的声明由基本类型、星号(*)和变量名组成
为指针赋值,赋值运算符右侧必须是一个地址.
如果是普通变量需要在前面加一个取地址运算符&;
如果是另一个指针变量或一个数组,不需要加&运算符
运算符*用于返回指针指向的内存地址中储存的值
使用指针访问一维数组和二维数组的元素
题外话
指针由于麻烦的原因,我们用引用多于指针,但是指针其自身的强大是我们不可或缺的工具,使用的场景常常为封装的时候