C语言指针详解

文章目录

1.字符指针

指针初步介绍

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

两种使用方式

int main()
{
	//1.
	/*char ch = 'w';
	char* pc = &ch;*/
	//2.
	const char* ptr = "hello world";
      //ptr中存放的是字符串“hello world”的首字符的地址
	return 0;
}

面试例题


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

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

2.指针数组

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

    int* arr[10];   //整形指针数组
    char* arr1[5];  //字符指针数组
    char** arr2[5]; //二级字符指针数组
typedef int* pint   //相当于重新定义了一种类型
#define PINT int*   //完全替换
int main()
{
  /* 
    int a, b;  a,b都是int类型

    int* pa, pb; pa是int*类型,pb是int类型

    pint pa, pb; pa,pb都是int*类型

    PINT pa, pb;  pa是int*类型,pb是int类型
    */
    return 0;
}

3.数组指针

指向数组的指针

int main()
{
	int(*pa)[10];
	//pa先和*结合代表pa是一个指针,指向的是具有10个整形元素的数组
	//[]的优先级高于*,要想pa先和*结合必须加上()
	return 0;
}

&数组名VS数组名
二者地址一样,但是+1之后的值不一样,&数组名+1,跳过整个数组
数组名+1,跳过数组中的一个元素的大小

int main()
{
	int arr[10] = { 0 };
	printf("arr = %p\n", arr);
	printf("&arr= %p\n", &arr);
	printf("arr+1 = %p\n", arr + 1);
	printf("&arr+1= %p\n", &arr + 1);
	return 0;
}

C语言指针详解
数组指针的使用
既然是数组指针那么里面存放的必须是数组的地址


void print_arr2(int(*p)[5], int row, int col)
{
    int i = 0;
    for (i = 0; i < row; i++)
    {
        int j;
        for (j = 0; j < col; j++)
        {
        //*(*(p + i) + j)
        //*(p+i) 相当于拿到了二维数组的第i行,也相当于第i行的数组名
	    //数组名表示首元素的地址,其实也是第i行第一个元素的地址
            printf("%d ", p[i][j]);
            //printf("%d ",*(*(p+i)+j));
        }
        printf("\n");
    }
}
int main()
{
    int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
    //数组名arr,表示首元素的地址
    //但是二维数组的首元素是二维数组的第一行
    //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
    //可以数组指针来接收
    print_arr2(arr, 3, 5);
    return 0;
}

4.数组传参和指针传参

二维数组传参

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

C语言指针详解
C语言指针详解
二级指针传参
C语言指针详解

5.函数指针


	//1. 把0强制类型转换为void (*)()类型的函数指针
	//2. 再去调用0地址处这个参数为无参,返回类型是void的函数
	//( *( void (*)() )0 )();//这是依次函数调用,调用0地址处的函数
int Add(int x, int y)
{
	return x + y;
}

void test(char* str)
{

}
int main()
{
	//1. 把0强制类型转换为void (*)()类型的函数指针	
    // 2. 再去调用0地址处这个参数为无参,返回类型是void的函数
	//( *( void (*)() )0 )();//这是依次函数调用,调用0地址处的函数
	int (*pf)(int, int) = Add;
	void(*pt)(char*) = test;
	return 0;
	
}
typedef void(*pf_fun)(int); //将void(*)(int)类型重定义为pf_fun
int main()
{
	//signal先与()结合代表signal是函数说明
	//函数有两个参数,第一个参数是int类型,第二个参数是函数指针类型,此函数指针
	//指向的函数的参数是int返回值为void类型
	//函数的返回值是函数指针类型void(*)(int)
	void (*signal(int, void(*)(int)))(int);
	//简化后:
	//pf_fun  signal(int,pf_fun);
	return 0;
}

6.函数指针数组

int main()
{
    //int(*pa[10])(int ,int) 
	//函数指针数组,pa先于[]结合代表是数组,数组中有10个元素
	//每个元素的类型都是int(*)(int,int)
	return 0;
}

函数指针数组的作用
计算器
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;
}

2.利用函数指针数组实现


#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;
    int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
    while (input)
    {
        printf("*************************\n");
        printf(" 1:add           2:sub \n");
        printf(" 3:mul           4:div \n");
        printf("*************************\n");
        printf("请选择:");
        scanf("%d", &input);
        if ((input <= 4 && input >= 1))
        {
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = (*p[input])(x, y);
        }
        else
            printf("输入有误\n");
        printf("ret = %d\n", ret);
    }
    return 0;
}

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

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

