(C语言进阶)指针进阶(上)

  • 1. 字符指针
  • 2. 数组指针
  • 3. 指针数组
  • 4. 数组传参和指针传参
  • 5. 函数指针
  • 6. 函数指针数组

————————————————————————————————————————

 回顾我们之前学过:

1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。

2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。

3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。 4. 指针的运算。

1.字符指针

	char ch = 'w';
	char* pc = &ch;//pc就是字符指针

//1.
	char* p = "abcdef";//表达式的值是首元素地址
	*p = 'w';//运行错误,常量字符串不能修改

//2.
	char arr[] = "abcdef";
	char* p = arr;//p指向的是数组的首元素,arr数组是可以修改的
	*p = 'w';
	printf("%s\n", arr);

首先,char*p=“abcdef” 对于初学者来说,可能认为把abcdef字符串放入指针p里面了,但是实质上是把abcdef的首元素地址"a"放入到p,同时*p存储的是一个常量字符串

所以,为了避免出错我们应该在前面加上const进行修饰:

const char*p ="abcdef";

例题:

#include <stdio.h>
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;
}

输出如下: 

解释:

1.str1和str2在存储数据的时候会分别开辟两个空间存储字符串“hello bit.” ,两个数组在内存中存储的位置不同,同时数组名表示首元素地址,而str1和str2的首元素地址不相同,故str1和str2不等。

2.str3和str4是存储一个常量字符串的首地址,常量字符串又不能被改变因此在内存中共用一个常量字符串“hello bit”,所以str3和str4指向的是一个同一个常量字符串。所以str1和str2不同,str3和str4相同。

3.(C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化,不同的数组的时候就会开辟出不同的内存块)

注:&str3!=&str4;str3=str4;(因为&str3和&str4取得的都是str3和str4的首元素地址)

&str3和&str4

2.指针数组

指针数组是一个存放指针的数组。

字符数组——存放字符的数组:char arr1[10];

整形数组——存放整形的数组:int arr2[5];

字符指针数组——存放字符指针的数组:char* arr3[5];

整形指针数组——存放整形指针的数组:int* arr[6];

...

int* arr1[10]; //整形指针的数组

char *arr2[4]; //一级字符指针的数组

char **arr3[5];//二级字符指针的数组

例如1:

#include <stdio.h>
int main()
{
	char* arr[] = { "abcdef","hehe","qwer" };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%s\n", arr[i]);
	}
	return 0;
}

输出结果:

arr[1]存放的是“abcdef”中的a的地址,arr[2]存放的是"hehe"中的h的地址,arr[3]存放的是"qwer"中的q的地址。

例如2:

#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* arr[] = { arr1,arr2,arr3 };//arr是一个存放整形指针的数组
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			//printf("%d ", arr[i][j]);//也可以
			printf("%d ", *(arr[i] + j));//arr[i]==*(arr+i)
		}
		printf("\n");
	}
	return 0;
}

3.数组指针

3.1数组指针的定义

数组指针是指针?还是数组? 答案是:指针。

整形指针: int * pint; 能够指向整形数据的指针,浮点型指针:float * pf; 能够指向浮点型数据的指针。那数组指针应该是:能够指向数组的指针。

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

int *p1[10];

int (*p2)[10];

//p1, p2分别是什么?

解释:

int *p1[10];

//指针数组,p先和[]结合

int (*p)[10];

//解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。

//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合

数组指针就是用来存放地址的。

小结:

指针数组——是数组——是一种存放指针的数组

数组指针——是指针——是一种指向数组的指针——存放的是数组的地址

3.2 &数组名VS数组名

先放入结论

数组名绝大部分情况下是数组首元素的地址但是有两个例外

①sizeof(数组名)

sizeof内部单独放一个数组名的时候,数组名表示的整个数组,计算得到的是数组的总大小

②&arr

这里的数组名表示整个数组,取出的是整个数组的地址,从地址值的角度来讲和数组首元素的地址是一样的,但是意义不一样

问题导入

arr 和 &arr 分别是啥?

我们知道arr是数组名,数组名表示数组首元素的地址。 那&arr数组名到底是啥?

我们看下面这个代码

#include <stdio.h>
int main()
{
    int arr[10] = {0};
    printf("%p\n", arr);
    printf("%p\n", &arr);
    return 0;
}

可见数组名和&数组名打印的地址是一样的。 难道两个是一样的吗? 我们再看一段代码: 

