指针进阶(1)

目录

1. 字符指针

例题1                                                                             

 2. 指针数组

指针和数组

通过指针遍历数组

另一种写法

 指针数组概念

整型指针数组

 字符指针数组 

 3.数组指针

数组指针的定义

&数组名VS数组名

二维数组传参

例题1

4. 数组参数、指针参数

4.1 一维数组传参

4.2 二维数组传参

4.3 一级指针传参  

4.4 二级指针传参  

5.函数指针

创建一个函数指针变量

调用函数指针变量

例题

阅读两段有趣的代码:

例题1

例题2


1. 字符指针

指针变量概念:

1. 指针就是个变量,用来存放地址,地址 唯一 标识一块内存空间。
2. 指针的大小是固定的 4/8 个字节( 32 位平台 /64 位平台)。
3. 指针是有类型,指针的类型决定了指针的+-整数的步长 ,指针解引用操作的时.候的权限。
int main()
{
	char ch = 'w';//取字符变量的地址赋给pc,pc是字符指针变量
	char* pc = &ch;

	 char* p = "abcdef";//常量字符串,最好在前面加上const,意思不是把字符串的值赋值给p,"abcdef\0"是7个字符,指针变量4个字节,意思是把字符串首元素地址给p
	*p = 'w';//程序报错的原因在于"abcdef"是常量字符串不能更改

	printf("%s\n", p);//不用*p的原因是,*p拿到是a,p是字符串的地址,
	return 0;
}

取字符变量的地址赋给pc,pc是字符指针变量 

"abcdef"常量字符串,最好在前面加上const,意思不是把字符串的值赋值给p,"abcdef\0"是7个字符,指针变量4个字节,意思是把字符串首元素地址给p

不用*p的原因是,*p拿到是a,p是字符串的地址,数组在内存中是连续存放的,只要拿到首字符地址,就会一直打印到'\0'

例题1                                                                             

int main()
{
    char str1[] = "hello bit.";
    char str2[] = "hello bit.";
    const char *str3 = "hello bit.";
    const char *str4 = "hello bit.";
    if(str1 ==str2)
 printf("str1 and str2 are same\n");
    else
 printf("str1 and str2 are not same\n");
       
    if(str3 ==str4)
 printf("str3 and str4 are same\n");
    else
 printf("str3 and str4 are not same\n");
       
    return 0;
}

 2. 指针数组

指针和数组

1.指针和数组是不同的对象,指针是一种变量,存放地址的,大小是4/8字节的,数组是一组相同类型元素的集合,是可以放多个元素的,大小是取决于元素个数和元素类型的 

2.数组的数组名是数组首元素的地址,地址是可以放在指针变量中,可以通过指针访问数组,数组是一段连续的空间,所以返回首元素地址,后面就紧跟着的是后序元素

通过指针遍历数组

#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	//赋值
	for (i = 0; i < sz; i++)
	{
		*p = i + 1;//*p++=i+1,*优先级高于++
		p++;
	}
	p = arr;                                                                                                                                                                                                                                                                                                 
	//打印
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *p);//*p++
		p++;
	}
	return 0;
}

*p++=i+1,++优先级高于*,先使用了后置++再执行*,但是后置++执行后的效果体现不出来,要到下一条语句才可以显现出作用,(先使用再++),

*p=i+1,p++<==>*p++=i+1

另一种写法

int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	//赋值
	for (i = 0; i < sz; i++)
	{
		*(p + i) = i + 1;
	}

//打印
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
	

}

 [] 是个操作符
        i和arr是[]这个操作符的操作数而已,*(arr + i) == *(i + arr) == i[arr];结果都是一样的,但是不建议写成i[arr]
        类比于a + b == b + a;

两种写法的结果是一样的 

 指针数组概念

指针数组是数组,指针数组是存放指针的数组

类比于字符数组是存放字符的数组,char arr[5]

整型数组是存放整型的数组 int arr[10]

int main() {
	
	int a = 10;
	int b = 20;
	int c = 30;
	int d = 40;
	int e = 50;
	int* arr[5] = { &a,&b,&c,&d,&e };
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", *(arr[i]));
	}
	return 0;
}

abcde五个变量不一定连续,把它们的首元素地址放在arr数组里面,数组在内存中是连续存放的

整型指针数组

 使用一维数组,模拟一个二维数组

#include<stdio.h>
int main()
{
	int a[] = { 1,2,3,4 };
	int b[] = { 2,3,4,5 };
	int c[] = { 3,4,5,6 };
	int* arr[3] = { a,b,c };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 4; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;

}

