学习C(十二)


① 指针偏移的概念
② 指针偏移的规则:偏移按模块进行,模块大小取决于基础数据类型
③ void* 通用指针的基本使用方式,以及void** 不是通用指针
④ void* 指针偏移的思路
  任意类型的数据,都可以在数据类型后面加上* ,形成他的指针类型。例如int->int*,char*->,int*->int**,void->void*,void*->void**。void* 并不代表空指针,没有空指针这个数据类型,只有空指针这个数据,用NULL来表示空指针
  void* 代表通用指针,他可以指向任意类型的指针数据。
  void* 只有在指向的时候是非常方便的,但是他在取值的时候却非常难用。
  指针的取值机制:由于指针无论指向多大的内存空间,该指针变量只能保存第一个字节的内存地址,那么在对该指针取值的时候,系统会判断该指针的基础数据类型,然后根据该类型去正确的访问n个字节的内存空间,然后对该段内存空间取值。
  所以如果对void* 取值的话,就需要void* 的基础数据类型,得到void类型,void类型多少字节不知道。所以void* 通用指针不能直接取值

指针的偏移

指针指向了一段连续内存空间中的首地址,那么可以通过对该指针向前或者向后偏移来访问到前面或者后面的内存地址
  指针偏移通过对该指针进行加法或者减法来实现:
    例如 int arr[5] = {1,3,5,7,9};
    arr 现在指向了1的地址
    arr+n代表着以arr为首地址,向后偏移n个模块
    注意:一个模块的大小完全取决于该指针的基础数据类型

数组指针与指针数组

指针数组:他是一个数组,什么样的数组?存放的元素是指针的数组
数组指针:他是一个指针,什么样的指针?指向了一个模块为数组的指针
  int arr[5] = {1,2,3,4,5};
  int* p = arr;
  数组指针的声明:指针类型+(* 指针名)[指向的数组的容量]
  指针类型完全取决于:指向的数组中存放的数据类型
  数组指针:仅仅在声明的时候很复杂,但是在使用的时候非常简单,因为数组指针的使用方式和多级指针一模一样。

关于通用化设计的指针的偏移

由于void* 可以指向任意类型的数组,在对数组访问的时候,只有进行强制类型转换后,才能对void* 指针取值。
  那么,现在的问题为:一个指向了特定数据类型数组的void* 指针,他仅仅只能代表首地址,即使强转之后取* ,也只能获得首个元素。
  那么,想要获得剩余的数组元素,必须对这个void* 指针做偏移。
  我们可以在函数传参的时候,将这个数组中每一个元素的字节大小size当成参数传入。然后函数内部,对这个void* 做强制类型转换,统一转成char*,然后在偏移的时候只要对偏移量*size就能准确的偏移到对应的数据上

#include<stdio.h>

//指针变量以p开头
//通用指针变量以vp开头,可以指向任意类型的指针类型
int main(){
	int a = 5;
	int* pa = &a;//pa->a->5
	void* vpa = pa;//vpa = pa->a->5
	void* vppa = &pa;//vppa->pa->a
	void** vvpa = &vpa;//vvpa->vpa=pa->a->5 
	void* _vvpa = &vpa;//_vvpa->vpa=pa->a
    
    //当void* 类型指向其他类型的指针时,使用时要先强制转换
    //void** 不是通用指针
    printf("*pa = %d\n",*pa);//得到a的值
    printf("*vpa = %d\n",*(int*)vpa);//得到a的值
    printf("*(int*)vppa = %d\n",*(int*)vppa);//得到pa的指向,pa指向a
    printf("*(int*)*vvpa = %d\n",*(int*)(*vvpa));//二级指针可以两次取值,
    //而void** vvpa不是通用指针,所以可以*vvpa得到void* vpa,vpa是通用指针要强制转换,*(int*)vpa得到a的值
    printf("*(int*)_vvpa = %d\n",*(int*)_vvpa);//得到vpa的指向,vpa指向a
	return 0;
}

指针偏移

#include<stdio.h>