int main()
{
	int arr[10] = { 0 };
	//printf("%d\n", sizeof(arr));//40
	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);
	int (*p)[10] = &arr;//p是一个数组指针
	//int(*)[10]
	return 0;
}

运行结果:

分析:

如下图所示,我们将输出对应起来,arr与arr+1之间相差4,arr[0]与arr[0]+1之间相差4,arr与arr+1相差0x28(0x28(十六进制)=40(十进制))。

第一组arr+1,arr类型是int*,因此arr+1跨越了4个字节;第二组arr[0]+1,类型也是int*,所以同理+1后也跨越4个字节,第三组&arr+1,&arr类型是int (*)arr[10],因此跨越40个字节。(注:如果把&arr放入指针,那么用数组指针,那么表示为int(*p)arr[10])

 

3.3数组指针的使用

1.打印数组

给一个数组部分代码,请完成对数组的打印:

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

方式1 通过下标的方式访问数组

	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}

 方式2 使用指针来访问

    int* p = arr;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}

 方式3  指针数组(不推荐)

   int (* p)[10] = &arr;
   for (i = 0; i < sz; i++)
	{
		printf("%d ", (*p)[i]);
	}
//注:原因如下
//p---&arr
//*p---*&arr
//*p---arr

 或者

	int (* p)[10] = &arr;
    for (i = 0; i < sz; i++)
	{
		printf("%d ", *((*p) + i));
	}

这种方式不推荐的理由是有点大材小用,而且代码观感不是很好。

2.二维数组传参(简单讨论)

在讨论二维数组传参前,我们先回到一维数组传参。我们之前学习过一维数组传参有两种方式,分别是:一维数组传参,形参是数组一维数组传参,形参是指针。

例如:如下代码,实现print函数打印数组arr[10]内的值。

int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};
	int sz = sizeof(arr) / sizeof(arr[0]);
	print(arr, sz);
	return 0;
}

①当一维数组传参,形参是数组时

//一维数组传参,形参是数组
void print1(int arr[10], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

②一维数组传参,形参是指针

//一维数组传参,形参是指针
void print(int *arr, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		//printf("%d ", arr[i]);
		printf("%d ", *(arr+i));
	}
	printf("\n");
}

因此,二维数组传参也有两种:数组指针

例如:如下代码,实现print函数打印数组arr[3][5]内的值。

int main()
{
	int arr[3][5] = {1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};
	print(arr, 3, 5);
	return 0;
}

①形参是数组时

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

②形参是指针时

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

int(*arr)[5]的原因是:二维数组的数组名,表示首元素地址,同时二维数组的首元素是在第一行,首元素的地址就是第一行的地址,是一个一维数组的地址。因此传参需要把二维数组的首元素也就是第一行的地址传入参数,同时是一个指针故int(*arr)[5]

回顾并看看下面代码的意思

int arr[5];              数组

int *parr1[10];      指针数组

int (*parr2)[10];    数组指针

int (*parr3[10])[5];parr3是数组,而数组中存放的是指针,该指针指向的又是数组(注意:不是二维数组)

4. 数组参数、指针参数

4.1一维数组传参

#include <stdio.h>
void test(int arr[])//ok?
{}
void test(int arr[10])//ok?
{}
void test(int *arr)//ok?
{}
void test2(int *arr[20])//ok?
{}
void test2(int **arr)//ok?
{}
int main()
{
 int arr[10] = {0};
 int *arr2[20] = {0};
 test(arr);
 test2(arr2);
}

分析:

第一组:第一个可以,我们传的是参数,形参不需要规定数组的大小。第二个可以,形参传入的是数组。第三个也可以,这种判断需要我们观察传入的是什么,arr数组存的是int类型,因为arr是首元素地址,因此是int的地址,因此要用int的指针。

第二组:第一个可以,写成数组的形式。第二个可以,arr数组里面的元素是int*,而arr首元素是int*的地址,因此要放入二级指针的地址,故可以写成**arr

小结:一维数组传参,形参可以是数组,也可以是指针。同时注意当为形参时,注意类型。

4.2二维数组传参

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

分析:

第一个可以,传入的是二维数组。第二个不可以,可以没有行但是必须知道有多少列,二维数组是连续存放的,只有知道多少列可以存放,才能把需要地址连续存放。因此第三个可以。

