指针与数组

文章目录

  • Tips
  • 字符指针
  • 函数指针
  • 回调函数
    • 模拟实现qsort函数
      • 代码实现
  • 数组形参的两种形式(以二维为例)
    • 1.二维数组的形式(也可以写成`int arr[][5]`)
    • 2.指针形式
  • 面试题
    • (1)
    • (2)
    • (3)
    • (4)
  • 变长数组与柔性数组
    • 变长数组(可以用变量确定元素个数)
    • 柔性数组

Tips

1.数组名就是数组的首元素地址(但有两个例外)

  • sizeof(数组名),这里数组名表示整个数组,sizeof计算的是整个数组的大小.
  • &数组名,这里数组名也表示整个数组,&数组名取出的是整个数组的大小.

2.指针类型决定了指针加减整数的步长和解引用操作时的权限

3.[ ] 下标引用操作符

[ ] 操作数:一个数组名 + 一个索引值

  • 操作数交换一下位置是可以的.(仅做科普,实际不要这么写)
    指针与数组_第1张图片
  • 常量字符串返回的是首元素的地址,所以可以这么用(也是仅做科普)
    指针与数组_第2张图片

4.void*指针

  • 不能进行解引用操作,也不能进行±整数操作
  • 可以用来存放任意类型数据的地址

字符指针

指针与数组_第3张图片
str3 和 str4 指向同一块空间.

函数指针

和数组指针类似.先看看数组指针.
指针与数组_第4张图片
pa就是数组指针变量.

而函数名(&函数名)就是函数的地址

指针与数组_第5张图片
所以例子中函数指针指向 Add 和 &Add 都行.(甚至可以不解引用或者很多次解引用使用)
指针与数组_第6张图片

看两个比较复杂的例子
1.void (*signal(int, void (*)(int)))(int);

这个代码是声明 signal 函数
函数参数有两个

  • 第一个是int类型
  • 第二个是函数指针类型void (* )( int )


    返回类型也是函数指针类型,该类型是void (* )( int )

2.( * (void( * )( ))0)( );

将0强制类型转换成void(* )( )函数指针类型
解引用
最右边那个括号是函数的参数

例1可以这样理解(但语法是错的)

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

可以用typedef 重命名一下void(*)(int)

typedef void(*p)(int);

这样例1就等于p signal(int, p);

回调函数

回调函数就是一个通过函数指针调用的函数.如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说是回调函数.

模拟实现qsort函数

qsort是一个库函数,使用快速排序的方式对数据进行排序(这里我们采用冒泡排序的方式)
在这里插入图片描述

  • void* base : 待排序数据的起始地址
  • size_t num : 待排序数据的元素个数
  • size_t size : 待排序数据的一个元素大小,单位是字节
  • int ( * compar)(const void* ,const void* ) : 函数指针 - 指向了一个比较函数,用来比较2个元素

int( * compar)(const void* e1,const void* e2)

  • e1指向的元素>e2指向的元素,返回>0的数字
  • e1指向的元素==e2指向的元素,返回0
  • e1指向的元素

代码实现

Swap函数交换元素
e1是前一个元素,e2是紧接的下一个元素
如果设置e1>e2返回大于0则交换两数(升序),反之降序

void Swap(char* buf1, char* buf2, size_t size)
{
	int i = 0;
	for (i = 0; i < size; i++)
	{
		char temp = *buf1;
		*buf1 = *buf2;
		*buf2 = temp;
		buf1++;
		buf2++;
	}
}

