指针的进阶1

初阶指针中,我们知道了指针的概念:
1.指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2.指针的大小是固定的4/8个字节(32位平台/64位平台)。
3.指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
4.指针的运算。

接下来我们继续学习指针的进阶知识

一、字符指针

char*

一般使用:

int main()
{
    char ch = 'w';
    char* pc = &ch;
    *pc = 'w';
    return 0;
}

还有一种使用方式如下:

这里是把一个常量字符串的首字符‘h’的地址放在pstr指针变量里面,通过pstr就可以访问整个字符串(只要提供首字符的地址就可以打印整个字符串)

int main()
{
    const char* pstr = "hello world";
    printf("%s\n",pstr);
    return 0;
}

常量字符串里面的字符是不可以被改变的,在字符指针的前面加上const,如果常量字符串被改变就会报警告

指针的进阶1_第1张图片

一道题:

int main()
{
	char str1[] = "hello world";
	char str2[] = "hello world";
	const char* str3 = "hello world";
	const char* str4 = "hello world";

	if (str1 == str2)
		printf("str1 and str2 are same\n");//x
	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");//x
	return 0;
}

这⾥str3和str4指向的是⼀个同⼀个常量字符串。C / C++会把常量字符串存储到单独的⼀个内存区域,当几个指针指向同⼀个字符串的时候,他们实际会指向同⼀块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同

二、指针数组

整型数组 - 存放整形的数组
字符数组 - 存放字符的数组

指针数组-存放指针(地址)数组

指针数组变量:

int* arr1[10];//整型指针的数组
char* arr2[4];//一级字符指针的数组
char** arr3[5];//二级字符指针的数组
int main()
{
    //存放字符指针的数组
    char* arr[4] = {"abcdef","qwer","hello","hehe"};
    int i = 0;
    for(i = 0;i < 4; i++)
    {
        printf("%s\n",arr[i]);
    }
    return 0;
}

模拟二维数组:

int main()
{
    int arr1[5] = {1,2,3,4,5};
    int arr2[5] = {2,3,4,5,6};
    int arr3[5] = {3,4,5,6,7};
    int arr4[5] = {0,0,0,0,0};
    
    //存放整形指针的数组
    int* arr[4] = {arr1,arr2,arr3,arr4};    
    int i = 0;
    for(i = 0;i < 4; i++)
    {
        int j = 0;
        for(j = 0;j < 5; j++)
        {
            printf("%d ",*(arr[i] + j));//arr[i][j]
        }
        printf("\n");
    }

    return 0;
}

三、数组指针

3.1数组指针的定义

字符指针 - 存放字符地址的指针 - 指向字符数据的指针 char*
整形指针 - 存放整形地址的指针 - 指向整型数据的指针 int*
浮点型指针 - 指向浮点型数据的指针 float*  double*
数组指针 - 存放数组地址指针 - 指向数组的指针

数组指针变量

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

3.2数组指针变量的初始化

数组指针变量是用来存放数组地址的,那怎么获得数组的地址呢?就是我们之前学习的 & 数组名 
int arr[10] = {0};
&arr;//得到的就是数组的地址
如果要存放个数组的地址,就得存放在数组指针变量中,如下:
int (*p)[10] = &arr;

通过调试可知,p和&arr的类型完全一致:int (*)[10] - 数组指针类型

数组指针类型解析:
int (*p) [10] = &arr;
 |   |    |
 |   |    |
 |   |    p指向数组的元素个数
 |   p是数组指针变量名
 p指向的数组的元素类型
int main()
{
    char ch = 'w';
    char* pc = &ch;

    int num = 10;
    int* pi = #

    int arr[10];
    int (*pa)[10] = &arr;//pa就是一个数组指针变量
    return 0;
}

3.3&数组名和数组名

对于以下的数组:

int arr[10];

arr是数组名,数组名表示数组首元素的地址

&arr则表示数组的地址

先看以下代码:

指针的进阶1_第2张图片

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

指针的进阶1_第3张图片

根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义是不一样的,实际上:&arr表示的是数组的地址,而不是数组首元素的地址。