void test(const char* str)
{
	printf("%s\n", str);
}
int main()
{
	//函数指针pfun
	void (*pfun)(const char*) = test;
	//函数指针的数组pfunArr
	void (*pfunArr[5])(const char* str);
	pfunArr[0] = test;
	//指向函数指针数组pfunArr的指针ppfunArr
	void (*(*ppfunArr)[5])(const char*) = &pfunArr;
	return 0;
}

8.回调函数

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

qsort函数

void qsort(
	void* base,
	size_t num,
	size_t size,
	int (*compar)(const void*, const void*));

使用回调函数,模拟实现qsort(采用冒泡的方式)

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

//qsort - 库函数 - 快速排序的方法实现的


//比较e1和e2指向的元素
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

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

void bubble_sort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			//if (arr[j] > arr[j + 1])
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				//两个元素的交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}
void test3()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);
}

9.指针和数组面试题的解析

数组名的两种特殊情况
1.数组名单独放在sizeof中,sizeof(数组名),数组名代表的是整个数组
2.&数组名,取出的是整个数组的地址
其他情况数组名都代表数组中首元素的地址

int main()
{
    //一维数组
    int a[] = { 1,2,3,4 };
    printf("%d\n", sizeof(a));       //16  整个数组的大小16个字节
    printf("%d\n", sizeof(a + 0));    //4  a+0代表首元素的地址,地址大小为4/8字节
    printf("%d\n", sizeof(*a));       //4  *a取出第一个元素,元素为整形,大小占4个字节 
    printf("%d\n", sizeof(a + 1));    //4  a+1是第二个元素的地址,地址大小为4/8字节
    printf("%d\n", sizeof(a[1]));     //4  a[1]代表第二个元素,元素为整形,大小占4个字节 
    printf("%d\n", sizeof(&a));       //4  &a代表整个数组的地址,是地址,大小就是4/8字节
    printf("%d\n", sizeof(*&a));    //16  &a代表整个数组的地址,在对整个数组的地址解引用
                                    //得到的是整个数组,整个数组所占空间大小为16字节
                                    //另一种理解*&抵消,相当于sizeof(a)
    printf("%d\n", sizeof(&a + 1));   //4 地址的大小   4/8
    printf("%d\n", sizeof(&a[0]));    //4  地址的大小   4/8
    printf("%d\n", sizeof(&a[0] + 1)); //4  地址的大小  4/8
    return 0;
}
int main()
{
    //字符数组
    char arr[] = { 'a','b','c','d','e','f' };
    printf("%d\n", sizeof(arr));    // 6  整个数组的大小  6个字节
    printf("%d\n", sizeof(arr + 0));  //4   地址的大小  4/8
    printf("%d\n", sizeof(*arr));    // 1  首元素的大小  1个字节
    printf("%d\n", sizeof(arr[1]));  // 1  第二个元素的大小  1个字节
    printf("%d\n", sizeof(&arr));    // 4   地址的大小    4/8字节
    printf("%d\n", sizeof(&arr + 1));   // 4  地址的大小   4/8字节
    printf("%d\n", sizeof(&arr[0] + 1)) ;//4  地址的大小   4/8字节
    return 0;
}
int main()
{
    //strlen 计算字符串长度的函数,遇到/0截止

    char arr[] = { 'a','b','c','d','e','f' }; 
    printf("%d\n", strlen(arr));  //因为在初始化字符数组时,结尾没有放'\0',结果是随机值
    printf("%d\n", strlen(arr + 0)); //和第一个同样
    //printf("%d\n", strlen(*arr));  //error  strlen接收的是地址
    //printf("%d\n", strlen(arr[1]));//error
    printf("%d\n", strlen(&arr));  //随机值
     printf("%d\n", strlen(&arr + 1)); //随机值
    printf("%d\n", strlen(&arr[0] + 1));//随机值
    return 0;
}
int main()
{
    char arr[] = "abcdef";     //用字符串常量初始化的数组,在字符串的末尾自动添加了'\0'
    printf("%d\n", sizeof(arr));  //7   abcedf\0  数组中存储了7个元素
    printf("%d\n", sizeof(arr + 0));//4  地址的大小 
    printf("%d\n", sizeof(*arr)); //1 首元素的大小
    printf("%d\n", sizeof(arr[1]));//1  第二个元素的大小 
    printf("%d\n", sizeof(&arr)); //4  地址的大小
    printf("%d\n", sizeof(&arr + 1));//地址的大小  4
    printf("%d\n", sizeof(&arr[0] + 1));//4  地址
    printf("%d\n", strlen(arr)); //6  
    printf("%d\n", strlen(arr + 0));//6
    //printf("%d\n", strlen(*arr));//error
   // printf("%d\n", strlen(arr[1]));//error
    printf("%d\n", strlen(&arr));//6
    printf("%d\n", strlen(&arr + 1));//随机值
    printf("%d\n", strlen(&arr[0] + 1));//5

    return 0;
}
int main()
{
    char* p = "abcdef";
    //printf("%d\n", sizeof(p));  //4
    //printf("%d\n", sizeof(p + 1));//4
    //printf("%d\n", sizeof(*p)); //1
    //printf("%d\n", sizeof(p[0]));//1
    //printf("%d\n", sizeof(&p)); //4
    //printf("%d\n", sizeof(&p + 1));//4
    //printf("%d\n", sizeof(&p[0] + 1)); //4
    //printf("%d\n", strlen(p)); //6
    //printf("%d\n", strlen(p + 1));//5
   //printf("%d\n", strlen(*p));//error
   // printf("%d\n", strlen(p[0]));//error
    //printf("%d\n", strlen(&p));//随机值
   //printf("%d\n", strlen(&p + 1));//随机值
    //printf("%d\n", strlen(&p[0] + 1));//5
    return 0;
}
int main()
{
    //二维数组
    int a[3][4] = { 0 }; 
    printf("%d\n", sizeof(a));  //48   整个数组的大小 12*4
    printf("%d\n", sizeof(a[0][0]));//4 一个元素的大小  
    printf("%d\n", sizeof(a[0]));//16  a[0]一维数组的数组名
    printf("%d\n", sizeof(a[0] + 1)); //4 地址的大小
    printf("%d\n", sizeof(*(a[0] + 1))); //4  a[0]代表a[0][0]的地址,a[0]+1代表
                                         //a[0][1]的地址,解引用得到一个元素
    printf("%d\n", sizeof(a + 1)); //4  //地址的大小 
    printf("%d\n", sizeof(*(a + 1))); //16 第一行的所有元素
    printf("%d\n", sizeof(&a[0] + 1));  //4  地址
    printf("%d\n", sizeof(*(&a[0] + 1))); //16 第二行的所有元素
    printf("%d\n", sizeof(*a)); //    16 第一行所有元素
    printf("%d\n", sizeof(a[3])); //16  sizeof操作符只关注类型,而不会检查数组是否越界
    //a[3]代表一维数组的数组名,sizeof(数组名)计算整个数组的大小
    return 0;
}