int main(){
	int arr[6] = {1,3,5,7,9,11};//一个模块4个字节,6个模块共24个字节
	printf("%d\n",*((short*)arr+4));//short类型1个模块2个字节,移动4个模块(8个字节),访问到数字5
/*
	对arr做偏移,只能偏移两次,每次只能偏移1个模块,要求访问到数据9
	对arr做偏移,只能偏移两次,每次只能偏移1个模块,要求访问到数据3
	对arr做偏移,只能偏移两次,每次只能偏移1个模块,要求访问到数据7
	对arr做偏移,只能偏移两次,每次只能偏移1个模块,要求访问到数据11
*/
	printf("%d\n",*(int*)((double*)arr+1+1));//注意:数组名为常量指针,不能被赋值
	printf("%d\n",*(int*)((short*)arr+1+1));
	printf("%d\n",*((int*)((double*)arr+1)+1));
	printf("%d\n",*((int*)(&arr+1)-1));//(&arr+1)偏移了整个数组模块24个字节,此时指针指向数组末尾

	printf("%d\n",arr[5]-arr[1]);//11-3=8
	printf("%d\n",*(arr+5)-*(arr+1));//*(arr+5)偏移了5个int类型的模块。11-3=8
	printf("%d\n",(arr+5)-(arr+1));//模块相减.16个字节,int类型一个模块4个字节,16/4=4
	printf("%d\n",(char*)(arr+5)-(char*)(arr+1));//模块相减。16个字节,char类型一个模块1个字节,16/1=16
	return 0;
}

指针数组、数组指针

#include<stdio.h>

int main(){
	int arr[2][3] = {1,2,3,4,5,6};
    int* pa[2] = {arr[0],arr[1]};//指针数组,存储指针的数组
	int (*p)[3] = arr;//数组指针,指向了一个模块为数组的指针

//指针数组的使用。因为是数组,我的习惯是先操作数组,后操作指针
    printf("%d\n",*(pa[0]+0));//1
    printf("%d\n",*(pa[0]+1));//2
    printf("%d\n",*(pa[0]+2));//3
    printf("%d\n",*(pa[1]+0));//4
    printf("%d\n",*(pa[1]+1));//5
    printf("%d\n",*(pa[1]+2));//6

//数组指针的使用。因为是指针,我的习惯是先操作指针,后操作数组
    printf("%d\n",(*(p+0))[0]);//1
    printf("%d\n",(*(p+0))[1]);//2
    printf("%d\n",(*(p+0))[2]);//3
    printf("%d\n",(*(p+1))[0]);//4
    printf("%d\n",(*(p+1))[1]);//5
    printf("%d\n",(*(p+1))[2]);//6
	return 0;
}

二级指针数组

#include<stdio.h>
/*
二级指针数组的操作
*/
int main(){
	int arr1[5] = {1,3,5,7,9};
	int arr2[5] = {11,13,15,17,19};
	int arr3[5] = {2,4,6,8,10};
	int arr4[5] = {12,14,16,18,20};
	int* arr_1[2] = {arr1,arr2};
	int* arr_2[2] = {arr3,arr4};
	int** arr[2] = {arr_1,arr_2};
	printf("%d\n",***arr);//1
/*
	通过arr,分别访问到数据3,15,8,20
*/
	printf("%d\n",*(arr[0][0]+1));//3
	printf("%d\n",*(arr[0][1]+2));//15
	printf("%d\n",*(arr[1][0]+3));//8
	printf("%d\n",*(arr[1][1]+4));//20
	return 0;
}

指针变量中的注意事项

特别注意:
++p和- -p
p++和p- -
例如:
int a[2] = {5,6};
int* p = a;
printf("%d\n",* (p++));//得到5 ,此时p指向a[1]的地址
//printf("%d\n",* (++p));得到6,此时p指向a[1]的地址

#include<stdio.h>

int main(){
	char* c[4]={"ENTER","NEW","POINT","FIRST"};
	char** cp[4]={c+3,c+2/*c+1*/,c+1,c};
	char*** cpp=cp;
	printf("%s\n",**++cpp);
/*
	cpp -> c+3的地址
	++cpp -> c+2的地址	cpp -> c+2的地址
	*++cpp -> *(c+2的地址) -> c+2的指向 ->"POINT"的地址
	**++cpp -> *("POINT"的地址) -> 'P'的地址
*/
	printf("%s\n",*--*cpp+++1);
/*
	cpp -> c+2的地址
	cpp++ -> c+2的地址	cpp -> c+1的地址
	*cpp++ -> *(c+2的地址) -> c+2的值
	--*cpp++ -> --(c+2的值) -> c+1 -> "NEW"的地址
	*--*cpp++ -> *("NEW"的地址) -> 'N'的地址
	*--*cpp+++1 -> 'N'的地址+1 -> 'E'的地址
*/
	printf("%s\n",*cpp[-2]+3);
/*
	cpp -> c+1 的地址
	cpp[-2] -> *(cpp-2) -> *(c+3的地址) -> c+3的指向 -> "FIRST"的地址
	*cpp[-2] -> *("FIRST"的地址) -> 'F'的地址
	*cpp[-2]+3 -> 'F'的地址+3 -> 'S'的地址
*/
	printf("%s\n",cpp[-1][-1]+1);
/*
	cpp -> c+1的地址
	cpp[-1] -> *(cpp-1) -> *(c+1的地址(原c+2的地址)) -> c+1的指向-> "NEW"的地址
	cpp[-1][-1] -> *("NEW"的地址 -1) -> *("ENTER"的地址) -> 'E'的地址
	cpp[-1][-1] +-> 'E'的地址+1 -> 'N'的地址
*/
	return 0;
}

