C++指针相关知识详解
一:内存和内存地址
内存可以说是电脑里的内存条,内存大小就是内存条的容量。操作系统和程序在运行过程中都需要用到不同的数据,而运行过程中用到的数据都保存在内存中。
程序运行时,操作系统首先把程序从硬盘读入到内存中,然后再把内存里面的代码指令读取到CPU内运行,这是因为硬盘的读写速度相对于CPU的运算速度慢很多,把数据先读到内存里面,减少CPU等待读取指令的时间,以提高CPU的利用率。
内存按照逐个字节进行划分,操作系统为每个字节的内存按顺序给一个编号,而这个编号就是内存地址,那么操作系统和应用程序就可以通过内存地址快速找到这段内存,然后对内存中的数据进行操作。
如:
此时对变量a的操作就变成对内存地址为245-248的操作,系统为了方便管理,一般变量的地址用地址的第一个字节的地址表示此时变量a的地址就是245,从245开始的四个字节就是变量a存储的四个字节
变量a被销毁时,内存就得到释放,原来存储变量a的地址就变为空闲状态。
内存数据有几点规律:
1:计算机中的所有数据都是以二进制存储的
2:数据类型决定了占用内存的大小
3:占据内存的地址就是地址值最小的那个字节的地址。
获取变量地址的方法:
取地址符:&
获取变量地址:&已经声明的变量
#include<iostream>
using namespace std;
int main()
{
int a=233;
cout<<&a;
return 0;
}
运行结果:
计算机内部,数据都是以2进制的形式保存和处理,所以,内存地址也应以2进制的形式表示,但是以2进制表示又很长,所以cout默认以十六进制显示。
内存地址的占用大小:
内存地址的占用大小由操作系统的位数决定:
16位操作系统的内存地址占用大小是16位,即2字节。
32位操作系统的内存地址占用大小是32位,即4字节。
64位操作系统的内存地址占用大小是64位,即8字节。
128位操作系统的内存地址占用大小是128位,即16字节。
操作系统的位数也叫做寻址位数,假设操作系统的位数是32位,就会用32位的数据作为内存地址
一共2的32次方个整数,即4GB内存。
#include<iostream>
using namespace std;
int main()
{
char a=0;
int b=233;
long long c=666;
double d=10.24;
cout<<sizeof(&a)<<endl;
cout<<sizeof(&b)<<endl;
cout<<sizeof(&c)<<endl;
cout<<sizeof(&d)<<endl;
}
32位操作系统运行结果:
64位操作系统运行结果:
二:指针
内存地址一般用十六进制的数值表示,也就是说内存地址就是数值。不过为了和普通的数值区分保存,就使用了特殊的变量来保存内存地址,这个变量就叫做指针。
数据类型 a;
数据类型 *p=&a;
假设有一个double类型的变量,指针的类型就是变量的类型后面加上 *,即指针的类型就是double *。以下代码就是取出变量value的地址并且用指针pointer保存,其中,int *pointer = &value;可以写成auto pointer = &value;:
#include <iostream>
int main(void)
{
int value = 100;
int *pointer = &value; // value类型是int, 所以pointer类型是int *
std::cout << "变量value的地址:" << &value << std::endl; // 变量value的地址
std::cout << "指针pointer保存的值:" << pointer << std::endl; // 变量pointer保存的值
return 0;
}
指针是变量,换句话说,指针能够保存一个数值,它自身也是有内存的。所以同样也可以取指针的内存地址:
#include <iostream>
using namespace std;
int main(void)
{
int value = 100;
int *pointer = &value; // value类型是int, 所以pointer类型是int *
int **ptrtopointer = &pointer; // pointer类型是int *, 所以ptrtopointer类型是int **
cout << "变量value的地址:\t\t" << &value << endl; // 变量value的地址
cout << "指针pointer保存的值:\t\t" << pointer << endl; // 变量pointer保存的值
cout << "指针pointer的地址:\t\t" << &pointer << endl; // 变量pointer的地址
cout << "指针ptrtopointer保存的值:\t" << ptrtopointer << endl; // 变量ptrtopointer保存的值
cout << "指针ptrtopointer的地址:\t" << &ptrtopointer << endl; // 变量ptrtopointer的地址
return 0;
}
易混淆的混淆的几个概念:
指针保存的地址所代表的内存空间,将其称为指针所指向的内存空间。
如何根据内存地址去操作内存空间呢?
*内存地址:在内存地址前加一个*号即代表内存地址所指向的内存空间
例如:
#include <iostream>
using namespace std;
int main(void)
{
int a=2358;
cout<<"变量a保存的值: "<<a<<endl;
cout<<"变量a的内存地址: "<<&a<<endl<<endl;
cout<<"内存地址所代表的内存空间里面保存的值: "<<*(&a)<<endl;
cout<<"变量a保存的值: "<<a<<endl;
cout<<"变量a的内存地址: "<<&a<<endl<<endl;
*(&a)=666;
cout<<"变量a保存的值: "<<a<<endl;
cout<<"变量a的内存地址: "<<&a<<endl<<endl;
cout<<"内存地址所代表的内存空间里面保存的值: "<<*(&a)<<endl;
return 0;
}
也可以通过指针变量操作,如:
int a=2358;
cout<<a<<endl;
cout<<&a<<endl;
int *p=&a;
cout<<p<<endl;
*p=666;
cout<<a;
易混淆的混淆的概念:
三:空指针
有时候声明一个指针后暂时不需要保存变量地址,这个时候声明的指针请务必要初始化为nullptr,防止由于粗心大意对这个指针进行操作而导致程序中的数据错乱甚至程序崩溃。还是那句话,声明变量记得初始化。
int *pointer = nullptr;
关键字nullptr是只能赋给指针的一个值,它的意思是空指针。当你不再需要用到指针保存的地址时,就应该将指针赋值nullptr。
关键字nullptr实际就是内存地址值:0。内存地址0是一个特殊的内存地址,操作系统不会用地址0来表示内存位置。而当你对地址0进行读写操作时,程序就会崩溃。这样做的目的很简单,当不需要再用到指针保存的地址时,将指针赋值为空指针,在你不小心再对该指针进行操作的时候程序崩溃,你就会马上发现自己做错了。
int *pointer= nullptr;
cout<<“指针变量pointer保存的地址”<<pointer<<endl;
return 0;
四:野指针
野指针保存的内存地址是不确定的,如果对野指针所指向的内存进行操作容易造成程序错乱、系统崩溃,
所以最好将野指针赋值为空指针
1:int *pointer;
2:int *pointer=nullptr;
{
int number=0;
pointer=&number;
}
cout<<pointer<<endl;
改进:
1:int *pointer=nullptr;
2:int *pointer=nullptr;
{
int number=0;
pointer=&number;
pointer=nullptr;
}
cout<<pointer<<endl;
五:指针的运算
指针的加法运算会跳过多少个字节是由指针的类型决定的。
#include <iostream>
using namespace std;
int main(void)
{
int value=100;
int *pointer=&value;
cout<<"指针变量pointer保存的地址: "<<pointer<<endl;
cout<<"指针变量pointer保存的地址+1: "<<pointer+1<<endl;
cout<<"指针变量pointer保存的地址-3: "<<pointer-3<<endl;
cout<<"指针变量pointer保存的地址+5: "<<pointer+5<<endl;
return 0;
}
指针的运算的简化和特殊用法:
pointer=pointer+n < - - > pointer+=n;
pointer=pointer-n < - - > pointer-=n;
pointer=pointer+1 < - - > pointer++或者++pointer;
pointer=pointer-1 < - - > pointer-- 或者--pointer;
*(pointer+n); < - - > pointer[n];
六 void 类型指针
可以用void* 类型指针来存储任何类型变量的地址:
using namespace std;
int main(void)
{
short a=0;
float b=0.0;
long long c=0;
void *p1=&a;
void *p2=&b;
void *p3=&c;
cout<<"指针变量p1保存的内存地址: "<<p1<<endl;
cout<<"指针变量p2保存的内存地址: "<<p2<<endl;
cout<<"指针变量p3保存的内存地址: "<<p3<<endl;
return 0;
}
但是void* 类型指针不能进行解引用或者加减运算,因为不知道它所指向的变量类型。
七:const修饰的指针类型
1:const修饰指针变量所指向的数据类型,指针变量所指向的内存空间内的值不可改变,指针变量保存的内存地址可以改变:
int number1=233;
int number2=666;
const int *pointer=&number1;
//*pointer=666; 编译出错
pointer=&number;//编译可以通过
2:const修饰指针变量后,指针变量所保存的内存地址不可改变,但是其所指向的内存空间内的值可以改变:
int number1=233;
int number2=666;
int * const pointer=&number1;
//pointer=&number2; 编译出错
*pointer=666;//编译通过
3:同时用const 修饰时,都不可改变
const int * const pointer=&number1;
八:精典题目
1:
void GetMemory(char* p)
{
p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(str);
strcpy(str,"hello world");
printf(str);
}
运行的结果是什么?
2:
int array[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
int *p = array;
p += 5;
int* q = NULL;
*q = *(p+5);
printf("%d %d",*p,*q);
运行结果是什么?
3:
int arr[5] = {0,1,2,3,4};
const int *p1 = arr;
int* const p2 = arr;
*p1++;
*p2 = 5;
printf("%d,%d",*p1,*p2);
*p1++ = 6;
*p2++ = 7;
printf("%d %d \n", *p1, *p2);
第一道题:我们一看函数传递,指针只是浅拷贝,申请的内存在临时对象p中,并没有传递到函数外面,然后又对str地址进行写操作,str初始地址为NULL,不能进行书写,所以系统会崩溃。
第二道题:一看很开心是指针类型的加减法,下标从0开始,但是数字从1开始,所以应该是6 11。但是你忽略了q是一个NULL指针,不能进行书写,所以会崩溃。
第三道题:指针指向数组,数组退化成指针,前两个指针操作是对的。但是后面*p1++ = 6; 不可以通过p1进行值的修改,*p2++ = 7;不能对p2进行修改。所以这道题是编译出错。
参考:1:http://www.xiaoguyin.com/wiki/001518793008149c2a6c34c70d44d60bc3cb34948cd02ea000/0015196541724418172fca2571a403ba35704fc5085f1a0000
2:https://blog.youkuaiyun.com/weixin_39640298/article/details/84900326
3:【小古银】NOIP信息学奥赛教程