计算机里面有内存,一般有4G,8G的可以用来使用。 为了更好的管理内存,将内存分隔成一个一个的内存单元,一个内存单元占用一个字节,并且给所有的内存单元编号,以此来标识他们。
在现实生活中,我们建立一幢幢楼房,给楼房里面的每个房间一个地址,以此来区分房间。所以这里的地址就相当于上面的编号。根据地址可以找到具体的房间/内存单元,也可以说地址是指向了这个房间/内存单元。所以也称地址为指针。
当我们要存储地址时,要声明某个变量来存储地址,我们称这个变量为指针变量。
指针的大小
我们可以使用sizeof这个关键字来得到指针的大小。单位是字节。
int main()
{
printf("%d\n",sizeof(int *));
printf("%d\n",sizeof(short *));
printf("%d\n",sizeof(char *));
printf("%d\n",sizeof(float *));
printf("%d\n",sizeof(double *));
printf("%d\n",sizeof(long *));
printf("%d\n",sizeof(long long *));
}
最后的结果是
可以看到最后的结果都是8。为什么最后的结果都是8呢?
计算机分为32位的和64位的,32位的通过产生电信号会产生一个32位的二进制序列,这个二进制序列就是地址。64位的同理。指针变量中存放的是二进制序列,那么这个二进制序列的大小就是指针的大小。转换为字节单位,32 bit = 4 byte,64 bit = 8 byte,那么我的电脑是64位的,最后的结果当然都为8。
所有类型的指针变量大小都是一样的,那么指针变量的类型有什么意义呢?
指针变量的类型意义
int main()
{
int a = 100000;
char* pc = &a;
*pc = 200000;
}
创建整型变量a,赋值为100000。创建字符指针变量pc,存储a的地址。通过解引用pc给a赋值为200000。最后a的值会被更改吗?会被更改为200000吗?
a的值会被更改,但是不会被更改为200000。因为这段程序中通过字符型的指针变量来修改。字符型的变量大小是1字节,所以只能修改一个字节的值,而整型是占四个字节的,剩余三个字节没有被修改,最后得出的结论就是最后的值了。
这是100000在内存中以如下方式被存储
这是200000在内存中以如下方式被存储
这是用字符型指针变量pc去解引用赋值整型变量a后,a的值在内存中的修改结果。第一个字节的确修改了,但是后面的字节没有被修改,结果就是会产生另一个既不是100000也不是200000的值。
总结:指针变量的类型决定解引用时可操作的大小
int main()
{
int arr[10] = {0};
char* pc = arr;
int i = 0;
for(i=0; i<10; i++)
{
*(pc+i) = i;
}
return 0;
}
创建有10个元素的整型数组arr,创建字符指针变量pc存储数组的首位元素地址。用一个for循环循环10次,给arr中的每一个元素赋值。
这是arr初始化后的样子,每两位(00)代表一个字节,前40个字节是arr数组占用的空间
我们希望通过循环给数组中的10个元素赋值为0,1,2,3,4,5,6,7,8,9,内存中的数据应如下图所示
但我们通过字符型指针变量去找整型指针变量,结果将会变成这样。
pc+i是从pc开始跳过i个pc类型的元素,然后指向这个元素,*(pc+i)就是解引用这个元素,可以给他赋值。
总结:指针变量的类型决定了指针+整型数值时跳过的距离
野指针
指向不确定的指针就是野指针
未初始化就使用
int main()
{
int* pi;
*pi = 20;
}
上面这段代码中创建了指针变量,但是没有初始化他就解引用赋值。未初始化的指针变量程序会给他一个随机值,我们不知道是什么地址,此时直接解引用赋值,就会改变未知区域的值,这是十分危险的操作。
数组越界
int main()
{
int arr[10] = 0;
int* pi = arr;
int i = 0;
for(i=0; i<=10; i++)
{
*pi = i;
pi++;
}
}
声明一个数组,并初始化为0。指针pi指向数组第一个元素,i初始值为0。
循环开始,i值改变,数组中的元素值改变,pi也在移动
当i增加到10时,arr数组已经遍历完成了,此时pi指向数组之外的下一个元素。但根据代码还要给当前pi所指向的地址赋值,pi所指向的地址是不确定的,未知的,这时赋值是不安全的,不可取的。
指针指向的空间被释放了
int *test()
{
int a = 10;
return &a;
}
int main()
{
int *p = test();
printf("%d\n", *p);
}
在main函数中,一开始调用了一个test函数,并且接收返回值给p。接着输出p所指向的值。 test函数中声明变量a,返回a的地址。
test函数中的a是局部变量,在test函数结束以后,这个变量就被释放了,操作系统会怎么处理它,或是把它分给了其他的程序,这些我们并不知道。此时在main函数中又去访问它或是改变它的值是不合法的。
指针运算
指针与数值的运算
int main()
{
int arr[10] = {0};
int* p = arr;
int i=0;
for(i=0; i<10; i++)
{
*(p+i) = i;
}
}
开始时p指向arr数组首元素起始位置。p+i,当i=0时,p仍指向首元素起始位置;当i=1时,p指向当前指向位置再往后一个元素的位置;当i=2时,p指向当前指向位置再往后两个元素的位置......这里很重要的是一个元素的大小是多少,因为这是整型指针,一个元素的大小就是4个字节。这个知识点在前面的指针类型的意义就曾说到了。
总结:pi+i运算是指针指向当前位置往后i个元素的位置
指针与指针的运算
int my_strlen(char* str)
{
char* start = str;
char* end = str;
while(*end != '\0')
{
end++;
}
return end-start;
}
int main()
{
char arr[] = "i want offer!";
int len = my_strlen(arr);
printf("%d\n",len);
return 0;
}
my_strlen函数可以计算字符串长度。它的实现方式是令一个指针指向字符串开头,另一个指针指向字符串结尾('\0'前面那个字符),然后指向结尾的指针减去指向开头的指针,就是字符串的长度。
总结:指针-指针结果是两个指针之间的元素个数。指针必须指向同一个数组
指针的关系运算
int main()
{
int arr[5] = {0};
int* pi = NULL;
for(pi = &arr[5]; pi>=&arr[0])
{
*--pi = 1;
}
}
指针和数组
我们前面说到数组名代表的首元素地址,这在大多数情况下是适用的,但有两种情况下不适用。
&数组名
当使用"&数组名"时,数组名就不表示首元素地址,而表示整个数组。&数组名意思是取整个数组的地址。
int main()
{
int arr[10] = {0};
printf("arr ----------> %p\n", arr);
printf("arr+1 --------> %p\n", arr+1);
printf("&arr[0] ------> %p\n", &arr[0]);
printf("&arr[0]+1 ----> %p\n", &arr[0]+1);
printf("&arr ---------> %p\n", &arr);
printf("&arr+1 -------> %p\n", &arr+1);
return 0;
}
从最终的运行结果我们可以看到,arr和&arr[0]的结果相同,所以arr此时是代表数组首元素地址的。但&arr不是说代表的整个数组的地址吗?此时为什么与&arr[0]的结果相同呢?
&arr代表整个数组的地址,这个数组也是从首元素开始的,所以&arr的结果是首元素地址。
令arr,&arr[0],&arr+1都+1,结果是怎样的呢?
arr和arr+1,&arr[0]和&arr[0]+1都只相差4个字节,arr是整型数组,里面的元素都是整型,而整型大小就是4个字节。&arr和&arr+1之间相差40个字节。arr数组中有10个元素,一个元素4个字节,正好是40个字节。
sizeof(数组名)
使用sizeof关键字获取数组的大小,此时数组名代表的是这个数组,因为它要计算整个数组的大小。
printf("sizeof(%d) --> %d\n", sizeof(arr));
二级指针
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;
return 0;
}
我们之前声明的指针都是一级指针,它都是指向基本类型(char,int,float,double等)的变量。二级指针是指向指针的指针,上面代码中的ppa就是一个二级指针。
既然有二级指针,同样会有三级指针,四级指针......以此类推 int**,最右边的*代表这是一个指针,前面剩下的部分代表这个指针的类型。所以我们可以说ppa是一个整型指针类型的指针。
指针数组
指针数组,落脚点在数组,指针数组是一个数组。什么样的数组呢?元素全是指针的数组。 数组是存储相同类型数据的集合,里面同样可以全部存储指针
int mian()
{
int a = 10;
int b = 20;
int c = 30;
int* pa = &a;
int* pb = &b;
int* pc = &c;
int* arr[3] = {pa, pb, pc};
int i=0;
for(i=0; i<3; i++)
{
printf("%p------%d",arr[i],*arr[i]);
}
return 0;
}
指针数组里面存储的是指针,指针也就是地址,所以也可以说指针数组里面存储的是地址,打印arr[i]的结果就是打印出地址。arr[i]是解引用,打印arr[i]的结果是打印地址指向的值。