第四个不可以,二维数组的地址是首元素地址,也就是二维数组的第一行的地址,而第一行相当于一个一维数组的地址,这里arr是一个整形地址,因此不可以。第五个不可以,直接看这是一个int形的指针数组,和二维数组不是一个类型。第六个可以,第一行是一维数组的地址,因此用一个数组指针接受是可以的,同时存放5个整形。即存放5个整形的数组指针。第七个不可以,这里的写法是说来存放一级指针的地址,但是此时的二维数组存放的是一行的地址,用二级指针是不行的。

总结:

二维数组传参,参数可以是数组,也可以是指针;

如果是数组,行可以省略,但是列不可以省略;

如果是指针,传过去的是第一行的地址,形参就应该是数组指针。

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

因此:一级指针形参的部分用一级指针接收即可

问:当一个函数的参数部分为一级指针的时候,函数能接收什么参数??

1、整形变量的地址 2、整形指针 3、整形数组的数组名

int a;
print(&a,10);//
int*p1 = &a;
print(pl,10);//
int arr[10];
print(arr,10);//

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;//把一级指针p的地址放到二级指针pp里面去,二级指针存放一级指针的地址
	test(pp);//形参是二级指针,传入二级指针
	test(&p);//二级指针存放的是一级指针的地址
	return 0;
}

二级指针传参形参就用二级指针来接收

问:当函数的参数为二级指针的时候,可以接收什么参数?

1、二级指针变量 2、一级指针变量地址 3、如果有int*arr[10]的整形数组,传入数组名是可以的

5.函数指针

整形指针-指向整形的指针 int*

字符指针-指向字符的指针 char*

数组指针-指向数组的指针 int arr[10]  int(*p)[10]=&arr;

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

如此类比需要存放函数的地址,函数也有地址吗?

经过在vs编译器上验证,&函数名得到的就是函数的地址。

同时注意:函数名和&函数名是都是函数的地址,没有区别。(不像数组名和&数组名)

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}

int main()
{
	printf("%p\n", &Add);
	printf("%p\n", Add);
	return 0;
}

 可以通过函数地址来调用函数吗?答案是可以的。例如下面例子:

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}

int main()
{   
    int (*pf)(int, int) = Add;//pf函数指针来对函数进行调用
	int ret = (*pf)(3, 5);//传参
	printf("%d", ret);
	return 0;
}

 输出结果:

 我们曾学过函数调用可以这样:

int ret =Add(3,5);

因此可以写成

int ret =pf(3,5);
//然后打印
printf("%d",ret);

输出结果: 

但是不可以写成 int ret = *pf(3,5); 

因此如果要加*号需要加括号,不加括号就不要加*;

同时(*pf)(3,5)对于编译器来说加多少个星号都是一样的。

阅读两段有趣的代码:

//代码1 (*(void (*)())0)();

//代码2 void (*signal(int , void(*)(int)))(int);

解释:

	(*( void (*)() ) 0)();
    //其中void (*)()是函数指针的一种类型,没有参数,返回类型为void
	//放入括号( ( void (*)() ),强制类型转换
	//()0,把0强制类型转换成指针的一种类型,比如int* double* float*的指针,但是这里是函数指针( void (*)() )
	//*()0 ,解引用,解引用的时候去找调用0的地址的函数,然后调用函数需要传参,然后*()0(),后面未传参

1. 将0强制类型转换为void (*)() 类型的函数指针
2. 这就意味着0地址处放着一个函数,函数没参数,返回类型是void

3. 调用0地址处的这个函数

4.这段代码其实是一次函数调用,调用的是0地址上的那个返回类型为void并且没有参数的函数

5.(* )()函数解引用*号可以不加,但现在是解读代码。

代码2

	void (*signal(int, void(*)(int)))(int);
	//signal是一个函数,其中int和void(*)(int)是函数的参数
	//目前看函数缺少返回类型,而void(*)(int)是signal的返回类型
	//你可以理解成void (* signal(int, void(*)(int) ) )(int);

1.上述的代码是一个函数的声明
2. 函数的名字是signal
3. signal函数的参数第一个是int类型,第二个是void(*)(int)类型的函数指针
4.该函数指针指向的函数参数是int,返回类型是void

5.signal函数的返回类型也是一个函数指针

类型重命名实例

//类型重命名
typedef int* ptr_t;//已知
typedef void(*pf_t)(int);//写出  //因为typedef void(*)(int) pf_t不符合语法
//含义是:将void(*)(int)类型重新起个别名叫pf_t
int main()
{
	//void (*signal(int, void(*)(int)))(int);//应用,将这句改写为
	pf_t signal(int, pf_t);//函数参数是pf_t,其返回值也是pf_t
	//注意必须定义在前才可以这样改写
}

