[C语言] 指针进阶


指针进阶需要一些指针初阶的前置知识, 需要的小伙伴可以自取:

指针初阶: http://t.csdn.cn/E00P4

一. 字符指针

1. 一般用法

	char ch = 'w';
    char *pc = &ch;
    *pc = 'w';

2. 常见用法

#include <stdio.h>
int main()
{
    const char * p = "abcdef";
    // 最好加上 const 修饰 *, 那么指着指向的内容就不能改
    // 因为 "abcdef" 本身就是字符串常量
    // 如果 *p = 'w' 就会报错
    printf("%c\n", *p);
    printf("%s\n", p);
    return 0;
}

结果:

在这里插入图片描述
原理:

在这里插入图片描述

3. 常见面试题

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

答案:
在这里插入图片描述
原因:
str1 和 str2 分别在内存中开辟了一块空间, 并都用 “hello world.” 初始化, 因为是两块空间, 所以地址是不相同的
str3 和 str4 都是常量字符串, 不能被修改,并且内容一摸一样, 那么只在内存中存储一份即可

在这里插入图片描述

二. 指针数组

指针数组: 存放指针的数组

在这里插入图片描述

更高级的用法:

在这里插入图片描述

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

三. 数组指针

1. 数组指针的定义

数组指针: 是数组还是指针? 是指针, 指向一个数组的指针
(不是指向数组首地址, 而是指向一个数组, 当然值和数组首地址是相等的)
//p1, p2分别是什么?
int *p1[10];
int (*p2)[10];

p1 是数组, p1 先与 [ ] 结合, 数组元素个数是10, 每个元素类型是 int *
p2 是指针, 是数组指针, p2 先与 * 结合, 指向一个数组, 数组有 10 个元素, 每个元素类型是 int

int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
// 数组名表示首元素地址, 但有两个例外
// 1. &数组名, 2. sizeof(数组名)
// 这两种情况表示整个数组 
p2 = &arr // 这里面 &arr 的类型就是 int (*)[10]

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

测试一下, p 的类型应该怎么写?

char* arr[5];
p = &arr

答案:

// p 是一个数组指针, 指向一个数组, 数组有 5 个元素, 每个元素类型为 char *
char* (*p)[5] = &arr;

2. (数组名) vs (&数组名)

通常情况下: 数组名表示首元素地址
两个例外:
sizeof(数组名) 计算的是整个数组的大小
&数组名 , 取出的是整个数组的地址

#include <stdio.h>
int main()
{
    int arr[10] = {0};
	printf("%d\n", sizeof(arr));       
	printf("%p\n", arr);
	printf("%p\n", &arr + 1);	// 跳过整个数组
    return 0;
}

结果:
在这里插入图片描述

3. 数组指针的使用

打印二维数组:

#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++)
        {
        	// arr[i] == *(arr+i)
        	// arr[i][j] == *(*(arr+i) + j)
        	// arr + i, 就指向第 i 行
        	// *(arr+i) 得到一维数组, 相当于一维数组的数组名
        	// *(arr+i) + j 指向一维数组的 下标为 j 的元素
        	// *(*(arr+i) + j) 就得到第 i 行 第 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;
}

在这里插入图片描述
注意: 这里面 &arr 得到的就是整个二维数组
这里面不用拿到整个数组的地址, 只需要拿到首元素地址(二维数组的首元素是一维数组), 然后往后面遍历就能拿到所有的元素了

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

接下来考你一下, 下面分别代表什么

int arr[5]; // 数组, 有 5 个元素, 每个元素类型为 int
int *parr1[10]; // 数组, 有 10 个元素, 每个元素类型为 int *
int (*parr2)[10]; // 指针, 指向一个数组, 数组中有 10 个元素, 每个元素类型为 int 
int (*parr3[10])[5]; 
// 存放数组指针的数组
// 指针数组, 数组中有 10 个元素, 每个元素是一个数组指针 int(*)[5], 每个指针指向一个数组, 每个数组中有 5 个元素, 每个元素的类型是 int 

在这里插入图片描述

四. 数组传参和指针传参

1. 一维数组传参

