1.初识指针
1.1指针变量和地址
计算机中的CPU处理数据时通常在内存中读取,为了方便读取数据,就把内存划分一个个小的单元,每个单元就是一个字节,并且每个单元上都会有编号,也可称之为地址,在C语言中又把它称为指针,总结:内存单元编号=地址=指针。
假设我们创建了一个变量a,我们如何知道它的地址呢?我们又怎么使用呢?就要用到&和指针变量。
int main()
{int a=10;
int * pa=&a;
return 0;
}
&是取出a的地址,存在pa指针变量中。那我们如何理解int *呢?*说明pa是指针变量,里面储存的是a的地址,int说明pa中储存的地址指向的是一个整型。那如果有一个char类型的变量c,变量c的地址应该是放在char *类型的变量里面。
那我们又如何使用地址呢?通过解引用操作符*可以改变a的值。
指针变量的大小 :
这个与机器的地址总线有关,假设有三十二个地址总线,一个地址总线代表一个bit位,那么就需要四个字节空间;如果有六十四个地址总线,那么就需要八个字节的空间。利用sizeof(指针变量)就可以求出,但是指针变量的大小与指针变量类型无关,只与平台有关,x86是4个字节,x64是八个字节。
void*指针:
无具体类型的指针变量,可以用来接收各种指针变量的地址,但不可以进行加减运算。利用void*类型指针变量可以实现泛型编程,在函数中作函数参数部分使用,处理多种类型数据。
1.2 二级指针
指针变量也是变量,也会有地址,其地址存放在二级指针里面。
# include<stdio.h>
int main()
{
int a=10;
int*pa=&a;
int**ppa=&pa;
return 0;
}
1.3const修饰指针
int const*p=10或者const int * p=10;(const围绕int转,那么int就不可以改变)
const放在*左边,表示修饰的是指针变量指向的内容不可变(*p不可以变换其他的数),而指针变量本身存的地址可以改变(p=&m,m可以是任意的);
int* const p=10(const围绕p转,那么地址就不可以改变)
const放在*右边,表示修饰的是指针变量本身存的地址不可以改变,而指针变量所指的内容可以改变。
1.4 野指针三种类型以及如何避免野指针
局部变量指针未初始化,默认为随机值;当指针指向的范围超出数组arr时,int *p=&arr【0】;*(p++)=i,i超过范围了;返回局部变量的地址;
指针初始化,赋予NULL空指针;;小心指针越界;避免返回局部变量的地址;
assert断言:
1.5传值调用和传址调用
什么情况下非用到指针呢?
# include<stdio.h>
void Swap(int x ,int y)
{
int temp=0;
temp=x;
x=y;
y=temp;
}
int main()
{
int a=0;int b=0;
sacnf("%d %d",&a,&b);
printf("交换前a=%d,b=%d\n",a,b);
Swap(a,b);
printf("交换后a=%d,b=%d\n",a,b);
return 0;
}
最后的结果一样,因为a与b和x与y是独立的空间,x的地址与a的地址不同,b与y的地址不同,在Swap函数内部交换的xy不影响mian函数的ab;
传参调用结论:实参传递给形参的时候,形参会创建一个空间来接受实参,对形参的修改不影响实参。
如何改进呢?
所以我们就只要把a与b的地址传过去就可以了
# include<stdio.h>
void Swap(int* x ,int* y)
{
int temp=0;
temp=*x;
*x=*y;
*y=temp;
}
int main()
{
int a=0;int b=0;
sacnf("%d %d",&a,&b);
printf("交换前a=%d,b=%d\n",a,b);
Swap(&a,&b);
printf("交换后a=%d,b=%d\n",a,b);
return 0;
}
这个就叫做传址调用。
2.指针与数组
2.1数组名的理解
数组名就是数组首元素的地址,有两个例外,sizeof(arr)和&arr,表示整个数组。
所以&arr[0]=arr &arr[0]+1=arr+1跳过一个元素; &arr+1跳过整个数组的;
2.2指针访问数组
# include <stdio.h>
int main()
{
int arr[10]={0};
int * p=arr;
int i=0;
for(i=0;i<10;i++)
{
scanf("%d",p+i)
}
for(i=0;i<10;i++)
{
printf("%d ",*(p+i));
}
return 0;
}
这里的*(p+i)可以换成 p[i], arr[i] 或者 *(arr+i).
2.3一维数组传参的本质
# include <stdio.h>
void test1(int arr[])
{
int sz=sizeof(arr)/sizeof(arr[0]);
printf("%d\n",sz);
}
void test2(int*arr)
{
int sz=sizeof(arr)/sizeof(arr[0]);
printf("%d",sz);
}
void test3(int*arr)
{
int sz3=sizeof(arr)
printf("%d",sz3);
}
int main()
{
int arr[10]={1,2,3,4,5,6,7,8,9,10};
int sz0=sizeof(arr)/sizeof(arr[0]);
printf("%d",sz0);
test1(arr);
test2(arr);
test3(arr);
rerurn 0;
}
test1与test2是一样的结果,都是1,算不了10,因为一维数组传参的本质是将数组首元素的地址也就是指针传过去,sizeof(arr)计算的也只是一个地址的大小(结果为4or8)【凡是sizeof()括号里面的是地址答案就是4or8,除了sizeof()括号里面真的是一个完整的数组,那么计算的就是整个数组的字节大小】而不是整个数组的大小。
而sz0=10,因为这时sizeof(arr)算的才是整个数组的大小。
总结 :一维数组传参,形参部分既可以写成数组形式也可以写成指针形式。
以及在函数内部算不了数组元素个数的。
2.4指针数组与数组指针变量
指针数组:
是存放指针的数组,即数组里每个元素都是存放指针的。
长这样 int * arr[5] 或者int * p[5] (是数组,一个叫arr,一个叫p,并且是int*类型的)
类比 int arr[5](是数组,是int类型的)
数组指针变量:
存放数组的地址,能够指向数组的指针变量。
长这样 int (*p)[5] .(p与*结合,说明p是一个指针变量,指向一个大小为5的整型数组。)
类比 int * p(存放的是整型变量的地址,就叫做整型变量指针。)
数组指针怎么初始化:
int arr[10]={0}
int (*p)[10]=&arr
int 是p指向的数组元素类型
p是数组指针变量名
{10}是p指向的数组的元素个数
用指针数组模拟二维数组:
# include<stdio.h>
int main()
{
int arr1[]={1,2,3,4,5};
int arr2[]={2,3,4,5,6};
int arr3[]={3,4,5,6,7};
int * parr[3]={arr1,arr2,arr3};
int i=0;
int j=0;
for(i=0;i<3;i++)
{
for(j=0;j<5;j++)
{
printf("%d ",parr[i][j];
}
printf("\n")
}
return 0;
}
解释parr[i]访问的是一维数组,parr[i][j]指向的是一维数组中的某个元素,这里利用到了指针访问数组
二维数组传参的本质:
一维数组传参时指针形式为 int * arr 数组形式int arr[]
二维数组传参时指针形式为 int (*p)[5] 数组形式int arr[3][5]
以前是这样写的
# include <stdio.h>
void test(int arr[3][5],int x,int y )
{
int i=0;
int j=0;
for(i=0;i<x;i++)
{
for(j=0;j<y;j++)
{
printf("%d ",arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5]={{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
test(arr,3,5);
return 0;
}
之后有了数组指针的概念,我们可以这样写
# include <stdio.h>
void test(int (*p)[5],int x,int y )
{
int i=0;
int j=0;
for(i=0;i<x;x++)
{
for(j=0;j<y;j++)
{
printf("%d ",p[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5]={{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
test(arr,3,5);
return 0;
}
注意参数那里一定是写成int(*)[5]的形式,而不是int(*)[3][5].
在内部函数中,p[i][j]可以写成 *(*(p+i)+j),同时p还可以换成arr。
总结:二维数组传参本质上也是传递了地址,传递的第一行这个一维数组的地址。二维数组传参,形参部分既可以写成数组的形式,也可以写成指针的形式。
题目分析:
3.指针变量
3.1字符指针变量
一般使用单个字母的就是和整型指针变量一样。
# include <stdio.h>
int main()
{
char c='a';
char * pc=&c;
*pc='w';
return 0;
}
还有常量字符串使用方式
# include <stdio.h>
int main()
{
const char * pstr="hello world";
printf("%s",pstr);
return 0;
}
结果为 hello world.
解释:上面代码的意思是把字符串首字母‘h'的地址存放在了指针变量ptsr中
# include <stdio.h>
int main()
{
char str1[]="hello world";
char str2[]="hello world";
const char*str3="hello world";
const char*str4="hello world";
return 0;
}
解释:str1与str2不同,因为用相同的常量字符串去初始化不同的数组时,内存中会开辟不同的空间;而str3与str4是相同的,因为str3与str4所存的地址指向的是用一个常量字符串,c/c++会把常量字符串储存到单独一个内存区域,当几个指针指向同一个字符串时,实际上它们所指的是同一块内存。
3.2数组指针变量
3.3函数指针变量
创建:
函数是有地址的,函数名就是函数的地址,也可以通过&函数名的方式获得函数的地址。
要将函数的地址存放起来就要使用到函数指针变量,写法如下
void test()
{
printf("hello world");
}
void (*pf1)()=&test;
void(*pf2)()=test;
int Add(int x,inty)
{
return x+y;
}
int (*pf3)(int x,int y)=&Add;
int(*pf4)(int,int)=Add;//x 与y写与不写都可以
int 是pf3指向的函数返回类型
pf3是函数指针变量名
int x int y 是指向的函数的参数类型
使用:
# include <stdio.h>
int Add(int x,int y)
{
return x+y;
}
int main()
{
int (*p)(int ,int)=Add;
printf("%d\n",p(3,4));
printf("%d\n"(*p)(3,5));
return 0;
}
以上两种使用都可以。
3.4函数指针数组
函数指针数组定义:
int (*parr1[3])()
解释:parr1先和[ ]结合,说明parr1是数组,是 int(*)( ) 类型的,函数指针类型。
3.5 函数指针数组的实现—转移表
# include <stdio.h>
int add(int a,int b)
{
return a+b;
}
int sub(int a,int b)
{
return a-b;
}
int main()
{
int x,y,input;
int ret=0;
int (*p[3])(int, int)={0,add,sub};
printf("请选择:");
scanf("%d",&input);
printf("请输入操作数:")
scanf("%d %d",&x,&y);
ret= p[input](x,y);//这里也可以是 ret=(*p[input])(x,y)
printf("%d\n",ret);
return 0;
}
4.指针使用
4.1回调函数
先举个例子:
# include <stdio.h>
int add(int a,int b)
{
return a+b;
}
int sub(int a,int b)
{
return a-b;
}
void cal(int(*pf)(int a,int b))
{
int a,b,ret;
printf("请输入操作数:");
scanf("%d %d",&a,&b);
ret=pf(a,b);
printf("%d",ret);
}
int main()
{
int input;
printf("********0.exit 1.add 2.sub*******\n" );
printf("请选择");
scanf("%d",&input);
switch(input)
{
case 0:
printf("退出计算机\n");
break;
case 1:
cal(add);
break;
case 2:
cal(sub);
break;
default :
printf("请重新选择\n");
break;
}
return 0;
}
说明:
回调函数就是通过函数指针调用的函数。把函数的地址作为参数传给另一个函数,这个地址被调用其所指向的函数时,被调用的函数就是回调函数。
4.2冒泡排序
# include<stdio.h>
void Bubble_sort(int * arr,int sz)
{
int i=0;int j=0;
for(i=0;i<sz-1;i++)
{
for(j=0;j<sz-1-i;j++)
{
if(arr[j]<arr[j+1])
{
int temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
}
void Print(int*arr, int sz)
{
int i=0;
for(i=0;i<sz;i++)
{
printf("%d ",arr[i]);
}
}
int main()
{
int arr[]={3,8,2,5,1,7,4,6,9,0};
int sz=sizeof(arr)/sizeof(arr[0]);
Bubble_sort(arr,sz);
Print(arr,sz);
return 0;
}
冒泡排序就是将两两元素进行比较,然后排续。
4.3qsort使用
利用qsort函数实现整形数据冒泡排序
# include<stdio.h>
int cmp(const void * p1,const void *p2)
{
return *((int*)p1)-*((int*)p2);
}
Print(int * arr,int sz)
{
int i=0;
for(i=0;i<sz;i++)
{
printf("%d ",arr[i]);
}
}
int main()
{
int arr[]={2,5,1,8,4,9,3,6,7,0};
int sz=sizeof(arr)/sizeof(arr[0]);
qsort(arr,sz,sizeof(int),cmp);
Print(arr,sz);
return 0;
}
解释:qsort函数(待排序数组的第一个元素指针,数组中元素的个数,数组中一个元素的大小(单位字节),函数指针(用来实现冒泡排序))
在实现冒泡排序的函数里要把void*类型转换成int*类型然后在进行解应用比较大小,如果相减为负数,那么不交换顺序,如果相减为正数,p1与p2交换位置。
同理也可以实现排序结构体数据
# include<stdio.h>
# include <string.h>
# include <stdlib.h>
struct Stu
{
char name[20];
int age;
};
int cmp_name(const void* p1,const void* p2)
{
return strcmp(*((struct Stu*)p1)->name,*((struct Stu*)p2)->name);
}
int main()
{
struct Stu arr[]={{"zhangsan",20 },{"lisi",35},{"wangwu",18}};
int sz=sizeof(arr)/sizeof(arr[0]);
qsort(arr,sz,sizeof(arr[0]),cmp_name);
return 0;
}
注意在比较函数中结构体中操作符的使用。将name换成age也同样适用。比较字符串时,利用strcmp函数,包含头文件#include<string.h>,qsort函数要包含头文件<stdlib.h>
4.4qsort函数模拟实现
使用回调函数来模拟实现qsort函数
# include <stdio.h>
void swap( char*buf1, char*buf2,int width)//注意这里的指针类型是char型的
{
int i;
char temp;
for(i=0;i<width;i++)//创建char类型的变量是为了将每个元素分割成width份,然后进行每份每份的交换
{
temp=*buf1;
*buf1=*buf2;
*buf2=temp;//注意这里不是交换地址,而是交换数值,所以要解应用
buf1++;
buf2++;
}
}
int cmp_int(const void*p1,const void*p2)//传过来的指针类型不确定
{
return *(int*)p1-*(int*)p2;//强制转换成int*类型(可以实现多种类型数据比较)然后解应用进行比较
}
//不确定传过来的指针类型 //注意这里的写法
void Bubble_sort(void*base,int sz,int width, int (*cmp)(const void*p1,const void*p2))
{
int i,j;
for(i=0;i<sz-1;i++)
{
for(j=0;j<sz-1-i;j++)
{
if(cmp((char*)base+j*width,(char*)base+(j+1)*width)>0)//强制转换成char*类型,这样可以实现广泛性
{
swap((char*)base+j*width,(char*)base+(j+1)*width,width);
}
}
}
}
int main()
{
int i;
int arr[10]={9,8,7,6,5,4,3,2,1,0};
int sz=sizeof(arr)/sizeof(arr[0]);
Bubble_sort(arr,sz,sizeof(arr[0]),cmp_int);
for(i=0;i<sz;i++)
{
printf("%d ",arr[i]);
}
return 0;
}
将数组改成其他类型数据,如结构体的年龄,姓名等等,就可以实现广泛类型的排序。
5.
5.1sizeof与strlen对比
前提
# include<stdio.h>
# include<string.h>
int main()
{//sizeof是操作符,计算的是变量所占空间的大小
int a = 10;
printf("%d\n", sizeof(a));//4
printf("%d\n", sizeof(int));//4
int arr[10]={0};
printf("%d\n", sizeof(arr));//40
//strlen是函数求字符串或者字符数组的长度
char arr1[3] = {'a', 'b', 'c'};//后面无/0;
char arr2[] = "abc";//后面有/0
printf("%zd\n", strlen(arr1));//所以这里的值是随机值
printf("%zd\n", strlen(arr2));//这里的值是3
printf("%zd\n", sizeof(arr1));//3,数组里存放字符
printf("%zd\n", sizeof(arr2));//4,字符串加后面的/0,共有四个字符
int arr3[]={1,2,3,4,5};
printf("%zd\n", strlen(arr3));//1,因为存储时是01 00 00 00 02 00 00 00....所以1后面就是/0
return 0;
}
sizeof的分析:
一维数组
# include<stdio.h>
# include<string.h>
int main()
{//数组名是首元素的地址,除了&arr,和sizeof(arr)
//sizeof计算地址,答案就是4(x64)/8(x86),
int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));//16,数组名放在sizeof里,计算的是整个数组的大小
printf("%d\n",sizeof(a+0));//4/8 ,数组名a并没有单独放在sizeof里面,这里的a是首元素的地址,计算的的是首元素的地址,
printf("%d\n",sizeof(*a));//4,a此时是首元素地址,*a=a[0]
printf("%d\n",sizeof(a+1));//4/8,a此时是首元素地址,+1还是地址,
printf("%d\n",sizeof(a[1]));//4
printf("%d\n",sizeof(&a));//4/8此时这里的a是整个数组,&a表示整个数组的地址,
printf("%d\n",sizeof(*&a));//16,因为*&a=a,相当于sizeof(a)
printf("%d\n",sizeof(&a+1));//4/8,&a表示整个数组,+1,跳过整个数组后的那个地址
printf("%d\n",sizeof(&a[0]));//4/8,求的也是地址的大小
printf("%d\n",sizeof(&a[0]+1));//4/8,相当于a[1],但还是地址
return 0;
}
二维数组
# include<stdio.h>
# include<string.h>
int main()
{//数组名是首元素的地址,除了&arr,和sizeof(arr)
//sizeof计算地址,答案就是4(x64)/8(x86),
int a[3][4] = {0};
printf("%d\n",sizeof(a));//48,a表示整个数组
printf("%d\n",sizeof(a[0][0]));//4
printf("%d\n",sizeof(a[0]));//16,a[0]表示第一行的数组名,单独放在sizeof内部,计算的是数组的大小
printf("%d\n",sizeof(a[0]+1));//4/8,a[0]并没有单独放在sizeof内部,所以是表示第一行数组的首元素,+1则表示下一个元素的地址
printf("%d\n",sizeof(*(a[0]+1)));//4,a[0]表示第一行第一个元素的地址,+1解应用,表示的是下一个元素
printf("%d\n",sizeof(a+1));//4/8,a没有单独放在sizeof中,所以表示的是首元素的地址——也就是第一行的地址,+1,表示第二行的地址
printf("%d\n",sizeof(*(a+1)));//16,没有单独放在sizeof中,所以表示的是首元素的地址——也就是第一行的地址,+1,表示第二行的地址 ,解应用表示第二行数组
printf("%d\n",sizeof(&a[0]+1));//4/8,&a[0]表示取出的是第一行的地址,+1,表示取出的是第二行的地址
printf("%d\n",sizeof(*(&a[0]+1)));//16,从上可以得出,解应用整个第二行
printf("%d\n",sizeof(*a));//16,a没有单独放在sizeof里面,a表示首元素地址==第一行,解应用
printf("%d\n",sizeof(a[3]));//16,sizeof不会真的访问,a[3]表示第四行数组名,单独放在sizeof里,计算的是数组大小
return 0;
}
字符数组
# include<stdio.h>
# include<string.h>
int main()
{//数组名是首元素的地址,除了&arr,和sizeof(arr)
//sizeof计算地址,答案就是4(x64)/8(x86),
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));//6,字符数组后面没有/0
printf("%d\n", sizeof(arr+0));// 4/8,并没有单独放在arr里面,也没有取地址,所以arr表示的就是首元素的地址
printf("%d\n", sizeof(*arr));//1,此时arr就是首元素地址
printf("%d\n", sizeof(arr[1]));//1
printf("%d\n", sizeof(&arr));//4/8, 尽管取出的是整个数组的地址,也是地址
printf("%d\n", sizeof(&arr+1));//4/8,同理
printf("%d\n", sizeof(&arr[0]+1));//4/8,同理
return 0;
}
字符串数组
# include<stdio.h>
# include<string.h>
int main()
{//数组名是首元素的地址,除了&arr,和sizeof(arr)
//sizeof计算地址,答案就是4(x64)/8(x86),
char arr[] = "abcdef"; //后面有/0
printf("%d\n", sizeof(arr));//7,arr表示整个数组,计算整个数组的大小,有六个字符加上/0字符
printf("%d\n", sizeof(arr+0));//4/8,arr表示首元素地址
printf("%d\n", sizeof(*arr));//1,*arr表示首元素a,
printf("%d\n", sizeof(arr[1]));//同理为1
printf("%d\n", sizeof(&arr));//4/8,arr表示整个数组地址
printf("%d\n", sizeof(&arr+1));//4/8
printf("%d\n", sizeof(&arr[0]+1));//4/8
return 0;
}
常量字符串
# include<stdio.h>
# include<string.h>
int main()
{//数组名是首元素的地址,除了&arr,和sizeof(arr)
//sizeof计算地址,答案就是4(x64)/8(x86),
char *p = "abcdef";//常量字符串,相当于把首字符的地址放到p里面去了
printf("%d\n", sizeof(p));//4/8,p是指针变量,相当于计算指针变量的大小
printf("%d\n", sizeof(p+1));//4/8,p+1是第二个元素的地址
printf("%d\n", sizeof(*p));//1,p是指针变量,指针变量解应用访问指针指向的对象,因为是char*指针,解应用只能访问一个字符
printf("%d\n", sizeof(p[0]));//1, p[0]=*p
printf("%d\n", sizeof(&p));//4/8,括号里表示取得是指针变量p的地址,二级指针
printf("%d\n", sizeof(&p+1));//4/8
printf("%d\n",sizeof(&p[0]+1)//4/8,&p[0]指的是拿出a的地址,+1则指拿出b的地址
return 0;
}
strlen分析
字符数组
# include<stdio.h>
# include<string.h>
int main()
{//数组名是首元素的地址,除了&arr,和sizeof(arr)
//sizeof计算地址,答案就是4(x64)/8(x86),
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", strlen(arr));//随机值,arr表示首元素地址,字符数组后面没有/0
printf("%d\n", strlen(arr+0));//随机值,arr表示首元素
printf("%d\n", strlen(*arr));//*arr首元素,相当于将a传过去,a的ASCII是97,strlen会认为97就是地址,然后访问,error
printf("%d\n", strlen(arr[1]));//同理
printf("%d\n", strlen(&arr));//随机值,即使是将整个数组地址取出,也还是从首元素开始访问,访问不到/0
printf("%d\n",strlen(&arr+1);//随机值,从整个数组后面开始访问,但还是访问不到/0
printf("%d\n",strlen(&arr[0]);//随机值
return 0;
}
字符串数组
# include<stdio.h>
# include<string.h>
int main()
{//数组名是首元素的地址,除了&arr,和sizeof(arr)
//sizeof计算地址,答案就是4(x64)/8(x86),
char arr[] = "abcdef";//后面有/0
printf("%d\n", strlen(arr));//6,arr是首元素地址,strlen从第一个元素访问,统计/0前字符的个数
printf("%d\n", strlen(arr+0));//6,arr首元素地址
printf("%d\n", strlen(*arr));//arr是首元素,所以*arr为a,strlen认为97就是地址,error
printf("%d\n", strlen(arr[1]));//同上
printf("%d\n", strlen(&arr));//6,这里的arr是整个数组的地址,与首元素一样指向同一个位置 ,strlen也是从第一个开始访问的
printf("%d\n", strlen(&arr+1));//随机值,括号里表示跳过一个数组从后面开始
printf("%d\n", strlen(&arr[0]+1));//5
return 0;
}
常量字符串
# include<stdio.h>
# include<string.h>
int main()
{//数组名是首元素的地址,除了&arr,和sizeof(arr)
//sizeof计算地址,答案就是4(x64)/8(x86),
char *p = "abcdef";
printf("%d\n", strlen(p));//6,首字符的地址交给strlen,从此开始访问
printf("%d\n", strlen(p+1));//5,从第二个字符开始访问
printf("%d\n", strlen(*p));//*p=a,strlen直接把97当作地址开始访问,error
printf("%d\n", strlen(p[0]));//p[0]=a同理,error
printf("%d\n", strlen(&p));//随机值,把p的地址取出来,此时与a的地址无关了,相当于是二级指针
printf("%d\n", strlen(&p+1));//随机值,从p的地址后面开始,无法确定有没有/0
printf("%d\n", strlen(&p[0]+1));//5,这是括号里相当于是b的地址,从后开始访问
return 0;
}
5.2指针运算
题目1:
#include <stdio.h>
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
printf( "%d,%d", *(a + 1), *(ptr - 1));
return 0;
}
//程序的结果是什么?//2,5
题目2:
//在X86环境下
//假设结构体的??是20个字节
//程序输出的结果是啥?
struct Test
{int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p = (struct Test*)0x100000;
int main()
{
printf("%p\n", p + 0x1);//p是结构体指针,+1跳过一个结构体,也就是20个字节,但因为是十六进制,所以变为0x100016
printf("%p\n", (unsigned long)p + 0x1);//p转化成整数,+1跳过1个字节,所以是0x100001;
printf("%p\n", (unsigned int*)p + 0x1);//+1跳过一个unsigned int类型,是四个字节,所以0x100004
return 0;
}
题目3:
#include <stdio.h>
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
int *p;
p = a[0];
printf( "%d", p[0]);//1
return 0;
}
//解释:
//因为是(),不是{}所以取两个数中较大的那个,所以真实的存放是{1,3,5}
题目4:
//假设环境是x86环境,程序输出的结果是啥?
#include <stdio.h>
int main()
{
int a[5][5];
int(*p)[4];
p = a;
printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
return 0;
}//第二个为-4,第一个为地址,所以打印-4的补码(转换成十六进制),当作地址:FFFFFFFC
题目5:
#include <stdio.h>
int main()
{
int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int *ptr1 = (int *)(&aa + 1);
int *ptr2 = (int *)(*(aa + 1));
printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
return 0;
}
//答案:10,5
题目6:
题目7:
#include <stdio.h>
int main()
{
char *c[] = {"ENTER","NEW","POINT","FIRST"};
char**cp[] = {c+3,c+2,c+1,c};
char***cpp = cp;
printf("%s\n", **++cpp);//POINT
printf("%s\n", *--*++cpp+3);//ER
printf("%s\n", *cpp[-2]+3);//ST
printf("%s\n", cpp[-1][-1]+1);//EW
return 0;
}