arr[i][j]可写成*(arr[i]+j),一个整型指针(地址)加j向后偏移j个元素

 用一个数组名表示首元素地址,把a,b,c放到arr中说明是把首元素地址存进去了,因为是个整型地址,所以是个整型指针,所以是int*arr[3]每个元素的类型是int*,本质是用一个指针数组,来管理3个一维数组

 字符指针数组 

int main()
{

	const char* arr[4] = { "abcdef","qwer","hello world","hehe" };
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		printf("%s\n", arr[i]);
	}
	return 0;

}

加const的原因是常量字符串的每个字符串产生的首字符地址,都是以常量字符串的起始地址

 3.数组指针

数组指针的定义

数组指针是数组还是指针?是指针

类比:

字符指针-存放字符地址的指针-指向字符的指针 char*

整型指针 - 存放整型地址的指针 - 指向整型的指针 int*

浮点型指针 - 存放浮点型地址的指针-指向浮点型的指针 float* double*

数组指针 - 存放数组地址的指针 - 指向数组的指针 

&数组名VS数组名

int main()
{
	
 int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };   

    printf("%p\n", arr);//int*
	printf("%p\n", arr+1);//4

	printf("%p\n", &arr[0]);//int*
	printf("%p\n", &arr[0]+1);//4

	printf("%p\n", &arr);//int(*)[10]
	printf("%p\n", &arr+1);//40
}

上图可看出arr==&arr[0],也说明指针类型决定了指针+1到底加的是几,即为数组名是数组首元素地址(元素1的地址),而&arr是整个数组的起始地址

int main()
{
	int a = 10;
	int* pa = &a;//1

	int arr[10] = { 1,2,3,4,5,6,7,8,9,10};
	int(*p)[10] = &arr;//2

	int(*p2)[10] = arr;//3

}

对上图总结:

1.*说明pa是指针,int说明pa指向的a变量是整型
2.&arr取出的是数组的地址,只有数组的地址才需要数组来接收,类型是int[10]

3.arr是不能放进左边指针(类型是int[10])里面的,arr本身的类型是int*

易错点

下面代码哪个是数组指针?

int *p1[10];

int (*p2)[10];

答:int (*p2)[10];

p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个 指针,指向一个数组,叫数组指针,[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

二维数组传参

二维数组传参两种写法

void print1(int arr[3][4],int r,int c)
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0; j < c; j++)
		{
			printf("%d", arr[i][j]);
		}
		printf("\n");
	}
}

二维数组的指针


void print2(int(*p)[4],int r,int c)
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0; j < c; j++)
		{ 
			/*printf("%d ", p[i][j]);*/
			printf("%d ",(*(p+i))[j]);//意思是访问完第i行之后再访问[j]列
	  }
		printf("\n");
	}
}
int main()
{
	int arr[3][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6}};
	
	print2(arr,3,4);
	return 0;

}

总结:二维数组传参,形参部分可以写成数组,也可以写出指针,二维数组的数组名表示第一行的地址,arr表示第一行的地址,如果要找第一行第一列的地址,应该是&arr[0][0],(*(p+i))[j])意思是访问完第i行之后再访问[j]列,可以写成p[i][j]

易错点:

1.int arr[5];整型数组,数组有10个元素

2.int *parr1[10];指针数组,数组10个元素,每个元素都是int*类型的

3.int(*parr2)[10];parr2是数组指针,该指针指向一个数组,数组是10个元素,每个元素是int类型

4.int (*p)[10]=arr,如何强转?(int(*)[10])arr

5.int* p=&num,对p解引用的类型int 

6.int(*p)[10]=&arr,&arr赋给p,*p=*&arr=arr

例题1

int(*parr3[10])[5]:

数组指针,parr3是数组,数组有10个元素,数组的每个元素的类型:int(*)[5]的数组指针类型

4. 数组参数、指针参数

4.1 一维数组传参

#include <stdio.h>
void test(int arr[])//1
{}
void test(int arr[10])//2
{}
void test(int* arr)//3
{}
void test2(int* arr[20])//4
{}
void test2(int** arr)//5
{}
int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);
}

1.数组传参,形参部分写出数组,可以不指定大小,指定类型即可

2.数组传参,数组接收,大小指定10个元素,可以

1.2是数组传参数组接收

3.arr[10]数组10个元素,每个元素都是int,数组名表示首元素地址(整型的地址),就应该放到int指针里去 3.是数组传参指针接收

4.数组传参可以写成一样的,大小可以不写:int *arr[]

5.数组名表示首元素地址,每个元素都是int*的地址,是一级指针地址,可以用二级指针接收,不可以写成int* arr

4.2 二维数组传参