#include <stdio.h>
void test(int arr[]){} //ok?  
// 可以, 数组传参, 本质传的是首元素地址, 并用将数组所有元素传过去, 也并不会真实创建数组, 所以参数中元素个数没有意义
void test(int arr[10]){} //ok?
// 可以, 数组传参, 本质传的是首元素地址, 所以元素参数中元素个数没有意义, 这里面看着是一个数组, 其实是一个指针, 编译器也将它当作指针来处理
void test(int arr[10000]){} //ok?
// 可以但是不建议
void test(int *arr){} //ok?
// 可以, 传的本身就是首元素地址, 所以传指针是正确的
void test2(int *arr[20]){} //ok?
// 可以, 这是写成数组的形式
void test2(int **arr){} //ok?
// 可以, 这里是写成指针的形式
// int* arr2[20], 首元素就是一个指针, 首元素的地址就是二级指针
int main()
{
	int arr[10] = {0};
	int *arr2[20] = {0};
	test(arr);
	test2(arr2);
}

2. 二维数组传参

void test(int arr[3][5]){} //ok?
// 当然可以
void test(int arr[][]){} //ok?
// 不可以
void test(int arr[][5]){} //ok?
// 可以
// 总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
// 因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
// 二维数组本质上在内存中也是连续存放的, 第一行后面跟着第二行, 第二行后面跟着第三行...
// 只有知道每行元素有多少个, 这样第二、三...行才知道跟在第几个元素后面

// 形参写成指针的形式
void test(int *arr){} //ok?
// 不可以
void test(int* arr[5]){} //ok?
// 不可以
void test(int (*arr)[5]){} //ok?
// 可以
// 二维数组首元素即为第一个一维数组, 所以首元素的地址类型为 int (*) [5]
void test(int **arr){} //ok?
// 不可以
// 二级指针是用来存放一级指针的地址的
// 数组的地址是用数组指针来存放的
int main()
{
	int arr[3][5] = {0};
	test(arr);
}

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);
	print(arr, sz);
	print(&arr[0], sz);
	return 0;
}

4. 二级指针传参

#include <stdio.h>
void test(int** ptr)
{
	// ...
}
int main()
{
	int n = 10;
	int*p = &n;
	int **pp = &p;
	int* arr[4];
	// 三个都可以, 本质都是二级指针
	test(pp);
	test(&p);
	test(arr);
	return 0;
}

五. 函数指针

函数指针: 指向函数的指针

函数的地址: 函数名 或者 &函数名
都表示函数的地址

#include <stdio.h>
void test(int a, int b)
{
	printf("hehe\n");
}
int main()
{
	printf("%p\n", test);
	printf("%p\n", &test);
	return 0;
}

在这里插入图片描述
怎么存储函数的地址? 地址当然用指针存储, 用哪种类型呢? 函数指针

void (*pf1) (int, int) = &test
void (*pf2) (int, int) = test

pf1, pf2 都是函数指针
pf1/2 先与 * 结合, 表示是一个指针, void (*) (int, int) 表示是一个函数指针, 指向一个函数, 函数的参数类型为 (int, int) , 函数返回值为 void

怎么根据函数指针找到这个函数呢?

#include <stdio.h>
int test(int a, int b)
{
	return a + b;
}
int main()
{
	int (*pf) (int, int) = test;
	int ret1 = test(2, 3);
	int ret2 = (*pf)(2, 3);
	int ret3 = pf(2, 3);
	int ret4 = (*****pf)(2, 3);
	printf("%d\n", ret1);
	printf("%d\n", ret2);
	printf("%d\n", ret3);
	printf("%d\n", ret4);
	return 0;
}

在这里插入图片描述

①解引用当然能找到
②不加 * 也能找到
由 int (*pf) (int, int) = test 看出, 其实 pf 和 test 其实是一回事
用 test(2, 3) 能调用 那么 pf(2, 3) 就也能调用
③使用多个 *
其实 (*pf)(2, 3) 里面这个 * 没有什么实际意义, 不加也行, 加上多个也行

两个比较复杂的函数指针的使用:

//代码1
(*(void (*)())0)(); // 整体是函数的调用
//代码2
void (*signal(int , void(*)(int)))(int); // 整体是函数的声明
// 代码 1
(void (*)())00 地址强制转化为一个函数指针, 指向一个 没有参数(), 没有返回值 void 的函数
(地址就是一个数字 如 0x11223344 那么 0 当然也可以作为一个地址)
意味着 0 地址出存放一个 返回类型为 void, 没有参数的函数
(*(void (*)())0)()
假如令  pf = (void (*)())0
那么就是 (*pf)()
就是对 0 地址处这个函数的调用
// 代码 2
先把 signal(int , void(*)(int)) 拿出来
可能你会觉得这是一个函数调用, 但是他不是, 首先参数里面是类型, 不是具体的值
所以判定它应该是一个函数声明, signal 是函数名, 有两个参数一个类型为 int, 另一个参数是一个函数指针
那么这个函数的返回值是什么呢, 也是一个函数指针 void (*)(int)
void (*) int signal (int , void(*)(int)) 虽然这段代码表达的就是这个意思, 但是不能这样写
写的更简单一点就是
typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);