void my_qsort(void* base, size_t num, size_t size, int(*compar)(const void* e1,const void* e2))
{
	int i = 0;
	for (i = 0; i < num - 1; i++)
	{
		int j = 0;
		for (j = 0; j < num - 1 - i; j++)
		{
			if (compar((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
			{
				Swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}
}
  • 整数升序my_qsort第四个参数:
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

整数倒序(将e1,e2交换一下顺序)

int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e2 - *(int*)e2;
}
  • 定义结构体
struct Stu
{
	char name[20];
	int age;
};

按name升序(只是英文字母,按ascll码的大小)

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

按age升序

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

数组形参的两种形式(以二维为例)

令arr是二维数组的数组名,是首元素( &arr[0] )的地址

1.二维数组的形式(也可以写成int arr[][5])

指针与数组_第7张图片

2.指针形式

指针与数组_第8张图片

面试题

(1)

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

在这里插入图片描述

pa++跳过一个char*指针,指向了"at"

(2)

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

在这里插入图片描述

%p是打印地址,认为内存中存储的补码就是地址.
指针与数组_第9张图片
&p[4][2] - &a[4][2] 等于-4
-4的补码是FFFFFFFFFFFFFFFC

(3)


//在x86的环境下演示
//在这里结构体的大小是20字节
struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p;

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

指针与数组_第10张图片

  • 16进制的14就是20
  • 第二个是强转成整型,不是指针

(4)

int main()
{
	int a[3][4] = { 0 };
	printf("%zd\n", sizeof(a));//48
	printf("%zd\n", sizeof(a[0]));//16
	printf("%zd\n", sizeof(a[0]+1));//4/8 - 第一行第一个元素的地址&a[0][1] 
	//a[0]并非单独放在sizeof内部,也没有&,所以a[0]表示第一行这个一维数组首元素地址
	printf("%zd\n", sizeof(*(a[0]+1)));//4 - int类型
	printf("%zd\n", sizeof(a+1));//4/8
	//a+1 就是第二行的地址,类型是:int (*)[4]
	printf("%zd\n", sizeof(*(a+1)));//16 - 第二行,也就是a[1]
	//sizeof(a[1]) - 数组名单独放在sizeof内部,计算的是第二行的大小
	printf("%zd\n", sizeof(&a[0]+1));//4/8
	//a[0]是第一行的数组名,&a[0]取出的是第一行这个一维数组的地址,类型就是int(*)[4]
	//&a[0]+1 就是第二行的地址,类型是int(*)[4]
	printf("%zd\n", sizeof(*(&a[0] + 1)));//16 - 跟*(a+1))一样
	printf("%zd\n", sizeof(*a));//16
	printf("%zd\n", sizeof(a[3]));//16 - sizeof没有访问空间,所以不算越界访问
	return 0;
}

变长数组与柔性数组

变长数组(可以用变量确定元素个数)

虽然C语言C99支持变长数组,但是变长数组不能初始化.

柔性数组

C99中,结构体中的最后一个元素允许是未知大小的数据,这就叫做柔性数组成员

特点:

  • 柔性数组成员前面必须至少有一个其他成员
  • sizeof返回的结构体大小不包括柔性数组
  • 包含柔性数组的结构体用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小

两种定义方式(arr[] 和 arr[0])

#include
#include
struct s
{
	char a;
	int arr[];//或者int arr[0]
};
int main()
{
	int i = 0;
	struct s* p = (struct s*)malloc(sizeof(struct s) + 10 * sizeof(int));
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	p->a = 97;
	printf("%c\n", p->a);
	for (i = 0; i < 10; i++)
	{
		p->arr[i] = i;
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", p->arr[i]);
	}
	free(p);
	p = NULL;
	return 0;
}

不用柔性数组

#include
#include
struct S
{
	int n;
	int* arr;
};
int main()
{
	struct S* ps = (struct S*)malloc(sizeof(struct S));
	if (ps == NULL)
	{
		perror("malloc");
		return 1;
	}
	ps->arr = (int*)malloc(5 * sizeof(int));
	if (ps->arr == NULL)
	{
		perror("malloc");
		return 1;
	}
	int i = 0;
	for (i = 0; i < 5; i++)
		ps->arr[i] = i;
	for (i = 0; i < 5; i++)
		printf("%d ", ps->arr[i]);
	int* ptr = (int*)realloc(ps->arr, 10 * sizeof(int));
	if (ptr != NULL)
	{
		ps->arr = ptr;
		ptr = NULL;
	}
	for (i = 5; i < 10; i++)
		ps->arr[i] = i;
	for (i = 0; i < 10; i++)
		printf("%d ", ps->arr[i]);
	//注意顺序
	free(ps->arr);
	ps->arr = NULL;
	free(ps);
	ps = NULL;
	return 0;
}

上述代码都可以完成相同的功能,但柔性数组方便内存释放,访问速度更快,减少内存碎片

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