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

目录

初级指针知识

高阶指针知识

1.字符指针

2.指针数组

3.数组指针

3.1数组指针的定义

3.2数组指针的使用

4.数组参数,指针参数

4.1一维数组传参

4.2二维数组传参

4.3一级指针传参

4.4二级指针传参

5.函数指针

6.函数指针数组

7.指向函数指针数组的指针

8.回调函数

 9.指针和数组笔试题


初级指针知识

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

2.指针大小为4或8个字节(取决于电脑是是32位还是64位平台)

3.指针是有类型,指针的类型决定了指针+-整数的步长.决定了对指针解引用的时候有多大的权限(能操作几个字节)

4.指针的运算

高阶指针知识

1.字符指针

字符指针char *

int main()
{
    char ch = 'w';
    char *pc = &ch;
    *pc = 'w';
    return 0;
}
int main()
{    
    const char* pstr = "hello world";//这里是把字符串hello world首字符的地址放在pstr中
    printf("%s\n", pstr);
    return 0;
}

有这样的一道面试题

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

输出结果如图

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


2.指针数组

指针数组-----顾名思义就是存放指针的数组

int* arr1[10];//存放整形指针的数组
char *arr2[4];//存放一级字符指针的数组
char **arr3[5];//存放二级字符指针的数组

3.数组指针

3.1数组指针的定义

数组指针----由字面意思可得是指针,并且指向数组

int *p1[10];//一个存放整形指针的数组
int (*p2)[10];//p2先和*结合,说明p2是一个指针变量,然后指向一个大小为10个整形的数组,所以p2是一个数组指针


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

3.2数组指针的使用

一个数组指针的使用

#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
    int i = 0;
    for(i=0; i<row; i++)
   {
        for(j=0; j<col; j++)
       {
            printf("%d ", arr[i][j]);
       }
       printf("\n");
   }
}
void print_arr2(int (*arr)[5], int row, int col)
{
    int i = 0;
    for(i=0; i<row; i++)
   {
        for(j=0; j<col; j++)
       {
            printf("%d ", arr[i][j]);
       }
        printf("\n");
   }
}
int main()
{
    int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};
    print_arr1(arr, 3, 5);
    //数组名arr,表示首元素的地址
    //在二维数组中,二维数组的首元素是二维数组的第一行
    //所以这里传递的arr,相当于第一行的地址,也就是一维数组的地址
    //可以用数组指针接收
    print_arr2(arr, 3, 5);
    return 0;
}
    

学了指针数组和数组指针后,下面代码的意思就清楚了

int arr[5];//整形数组,存放5个整形
int *parr1[10];指针数组,存放10和int*类型的指针
int (*parr2)[10];//数组指针,parr2指针指向一个存放10个int类型的数组
int (*parr3[10])[5];//为一个数组包含10个元素,每个元素为数组指针,指针指向一个存放5个int类型的数组,简洁来说就是一个存放数组指针的数组

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 = { 0 };
	test(arr);
	test2(arr2);
}

以上的一位数组传参的形参都是可行的,需要注意的是如果参数是void test (int arr[]),那么[]里可以任意填数字或不填

4.2二维数组传参

void test(int arr[3][5]) //ok
{}

void test (int arr[][]) //err
{}

void test(int arr[][5]) //ok
{}

int main()
{
	int arr[3][5] = { 0 };
	test(arr);
}

总结:二维数组传参,函数形参的设计只能省略第一个[]的数字

因为对一个二维数组,可以不知道有多少行,但是必须知道一行有多少个元素,因为这样才方便运算

4.3一级指针传参

#include<stdio.h>

void print(int* p, int sz)
{
	for (int i = 0;i < sz;i++)
	{
		printf("%d ", *(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]);
	print(p, sz);
	return 0;
}

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

比如:

void test1(int *p)
{}
//test1函数能接收什么参数?
//1.整形变量的地址
//2.整形数组首元素的地址
//3.一级指针变量
void test2(char* p)
{}
//test2函数能接收什么参数
//1.字符变量的地址
//2.字符数组首元素的地址
//3.一级指针变量

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

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

void test(char** p)
{

}

int main()
{
	char c = 'b';
	char* pc = &c;
	char** ppc = &pc;
	char* arr[10];
	test(&pc);
	test(ppc);
	test(arr);
	return 0;
}

5.函数指针

先来看一段代码

#include<stdio.h>
void test()
{
	printf("hehe\n");
}

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

输出结果如图

打印的内容相同,&函数名与函数名意义相同

如何保存函数的地址呢?

void (*pfun)();


6.函数指针数组

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

比如:

int* arr[10];

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

int(*parr[10])();

parr先和[]结合,说明parr是数组,数组的内容为int(*)()类型的函数指针

例子:(计算器)

#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;
}
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);
        int (*parr[5])(int, int) = { 0 , add, sub, mul, div };
        if (input > 0 && input <= 4)
        {
            printf("请输入操作数:>");
            scanf("%d %d", &x, &y);
            int ret = parr[input](x,y);
            printf("ret = %d\n", ret);
        }
        else if (input == 0)
        {
            printf("退出程序!\n");
            break;
        }
        else
        {
            printf("选择错误\n");
        }
    } while (input);

    return 0;
}