六. 函数指针数组

函数指针数组: 存放的类型是函数指针的数组
如 int (*parr[10])(int, int)
parri 先与 [] 结合, 表示一个数组, 数组每个元素是一个函数指针
指向的函数的参数类型为 (int, int) 返回值也是 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(*p[4])(int x, int y) = { add, sub, mul, div };
	for (int i = 0; i < 4; i++)
	{
		printf("%d\n", p[i](18, 3));
	}
	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;
    // 转移表, 有一个跳转的效果
    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;
}

以后如果增加功能, 直接加进去即可, 其他代码就不用动
当然它有局限性, 就是所有函数的参数以及返回值都要一样

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

首先, 它是一个指针, 其次, 它指向一个数组, 而数组里面的元素是函数指针

#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()
{
	//函数指针数组 pfunArr
	int (*pfunArr[4])(int, int);
	pfunArr[0] = add;
	pfunArr[1] = sub;
	pfunArr[2] = mul;
	pfunArr[3] = div;
	//指向函数指针数组pfunArr的指针ppfunArr
	int (*(*ppfunArr)[5])(int, int) = &pfunArr;
	for (int i = 0;i < 4;i++)
	{
		printf("%d ", (*ppfunArr)[i](18, 3));
	}
	return 0;
}

结果:
在这里插入图片描述

(*ppfunArr)  ->  pfunArr
(*ppfunArr)[i]  ->  pfunArr[i]

在这里插入图片描述

八. 回调函数

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

回调函数例子: qsort的使用

#include <stdio.h>
// qosrt 函数的使用者得实现一个比较函数
int int_cmp(const void* p1, const void* p2)
{
    return (*(int*)p1 - *(int*)p2);
}
int main()
{
    int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
    int i = 0;
    // 这里面 int_cmp 就是作为一个回调函数
    // 将函数 int_cmp 的地址作为 参数传入 qsort 函数, 在 qsort 函数里面进行调用
    qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);
    for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}
void qsort (void* base, size_t num, size_t size,
            int (*compar)(const void* e1,const void* e2));
base: 待排序数组的起始位置
num: 待排序数组元素个数
size: 每个元素的字节大小
compar: 函数指针, e1, e2 为两个待比较元素, 返回值为 int
要求 qsort 函数的使用者, 自定义一个比较函数, 指定比较规则
因为 假如比较对象是一个结构体的话, 不方便直接使用 > < == 进行比较了,需根据实际情况, 指定比较规则

使用 qsort 排序结构体:

#include <stdio.h>

typedef struct Stu {
    char name[20];
    int age;
    double score;
}Stu;

int cmp_stu_by_age(const void* e1, const void* e2)
{
    return ((Stu*)e1)->age - ((Stu*)e2)->age;
}

int cmp_stu_by_name(const void* e1, const void* e2)
{
    return strcmp(((Stu*)e1)->name,  ((Stu*)e2)->name);
}

void print(Stu arr[3], int size)
{
    for (int i = 0;i < size;i++)
    {
        printf("%s\t%d\t%f\n", arr[i].name, arr[i].age, arr[i].score);
    }
}

void test()
{
    Stu arr[3] = { {"zhangsan", 20, 55.1}, {"lisi", 30, 78.6}, {"wangwu", 10, 99.9} };
    int sz = sizeof(arr) / sizeof(arr[0]);
    qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
    printf("根据年龄排序:\n");
    print(arr, 3);
    qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
    printf("根据姓名排序:\n");
    print(arr, 3);
}

int main()
{
    test();
    return 0;
}

在这里插入图片描述
使用回调函数,模拟实现qsort(采用冒泡的方式):