同样函数设计

通用指针void*

通用函数的设计:
  一般都要用void* arr和int size 来传参,然后在函数内部强制转换指针类型,最后指针偏移。
  用void* 来返回地址,同样的,因为返回的是void* 类型的,需要强制转换指针类型

#include<stdio.h>
/*
	封装一个函数,实现功能:能够传入任意类型的数组,然后在函数内部,通过scanf获取一个下标位置,然后返回该数组下标位置的地址。
	然后在主函数中,根据传入的数组的数据类型,打印返回的地址上的值
*/

/*
函数名:func
函数参数:void* arr-可以传入任意数组,int size-传入的数组的类型的长度
函数返回值:返回输入的数组下标位置的地址
函数功能:能够传入任意类型的数组,然后在函数内部,通过scanf获取一个下标位置,然后返回该数组下标位置的地址。
*/
void* func(void* arr,int size){
	int pos = 0;
	printf("请输入下标位置:");
	scanf("%d",&pos);
	while(getchar()!=10);
	return (char*)arr + pos*size;//通用指针先强制转换,然后指针偏移
}

int main(){
	int arr[5] = {1,3,5,7,9};
	char str[6] = "hello";
	double darr[5] = {1.3,2.1,3.6,4.4,5.9};
	char* argv[2] = {"你好","中国"};//指针数组
	void* p = func(arr,4);
	printf("%d\n",*(int*)p);//使用通用指针void* 时要强制转换
    p = func(str,1);
	printf("%c\n",*(char*)p);
    p = func(darr,8);
	printf("%g\n",*(double*)p);
    p = func(argv,8));//因为指针数组存的是指针,而指针类型固定大小为8个字节
	printf("%s\n",*(char**)p);//char* 为字符型指针,char** 为字符串型指针
	return 0;
}

内存拷贝函数memcpy

memcpy的使用需要头文件string.h

#include<stdio.h>
#include<string.h>

/*
	函数名:memcpy
	函数参数:void* a,void* b,int size
	函数功能:将b指向的地址作为首地址,然后选取包含首地址在内的一共size个字节的,将这个size字节内存上的数据拷贝给a指向的内存为首的地址
*/

int main(){
	char a[20] = "hello";
	char b[10] = " world";
	/*
		使用memcpy拼接a和b,使a变成"hello world"
	*/
	memcpy(a+6,b,7);
	printf("%s\n",a);
	return 0;
}

任意数组元素间的交换

#include<stdio.h>
#include<string.h>
/*
	编写一个函数:能够接受任意的数组,然后在函数内部,通过scanf输入2个下标位置。交换两个下标位置上的值
	主函数中打印更新后的数组
*/
/*
函数名:func
函数参数:void* arr-任意类型的数组,int size-传入的数组类型长度
函数返回值:void
函数功能:能够接受任意的数组,然后在函数内部,通过scanf输入2个下标位置。交    
    换两个下标位置上的值
*/
void func(void* arr,int size){
	int a = 0,b = 0;
	scanf("%d %d",&a,&b);
	while(getchar()!=10);
	char temp[size];
	memcpy(temp,(char*)arr+a*size,size);
	memcpy((char*)arr+a*size,(char*)arr+b*size,size);
	memcpy((char*)arr+b*size,temp,size);
}

int main(){
	int arr[5] = {1,3,5,7,9};
	char str[6] = "hello";
	double darr[5] = {1.3,2.1,3.6,4.4,5.9};
	char* argv[2] = {"你好","中国"};
	//func(arr,4);
	//func(str,1);
	//func(darr,8);
	func(argv,8);
	int i = 0;
	for(i=0;i<2;i++){
		//printf("%d ",arr[i]);
		//printf("%c ",str[i]);
		//printf("%g ",darr[i]);
		printf("%s ",argv[i]);
	}
	printf("\n");
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值