学习C语言指针

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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值