#include <stdio.h>
// 可以排序任何类型, 但是需要自己指定比较规则
int int_cmp(const void* p1, const void* p2)
{
    return (*(int*)p1 - *(int*)p2);
}
void _swap(void* p1, void* p2, int size)
{
    int i = 0;
    for (i = 0; i < size; i++)
    {
        char tmp = *((char*)p1 + i);
        *((char*)p1 + i) = *((char*)p2 + i);
        *((char*)p2 + i) = tmp;
    }
}
void bubble(void* base, int count, int size, int(*cmp)(void*, void*))
{
    int i = 0;
    int j = 0;
    for (i = 0; i < count - 1; i++)
    {
        for (j = 0; j < count - i - 1; j++)
        {
            if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
            {
                _swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
            }
        }
    }
}
int main()
{
    int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
    //char *arr[] = {"aaaa","dddd","cccc","bbbb"};
    int i = 0;
    bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);
    for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}

九. 指针和数组笔试题

sizeof():
一个操作符, 计算对象所占内存的大小, 单位是 字节 size_t
不在乎内存中放的是什么, 只在乎内存大小
strlen():
求字符串长度, 从给定的地址向后访问字符, 统计 ‘\0’ 之前出现的字符个数

	//一维数组
	int a[] = {1,2,3,4};
	printf("%d\n",sizeof(a)); // 4*4 = 16 整个数组的大小 
	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 表示首元素地址, a+1 就是数组第二个元素地址 
	printf("%d\n",sizeof(a[1])); // 4 第二个元素的大小 
	printf("%d\n",sizeof(&a)); // 4/8 &a 是整个数组的地址, 是地址, 大小就是 4/8  
	printf("%d\n",sizeof(*&a)); // 16 &a 得到整个数组的地址, 再解引用, 得到整个数组 
	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 数组首个元素的地址 
	printf("%d\n", sizeof(*arr)); // 1 arr是数组首元素地址, *arr 就是首元素 
	printf("%d\n", sizeof(arr[1])); // 1 第二个元素的大小 
	printf("%d\n", sizeof(&arr)); // 4/8 整个数组的地址, 是地址, 大小就是 4/8 
	printf("%d\n", sizeof(&arr+1)); // 4/8 &arr是整个数组的地址, +1 是跳过整个数组后的下一个地址 
	printf("%d\n", sizeof(&arr[0]+1)); // 4/8 数组第二个元素的地址 
#include <stdio.h>
#include <stdlib.h>
int main()
{
	//字符数组
	char arr[] = { 'a','b','c','d','e','f' };
	// strlen 是根据传入的地址, 找到第一个'\0' 之前的字符串的长度 
	printf("%d\n", strlen(arr)); // 随机值, 因为只知道数组中的元素都有哪些, 并不知道什么时候会出现 '\0' 
	printf("%d\n", strlen(arr + 0)); // 随机值 
	// printf("%d\n", strlen(*arr)); // err *arr -> 'a' -> 97, 把 97 作为起始地址进行访问是非法的, 这些地址就不是分配给我们的, 我们不能访问
	// printf("%d\n", strlen(arr[1])); // err 同理 arr[1] -> 'b' -> 98 非法访问 
	printf("%d\n", strlen(&arr)); // 随机值,但是与strlen(arr), strlen(arr + 0) 相等
	printf("%d\n", strlen(&arr+1)); // 随机值,但比 strlen(arr) 小数组的长度, 这里面就是 6 
	printf("%d\n", strlen(&arr[0] + 1)); // 随机值, 但比 strlen(arr) 小1
}

在这里插入图片描述

	char arr[] = "abcdef";
	// 数组里面其实储存的是 'a', 'b', 'c', 'd', 'e', 'f', '\0' 
	printf("%d\n", sizeof(arr)); // 7 注意需要算上 '\0' 
	printf("%d\n", sizeof(arr+0)); // 4/8 首元素地址 
	// printf("%d\n", sizeof(*arr)); // err 
	// printf("%d\n", sizeof(arr[1])); // err
	printf("%d\n", sizeof(&arr)); // 4/8 整个数组的地址 
	printf("%d\n", sizeof(&arr+1)); // 4/8 跳过整个数组后的地址 
	printf("%d\n", sizeof(&arr[0]+1)); // 4 第二个元素的地址 
	char arr[] = "abcdef";
	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)); // 随机值, 并不知道什么时候遇到 '\0' 
	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 第二个字符 'b' 的地址 
	printf("%d\n", sizeof(*p)); // 1 第一个字符 'a' 
	printf("%d\n", sizeof(p[0])); // 1 同理 'a' 
	printf("%d\n", sizeof(&p)); // 4/8  p 在内存中的地址 
	printf("%d\n", sizeof(&p + 1)); // 4/8 跳过 p 之后的地址 
	printf("%d\n", sizeof(&p[0] + 1)); // 4/8 第二个字符 '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