void test(int arr[3][5])//1
{}
void test(int arr[][])//2
{}
void test(int arr[][5])//3
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//4
{}
void test(int* arr[5])//5
{}
void test(int (*arr)[5])//6
{}
void test(int **arr)//7
{}
int main()
{
 int arr[3][5] = {0};
 test(arr);
}

1.可以写成一模一样的

2.二维数组传参,函数形参的设计只能省略第一个[]的数字。因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素,这样才方便运算。

3.看2

4.数组传参,要么写成数组要么写成指针,一维指针,不对,应该是数组指针

5.数组传参,形参要么写成数组,要么指针,是个数组,但又不是二维数组,能存指针但又不是指针

6.数组前有(*arr),说明是指针,指针指向的[]是数组,数组5个元素,每个元素是int(一行五个元素)

7.看传过来的类型是什么,二级指针是用来接收一级指针的地址的,而arr传的是第一行的地址 

4.3 一级指针传参  

#include <stdio.h>
void print(int *p, int sz)
{
 int i = 0;
 for(i=0; i<sz; i++)
 {
 printf("%d\n", *(p+i));
 }
}
int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9};
 int *p = arr;
 int sz = sizeof(arr)/sizeof(arr[0]);
 //一级指针p,传给函数
 print(p, sz);
 return 0;
}

 p是一级指针传参,一级指针接收即可 

4.4 二级指针传参  

#include <stdio.h>
void test(int** ptr)
{
 printf("num = %d\n", **ptr); 
}
int main()
{
 int n = 10;
 int*p = &n;
 int **pp = &p;
 test(pp);
 test(&p);
 return 0;
}

5.函数指针

数组指针- 指向数组的指针

函数指针 -  指向函数的指针 

int Add(int x,int y)
{
	return x + y;
}
int main()
{

    printf("%p\n", &Add);
	printf("%p\n", Add);

  return 0;
}

 可看到以上代码两种表示方法结果是一样的,函数没有什么首元素的地址,数组才有,所以函数指针用,&和不用&拿到的都是首元素地址,,结果都是一样,&函数名和函数名都是函数的地址

创建一个函数指针变量

int Add(int x,int y)
{
	return x + y;
}
int main(){
    int arr[10];
	int(*pa)[10] = &arr;

	int (*pf)(int, int) = &Add;
    //int (*pf)(int, int) = &Add;
}

pf是一个存放函数地址的指针变量 - 函数指针 ,和数组指针写法相似,注意:函数指针变量名是pf,不是*pf,*只是告诉我们pf是指针

调用函数指针变量

int Add(int x,int y)
{
	return x + y;
}
int main(){
    int(*pa)[10] = &arr;

	int (*pf)(int, int) = &Add  //int (*pf)(int, int) = &Add
    
    int ret=pf(2,3);//int ret=Add(2,3); int ret=(*pf)(2,3)

}

以上//是等价的意思,且*pf一定要加(),.否则*pf(2,3),求其结果是*5

例题

int main()
{
	int* arr[3][3] = { 1 };

	int p = **arr;
	printf("%d", p);

}

阅读两段有趣的代码:

例题1

int main()
{
	//代码1
	(*(void (*)())0)();

	return 0;

}

从0开始下手,0是整型int,若在(void(*)())放p,为void(*p)()说明是函数指针,(void(*)())0说明是把0强制转换成函数指针类型(0被当成一个地址,0地址,放的参数是无参,返回类型是void的函数),

在前面加*,写成*(void(*)())0,说明对0地址做解引用操作,那个函数是无参的,所以最后面的括号写成()没有传任何参数,

总结:该代码是一次函数调用,调用的0地址处的一个函数,首先代码中将0强制类型转换为类型void(*)()函数指针,然后去调用0地址处的函数--------来自《C陷阱和缺陷》

可用上述代码类比一下   int (*pf)(int, int) <--->(*(void (*)())0)();

(*(void (*)())0)的(void (*)())0为pf部分,()为(int, int)部分

例题2

int main(){

void(*signal(int, void(*)(int)))(int);

return 0;
}

从signal开始,*和signal没()一起,说明不是个指针,而signal括号包含两个参数类型,一个int,另一个是函数指针,说明signal第一个参数是整型指针,第二个为函数指针,函数除了函数名和参数外,还有返回类型,把分析过的东西删去,即为返回类型:void(*)(int)

虽然这样更容易理解,但这样写是错误的:void(*)(int)signal(int,void(*)(int));(只用于方便理解)

总结:该代码是一次函数声明,声明的函数名字叫signal,siganl函数的参数有2个,第一个是int类型,第二个是函数指针类型,该函数指针能够指向的那个函数的参数是int,返回类型是void

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dream_Chaser~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值