7.指向函数指针数组的指针

指向函数指针数组的指针是一个指针

指针指向一个数组,数组的元素都是函数指针;

如何定义?

void (*(*parr)[5])(int ,int )

8.回调函数

回调函数就是一个通过函数指针调用的函数.如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数.回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应.

首先演示一下qsort函数的用法:

#include<stdio.h>
int int_cmp(const void* e1, const void* e2)
{
	return (*(int*)e1 - *(int*)e2);
}

int main()
{
	int arr[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort( arr, sz, sizeof(arr[0]),int_cmp);
	for (int i = 0;i < sz;i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

输出结果如图: 

模拟实现qsort(采用冒泡排序的方式)

#include<stdio.h>
int int_cmp(const void* e1, const void* e2)
{
	return (*(int*)e1 - *(int*)e2);
}

void Swap(char* p1, char* p2, int width)
{
	for (int i = 0;i <width;i++,p1++,p2++)
	{
		char tmp = *p1;
		*p1 = *p2;
		*p2 = tmp;
	}
}

void bubble_sort(void* base, int num, int width, int (*cmp)(const void*, const void*))
{
	for (int i = 0;i < num - 1;i++)
	{
		for (int j = 0;j < num - i - 1;j++)
		{
			if (cmp((char*)base + j * width,(char*)base + (j + 1) * width) > 0)
			{
				Swap((char*)base + j * width, (char*)base + (j + 1) * width ,width);
			}
		}
	}
}

int main()
{
	int arr[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), int_cmp);
	for (int i = 0;i < sz;i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

输出如图:

 9.指针和数组笔试题

//一维数组
	int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));//4*4=16   a表示首元素的地址,单独放在sizeof内部,表示整个数组
	printf("%d\n", sizeof(a + 0));//4/8   a+0表示数组首元素的地址,是地址,大小就是4/8个字节
	printf("%d\n", sizeof(*a));//4   a表示数组首元素的地址,*a表示数组的第一个元素
	printf("%d\n", sizeof(a + 1));//4/8   a + 1表示数组第二个元素的地址
	printf("%d\n", sizeof(a[1]));//4   a[1]表示数组的第二个元素
	printf("%d\n", sizeof(&a));//4/8    &a取出的是整个数组的地址
	printf("%d\n", sizeof(*&a));//16   计算的是整个数组的大小
	printf("%d\n", sizeof(&a + 1));//4/8   &a取出的是整个数组的地址,+1跳过整个数组,产生的是4后边位置的地址
	printf("%d\n", sizeof(&a[0]));//4/8    取出的是数组第一个元素的地址
	printf("%d\n", sizeof(&a[0] + 1));//4/8   数组第二个元素的地址


	//字符数组
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));//6
	printf("%d\n", sizeof(arr + 0));//4/8	arr + 0是数组首元素的地址
	printf("%d\n", sizeof(*arr));//1	*arr表示数组的第一个元素
	printf("%d\n", sizeof(arr[1]));//1	 arr[1]表示数组的第一个元素
	printf("%d\n", sizeof(&arr));//4/8	&arr表示取出整个数组的地址
	printf("%d\n", sizeof(&arr + 1));//4/8	&arr取出的是整个数组的地址,+1表示跳过整个数组后产生的地址 
	printf("%d\n", sizeof(&arr[0] + 1));//4/8	&arr[0]+1表示数组第二个元素的地址


	printf("%d\n", strlen(arr));//随机值	arr数组中没有\0,所以strlen函数会继续往后找\0,统计\0之前出现的字符个数
	printf("%d\n", strlen(arr + 0));//随机值	arr+0还是数组首元素的地址
	printf("%d\n", strlen(*arr));//err	*arr表示数组的首元素,'a' - 97
	printf("%d\n", strlen(arr[1]));//err
	printf("%d\n", strlen(&arr));//随机值
	printf("%d\n", strlen(&arr + 1));//随机值
	printf("%d\n", strlen(&arr[0] + 1));//随机值
	
	
	
	char arr[] = "abcdef";
	printf("%d\n", sizeof(arr));//7
	printf("%d\n", sizeof(arr + 0));//4/8	arr + 0表示数组首元素的地址
	printf("%d\n", sizeof(*arr));//1	*arr 数组首元素的地址
	printf("%d\n", sizeof(arr[1]));//1	arr[1]表示数组第二个元素的地址
	printf("%d\n", sizeof(&arr));//4/8	&arr - 取出整个数组的地址
	printf("%d\n", sizeof(&arr + 1));//4/8	&arr + 0表示\0后边的这个地址
	printf("%d\n", sizeof(&arr[0] + 1));//4/8	&arr[0] + 1是数组第二个元素的地址

	printf("%d\n", strlen(arr));//6
	printf("%d\n", strlen(arr + 0))//6;
	printf("%d\n", strlen(*arr));//err
	printf("%d\n", strlen(arr[1]));//err
	printf("%d\n", strlen(&arr));//6
	printf("%d\n", strlen(&arr + 1));//随机值
	printf("%d\n", strlen(&arr[0] + 1));//5
	
	
	
	char* p = "abcdef";
	printf("%d\n", sizeof(p));//4/8	p是指针,计算的是指针变量的大小
	printf("%d\n", sizeof(p + 1));//4/8	p+1是'b'的地址
	printf("%d\n", sizeof(*p));//1	*p其实就是'a'
	printf("%d\n", sizeof(p[0]));//1	p[0] = 'a'
	printf("%d\n", sizeof(&p));//4/8	&p是指针变量p在内存中的地址	
	printf("%d\n", sizeof(&p + 1));//4/8	&p+1是跳过p之后的地址
	printf("%d\n", sizeof(&p[0] + 1));//4/8		&p[0]是'a'的地址,&p[0]+1就是'b'的地址


	printf("%d\n", strlen(p));//6
	printf("%d\n", strlen(p + 1));//5
	printf("%d\n", strlen(*p));//err
	printf("%d\n", strlen(p[0]));//err
	printf("%d\n", strlen(&p));//随机值
	printf("%d\n", strlen(&p + 1));//随机值
	printf("%d\n", strlen(&p[0] + 1));//5	从b的位置开始向后取字符
	
	
	
	//二维数组
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));//48	计算的是整个数组的大小,单位是字节3*4*4 = 48
	printf("%d\n", sizeof(a[0][0]));//4 第一行第一个元素的大小
	printf("%d\n", sizeof(a[0]));//16	a[0]作为第一行的数组名,sizeof(a[0])就是第一行的数组名单独放在sizeof内部,计算的是第一行的大小
	printf("%d\n", sizeof(a[0] + 1));//4/8	a[0]作为第一行的数组名,并没有单独放在sizeof内部,也没有被取地址,所以a[0]就是数组首元素的地址,也就是第一行第一个元素的地址,a[0]+1就是第一行第二个元素的地址
	printf("%d\n", sizeof(*(a[0] + 1)));//4	 *(a[0] + 1))表示第一行第二个元素
	printf("%d\n", sizeof(a + 1));//4/8	  a[0]作为首元素的地址,a是二维数组,首元素的地址就是第一行的地址,所以a表示二维数组第一行的地址,a+1就是第二行的地址
	printf("%d\n", sizeof(*(a + 1)));//16	对第二行的地址解引用访问到的就是第二行
	//*(a+1) == a[1]
	//sizeof(a[1])
	printf("%d\n", sizeof(&a[0] + 1));//a[0]是第一行的数组名,&a[0]取出的就是第一行的地址,&a[0]+1就是第二行的地址
	printf("%d\n", sizeof(*(&a[0] + 1)));//16	对第二行的地址解引用访问到的就是第二行
	printf("%d\n", sizeof(*a));//16	a就是首元素的地址,就是第一行的地址,*a就是第一行
	//*a == *(a + 0) == a[0]
	printf("%d\n", sizeof(a[3]));//16 int [4]

总结

sizeof是一个操作符

sizeof 计算的是对象所占内存的大小,单位是字节,size_t

不在乎内存中存放的是什么,只在乎大小

strlen是库函数

只能求字符串,从给定的地址向后访问字符,统计\0之前出现的字符个数

1.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。

2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。

3. 除此之外所有的数组名都表示首元素的地址。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值