在这里插入图片描述
二维数组的理解:
二维数组可以看作一维数组, 二维数组的每一行就是一个一维数组

//二维数组
	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 内部 
	
	printf("%d\n",sizeof(*(a[0]+1))); //  4 第一行第二个元素 
	printf("%d\n",sizeof(a+1)); // 4/8 第二行元素的地址 
	// a 数组名没有单独放到 sizeof 内部, 就表示首元素地址, 二维数组的首元素就是一维数组
	// a + 1 就表示第二行的地址
	 
	printf("%d\n",sizeof(*(a+1))); // 16 第二行元素 4*4 = 16 
	printf("%d\n",sizeof(&a[0]+1)); // 4/8 第二行元素的地址 
	printf("%d\n",sizeof(*(&a[0]+1))); // 16 二行元素 4*4 = 16 
	printf("%d\n",sizeof(*a)); // 16 第一行元素 4*4 = 16 
	printf("%d\n",sizeof(a[3])); // 16, a[3] 明显越界了, 但是这里面并不会真正访问这一块内存, 
	// 而是只要知道 a[3] 的类型即可, a[3] 的类型就是  int [4]

在这里插入图片描述

总结:
数组名的意义:

  1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
  2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
  3. 除此之外所有的数组名都表示首元素的地址。

十. 指针笔试题

int main()
{
    int a[5] = { 1, 2, 3, 4, 5 };
    int *ptr = (int *)(&a + 1);
    printf( "%d,%d", *(a + 1), *(ptr - 1));
    return 0;
}
//程序的结果是什么?
2, 5

在这里插入图片描述

#include <stdio.h>
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);
	printf("%p\n", (unsigned long)p + 0x1);
	printf("%p\n", (unsigned int*)p + 0x1);
	return 0;
}

结果:
在这里插入图片描述
原因:
① p 是指针, 指针+1 跳过所指向类型的大小, 这里面这个结构体大小为 20B, 16进制就是 0x14
② 强制类型转换为 (unsigned long) 整数, 而不是 (unsigned long *) 指针, 所以 整数 +1 就是值 +1, 十进制 1 和 十六进制的 1 一样
③ 强制类型转换为 (unsigned int*) , +1 就跳过一个 int 的大小

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

结果:
在这里插入图片描述
原因:
在这里插入图片描述

#include <stdio.h>
int main()
{
    int a[3][2] = { (0, 1), (2, 3), (4, 5) }; // 注意逗号表达式
    int *p;
    p = a[0];
    printf( "%d", p[0]);
	return 0;
}

在这里插入图片描述

int main()
{
    int a[5][5];
    int(*p)[4];
    p = a;
    printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
    return 0;
}

结果:
在这里插入图片描述

原因:
在这里插入图片描述

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

结果:
在这里插入图片描述
原因:
在这里插入图片描述

#include <stdio.h>
int main()
{
	char *a[] = {"work","at","alibaba"};
	char**pa = a;
	pa++;
	printf("%s\n", *pa);
	return 0;
}

结果:
在这里插入图片描述

原因:
在这里插入图片描述

int main()
{
	char *c[] = {"ENTER","NEW","POINT","FIRST"};
	char**cp[] = {c+3,c+2,c+1,c};
	char***cpp = cp;
	printf("%s\n", **++cpp); // 先 ++ 再 *, 这里注意 cpp 变化了,  + 1 了
	printf("%s\n", *--*++cpp+3); // 先 ++ 指向 c+1, * 后再 --, cp[2] 里面的内容变为 c, 不再是 c+1, * 指向 第一个E, +3 指向 第二个 E 
	// 上面 cpp 再次变化 + 1 了
	printf("%s\n", *cpp[-2]+3); // -2 重新指向cp[0] 解引用指向 FIRST, +3 指向 S 
	// 上面 虽然 cpp-2, 但是cpp 本省并没有变化
	printf("%s\n", cpp[-1][-1]+1); // cpp[-1] = c+2, -1 后指向 NEW , +1 指向  E
	return 0;
}

结果:
在这里插入图片描述
原因:
在这里插入图片描述
希望能帮到你, 评论区欢迎指正!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值