&arr取出的是数组的地址,只有数组的地址才需要数组来接收

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

3.4数组指针的使用

既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址

一个数组指针的使用:

int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int* p = arr;
    int i = 0;
    for(i = 0;i < 10; i++)
    {
        printf("%d ",*(p + i));
    }

    //使用数组指针实现
    int (*p)[10] = &arr;//对数组的访问需要循环来完成,不能直接打印,因为没有像字符串那样有‘\0’作为结束标志
    int i = 0;
    for(i = 0;i < 10; i++)
    {
        printf("%d ",(*p)[i]);
        //printf("%d ",arr[i]);  // *p = * &arr = arr
    }
    return 0;
}
void print1(int arr[3][4],int row,int col)
{
    int i = 0;
    for(i = 0;i < row; i++)
    {
        int j = 0;
        for(j = 0;j < col; j++)
        {
            printf("%d ",arr[i][j]);
        }
        printf("\n");
    }
}

void print2(int (*p)[4],int row,int col)//将二维数组首元素的地址,即第一行的地址传给p,则p指向第一行的元素,p+1就跳过4个元素,即指向第二行
{
    int i = 0;
    for(i = 0;i < row; i++)
    {
        int j = 0;
        for(j = 0;j < col; j++)
        {
            printf("%d ",(*(p + i))[j]);//p[i][j]
        }
        printf("\n");
    }
}

int main()
{
    int arr[3][4] = {{1,2,3,4},{2,3,4,5},{3,4,5,6}};//数组名是首元素的地址,而二维数组首元素地址是第一行的地址(int [4]),相当于一维数组的地址,将一维数组的地址放到一个数组指针中去,即int (*p)[4]
    ptint1(arr,3,4);
    ptint2(arr,3,4);
    return 0;
}

指针数组与数组指针的辨别

int arr[5];//整型数组,数组是5个元素
int* arr1[10];//指针数组,数组10个元素,每个元素是int*类型的
int (*p)[10];//数组指针,p是数组指针(变量),该指针指向一个数组,数组是10个元素,每个元素是int (*)[10]类型的
int (*p2[10])[5];//p2是数组,数组有10个元素,数组的每个元素的类型是:int (*)[5]的数组指针类型

四、数组参数、指针参数

4.1一维数组传参

void test(int arr[])//ok 
{}
void test(int arr[10])//ok 
{}
void test(int* arr)//ok 
{}
void test2(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);//数组名是首元素的地址,而arr每个元素的类型是int,int的地址是int*
	test2(arr2);//数组名表示首元素的地址,而arr2每个元素的类型是int*,是一级指针;int*的地址,就是int**(二级指针)
	return 0;
}

4.2二维数组传参

void test(int arr[3][5])//ok  
{}
void test(int arr[][])//error
{}
void test(int arr[][5])//ok
{}
//对于一个二维数组,可以不知道多少行,但必须知道多少列,即行数可以省略,列数不可以省略
void test(int* arr)//整型指针 error
/{}
void test(int* arr[5])//指针数组 error   
{}
void test(int (*arr)[5])//ok
{}
void test(int** arr)//要接收的是数组的地址,而二级指针是接收一级指针的地址的  error
{}
int main()
{
	int arr[3][5] = { 0 };
	test(arr);//二维数组数组名是首元素的地址,即第一行的地址(一维数组的地址),类型是数组指针
	return 0;
}

4.3一级指针传参

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

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

void test(int* p)
{}
int main()
{
	int a = 10;
	int* p = &a;
	int arr[10];

	test(&a);
	test(p);
	test(arr);
	return 0;
}

函数可以接收的参数:变量的地址、一级指针变量、一维数组的数组名

4.4二级指针传参

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

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

void test(int** p)
{}
int main()
{
	int** ptr;
	int* pp;
	int* arr[10];

	test(ptr);
	test(&pp);
	test(arr);
	return 0;
}

函数可以接收的参数:二级指针变量、一级指针变量的地址、指针数组的数组名

五、函数指针