注意:

typedef void(*pf_t2)(int);//pf_t2是类型名
void(*pf)(int);//pf是函数指针变量的名字

注:一般对类型名字重定义不会用#define

6. 函数指针数组

函数指针数组:数组的每一个元素是一个函数指针

数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组, 比如:

int *arr[10];

//数组的每个元素是int*

 那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?

int (*parr1[10])();

int *parr2[10]();

int (*)() parr3[10];

答案是:parr1

parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢?

是 int (*)() 类型的函数指针。

6.1函数指针数组的用途:转移表

例子:写一个程序实现一个计算器功能(+ - * /)代码: 即可

 代码: 

#include <stdio.h>
int add(int a, int b)
{
    return a + b;
}
int sub(int a, int b)
{
    return a - b;
}
int mul(int a, int b)
{
    return a * b;
}
int div(int a, int b)
{
    return a / b;
}
int main()
{
    int x, y;
    int input = 1;
    int ret = 0;
    do
    {
        printf("*************************\n");
        printf(" 1:add           2:sub \n");
        printf(" 3:mul           4:div \n");
        printf("*************************\n");
        printf("请选择:");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = add(x, y);
            printf("ret = %d\n", ret);
            break;
        case 2:
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = sub(x, y);
            printf("ret = %d\n", ret);
            break;
        case 3:
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = mul(x, y);
            printf("ret = %d\n", ret);
            break;
        case 4:
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = div(x, y);
            printf("ret = %d\n", ret);
            break;
        case 0:
            printf("退出程序\n");
            break;
        default:
            printf("选择错误\n");
            break;
        }
    } while (input);

    return 0;
}

使用函数指针数组实现:

#include <stdio.h>
int add(int a, int b)
{
    return a + b;
}
int sub(int a, int b)
{
    return a - b;
}
int mul(int a, int b)
{
    return a * b;
}
int div(int a, int b)
{
    return a / b;
}
void menu()
{
    printf("******************************\n");
    printf("****  1:add        2:sub  ****\n");
    printf("****  3:mul        4:div  ****\n");
    printf("******************************\n");
}
int main()
{
    int x = 0;
    int y=0;
    int input = 0;
    int ret = 0;
    int (*pfArr[])(int, int) = {0,add,sub,mul,div };//故意选择5个元素下标分别从0-4
    do
    {
        menu();
        printf("请选择:>");
        scanf("%d", &input);
        if (input == 0)
        {
            printf("退出程序\n");
            break;
        }
        else if (input >= 1 && input <= 4)
        {
            printf("请输入两个操作数:>");
            scanf("%d %d", &x, &y);
            ret = pfArr[input](x, y);
            printf("%d\n", ret);
            //如果写成printf("%d\n", &ret); 会报错;这里本来就是一个整形指针数组,数组类型为int*(刚开始写错了)
            //(vs警告)warning C4477: “printf”: 格式字符串“%d”需要类型“int”的参数,但可变参数 1 拥有了类型“int *”
        }
        else
        {
            printf("选择错误\n");
        }
    } while (input);
    return 0;
}

注:需要注意的是

1.关于(printf("%d\n", ret);)中ret没有取地址(当时编译时,我写错了)

把函数指针数组调用返回值赋值给ret(ret = pfArr[input](x, y);之后打印(print)不用再将ret取地址了,因为此时ret和数组里面的元素类型一样都是int*即(printf("%d\n", ret);)不要取地址否则会报错!

2.关于pfArr[]数组中第一个元素为0的原因

这里是想让程序是“输入‘0’退出程序”如此设定,另外也是数组下标从零开始和我们对数字的使用相差1,用来起到一个站位

所以pfArr[]数组的下标为0的元素,可以写成“0”之外,也可以写成“NULL”."NULL"更加形象表现指针为空,为空指针。

小结:

int my_strlen(const char* str)
{
	return 0;
}

int main()
{
	//指针数组
	char* arr[10];
	//数组指针
	int arr2[5] = { 0 };
	int(*p)[5] = &arr2;//p是一个指向数组的指针变量
	//函数指针
	int (*pf)(const char*) = my_strlen;//pf是一个指向函数的函数指针变量
	//调用:(*pf)("abcdef")
	//my_strlen("abcdef")
	//pf("abcdef")
	
	//函数指针数组 - 存放函数指针的数组
	int (*pfArr[10])(const char*);

	return 0;
}

(上片完)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值