笔试题
1.

int main()
{
    int a[5] = { 1, 2, 3, 4, 5 };
    int* ptr = (int*)(&a + 1);
    printf("%d,%d", *(a + 1), *(ptr - 1));//2  5
    return 0;
}

C语言指针详解
2.

struct Test
{
    int Num;
    char* pcName;
    short sDate;
    char cha[2];
    short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
    p = (struct Test*)0x100000;
    printf("%p\n", p + 0x1);                 //00100014
    //p是结构体类型指针,+1跳过20个字节
    printf("%p\n", (unsigned long)p + 0x1);//00100001
    //p被强转为unsigned long类型,该类型+1,还是+1
    printf("%p\n", (unsigned int*)p + 0x1);  //00100004
    //p是int类型指针,int类型指针+1,跳过4个字节
    return 0;
}
int main()
{
    int a[4] = { 1, 2, 3, 4 };
    int* ptr1 = (int*)(&a + 1);  
    int* ptr2 = (int*)((int)a + 1);
    printf("%x,%x", ptr1[-1], *ptr2);  //4    2000000
    return 0;
}

C语言指针详解
4.

int main()
{
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };
    //符号表达式最终的运算结果是最右侧的值,故实际存储在数组中的数是1,3,5,0,0,0
    int* p;
    p = a[0];   //p指向a[0][0]
    printf("%d", p[0]); //p[0]相当于 *(p+0),结果为1
    return 0;
}
int main()
{
    int a[5][5];
    int(*p)[4];
    p = a; 
    //指针相减得到的是之间的内存块数目
    //&p[4][2]-&a[4][2]=-4
    //%p是以16进制打印-4在内存中的补码
    printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
    //FFFFFFFC   -4
    return 0;
}

C语言指针详解
6.

int main()
{
    int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    int* ptr1 = (int*)(&aa + 1);
    int* ptr2 = (int*)(*(aa + 1));
    printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));//10  5
    return 0;
}

C语言指针详解
7.

int main()
{
    char* a[] = { "work","at","alibaba" };
    char** pa = a; //pa指向a[0]
    pa++;   //pa++后指向a[1]
    printf("%s\n", *pa);//at
    return 0;
}

C语言指针详解

上一篇:一维数组和二维数组


下一篇:冒泡排序(C++)