5.1函数指针变量的创建

根据前⾯学习整型指针,数组指针的时候,我们的类⽐关系,我们不难得出结论:
函数指针变量是⽤来存放函数地址的,未来通过地址能够调用函数的。
那么函数是否有地址呢?

看以下的代码:

void test()
{
    printf("hehe\n");
}
int main()
{
    printf("test:%p\n",test);
    printf("&test:%p\n",&test);
    return 0;
}

输出的结果:

test: 005913CA
&test: 005913CA
确实打印出来了地址,所以函数是有地址的,函数名就是函数的地址,当然也可以通过 &函数名 的方 式获得函数的地址。(&函数名和函数名都是函数的地址)
如果我们要将函数的地址存放起来,就得创建函数指针变量,函数指针变量的写法其实和数组指针 非常类似,如下:
int (*p)(int,int) = &test;
int (*p)(int,int) = test;

5.2函数指针变量的使用

通过函数指针调用指针指向的函数
int Add(int x,int y)
{
    return x + y;
}
int main()
{
    int (*pf)(int,int) = &Add;//pf是一个存放函数地址的指针变量 - 函数指针
    //int (*pf)(int x,int y) = &Add;//x和y写上或者省略都是可以的
    //int (*pf)(int,int) = Add;
    
    int ret = (*pf)(2,3);
    //int ret = Add(2,3);
    //int ret = pf(2,3);
    //int ret = (******pf)(2,3);//(*pf)中,*可以省略,或者无论加多少颗*都可以,结果都一样
    
    printf("%d\n",ret);
    return 0;
}
函数指针类型解析:
int (*pf3) (int x, int y)
 |     |    ------------ 
 |     |         |
 |     |        pf3指向函数的参数类型和个数的交代
 |    函数指针变量名
 pf3指向函数的返回类型

int (*) (int x, int y) //pf3函数指针变量的类型

---------------------------------------------------------------------------------------------------------------------------------

int* arr[3][3] = {1};
int p = **arr;
printf("%d",p);

二维数组数组名是首元素的地址,即第一行的地址(一维数组的地址)

*arr = *(arr+0) = arr[0] ----> 第一行

**arr = *(arr[0]+0) = arr[0][0] -----> 第一行第一个元素

所以打印的结果为:p = 1

5.3两段有趣的代码

5.3.1

( *( void (*)() ) 0 )();

该代码是一次函数调用,调用0地址处的一个函数;首先代码中将0强制转化为类型为 void (*)() 的函数指针,然后去调用0地址处的函数

5.3.2

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

该代码是一次函数的声明,声明的函数叫signal;signal函数的参数有2个,第一个是int类型,第二个是函数指针类型void (*)(int),该函数指针能够指向的那个函数的参数是int,返回类型是void
signal函数的返回类型是一个函数指针void (*)(int),该函数指针能够指向的那个函数的参数是int,返回类型是void void (*)(int)  signal( int, void(*)(int) ,类似于 void test(int x,int y);

5.3.2.1 typedef 关键字
typedef 是⽤来类型重命名的,可以将复杂的类型,简单化。
比如,你觉得 unsigned int 写起来不方便,如果能写成 uint 就方便多了,那么我们可以使用:
typedef unsigned int uint;
//将unsigned int 重命名为uint
如果是指针类型,也可以重命名,比如,将 int* 重命名为 ptr_t ,这样写:
typedef int* ptr_t;
但是对于数组指针和函数指针稍微有点区别:
比如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写:
typedef int(*parr_t)[5]; //新的类型名必须在*的右边
函数指针类型的重命名也是⼀样的,⽐如,将 void(*)(int) 类型重命名为 pf_t ,就可以这样写:
typedef void(*pfun_t)(int);//新的类型名必须在*的右边

那么我们就可以简化5.3.2中的代码:

void ( *signal( int, void(*)(int) ) )(int);
typedef void (*pf_t)(int);//将函数指针类型void (*)(int)重命名为pf_t
pf_t signal(int,pf_t);

你可能感兴趣的:(c语言)