1. 数组名的理解
简单来说,数组名就是数组⾸元素(第⼀个元素)的地址。
但是有两种特殊情况除外:
- sizeof(数组名)-------这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节
- &数组名-----------这里的数组名也表示整个数组,取出的是整个数组的地址
除以上两种情况之外,所有的数组名都是数组首元素的地址
1.1 数组名和 &数组名的区别
include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("arr = %p\n", arr);
printf("&arr = %p\n", &arr);
return 0;
}
//打印出来的结果是一模一样的
再看如下代码
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("arr = %p\n", arr);
printf("arr+1 = %p\n", arr+1);
printf("&arr = %p\n", &arr);
printf("&arr+1 = %p\n", &arr+1);
return 0;
}
输出结果:
arr = 0077F820
arr+1 = 0077F824
&arr = 0077F820
&arr+1 = 0077F848
这⾥我们发现&arr[0]和&arr[0]+1相差4个字节,arr和arr+1相差4个字节,是因为&arr[0] 和 arr 都是⾸元素的地址,+1就是跳过⼀个元素。
但是&arr 和 &arr+1相差40个字节,这就是因为&arr是数组的地址,+1 操作是跳过整个数组的。
2. 使⽤指针访问数组
int main()
{
int arr[10] = { 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
int *p = arr;
//输入
int i = 0;
for (i = 0; i < sz; i++)
{
scanf("%d", &arr[i]); //&arr[i]可以写成p + i,也可以写成 &p[i]
}
//输出
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]); // arr[i]可以写成 *(p + i),也可以写成p[i]
}
return 0;
}
注意:
arr[i] <==> *(arr+i)
3. ⼀维数组传参的本质
数组名是数组⾸元素的地址;而在数组传参的时候,传递的是数组名,也就是说本质上数组传参传递的是数组⾸元素的地址。此外,数组的大小只能通过另外一个形参传递,而不能通过在函数内部使用 sizeof来计算。
总结:⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。
4. 冒泡排序
冒泡排序的核⼼思想就是:两两相邻的元素进⾏⽐较。
#include <stdio.h>
void bubble_sort(int * arr,int sz){
int i = 0;
for(i = 0;i < sz - 1;i++){
int j = 0;
int flag = 1; //假设这⼀趟已经有序了
int temp = 0;
for(j = 0;j < sz - 1 - i;j++){
if(arr[j] > arr[j+1]){
flag = 0; //发⽣交换就说明,⽆序
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
if(flag == 1) //这⼀趟没交换就说明已经有序,后续⽆序排序了
break;
}
}
int main(){
int arr[6] = {0};
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
for(i = 0;i < sz;i++ ){
scanf("%d",arr+i);
}
bubble_sort(arr,sz);
for(i = 0;i < sz;i++ ){
printf("%d ",arr[i]);
}
return 0;
}
5. ⼆级指针
指针变量也是变量,是变量就有地址,则指针变量的地址存放在二级指针

对于⼆级指针的运算有:
• *ppa 通过对ppa中的地址进⾏解引⽤,这样找到的是 pa , *ppa 其实访问的就是 pa
int b = 20;
*ppa = &b;//等价于 pa = &b;
• **ppa 先通过 *ppa 找到 pa ,然后对 pa 进⾏解引⽤操作: *pa ,那找到的是 a
**ppa = 30;
//等价于*pa = 30;
//等价于a = 30;
6. 指针数组
简单来说,指针数组就是存放指针类型的数组。可类比整型数组----存放整型的数组,字符数组-------存放字符的数组。

指针数组的每个元素都是⽤来存放地址(指针)的。如下图:

7. 指针数组模拟⼆维数组
#include <stdio.h>
int main()
{
int arr1[] = {1,2,3,4,5};
int arr2[] = {2,3,4,5,6};
int arr3[] = {3,4,5,6,7};
//数组名是数组⾸元素的地址,类型是int*的,就可以存放在parr数组中
int* parr[3] = {arr1, arr2, arr3};
int i = 0;
int j = 0;
for(i=0; i<3; i++)
{
for(j=0; j<5; j++)
{
printf("%d ", parr[i][j]);
}
printf("\n");
}
return 0;
}

parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型⼀维数组,parr[i][j]就是整型⼀维数组中的元素。
上述的代码模拟出⼆维数组的效果,实际上并⾮完全是⼆维数组,因为每⼀⾏并⾮是连续的。
8.字符指针变量
在指针的类型中我们知道有⼀种指针类型为字符指针 char* ;
⼀般使⽤:
int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}
还有⼀种使⽤⽅式如下:
int main()
{
const char* pstr = "hello bit.";//这⾥是把⼀个字符串放到pstr指针变量⾥了吗?
printf("%s\n", pstr); //在打印字符串时,只需要知道首字符的地址就行。
return 0;
}
代码 const char* pstr = "hello bit."; 特别容易让人以为是把字符串 hello bit 放到字符指针 pstr ⾥了,但是本质是把字符串 hello bit. ⾸字符的地址放到了pstr中。
《剑指offer》中收录了⼀道和字符串相关的笔试题:
#include <stdio.h>
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;
}
这⾥str3和str4指向的是⼀个同⼀个常量字符串。C/C++会把常量字符串存储到单独的⼀个内存区域,当⼏个指针指向同⼀个字符串的时候,他们实际会指向同⼀块内存。但是⽤相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。
9.数组指针变量
前面章节解释到指针数组是数组,存放的是指针(地址),那数组指针是什么呢?
我们可以做一个类比:
- 字符指针 - char* - 指向字符的指针 - 字符指针变量中存放字符变量的地址
- 整型指针 - int * - 指向整型的指针 - 整型指针变量中存放整型变量的地址
- 数组指针 - 指向数组的指针 - 数组指针变量中存放数组的地址(即&数组名)
数组指针定义如下:
int arr[10] = {0};
int (*p) [10] = &arr;
指针p的类型为 int (*)[10],也称为数组指针类型,存放的是数组的地址。
10.⼆维数组传参的本质
首先要理解二维数组的数组名代表着什么?
其实二维数组的数组名也代表着数组首元素的地址,但与一维数组不同,二位数组的首元素是第一行数组,也就是说二维数组的数组名代表的是二维数组第一行数组的地址。
二维数组可以理解为一维数组的数组------二维数组的每一行可以看做是一个一维数组。
⼆维数组传参本质上也是传递了地址,传递的是第⼀⾏这个⼀维数组的地址。因此,形参也可以写成指针的形式,代码如下:
void print(int (*arr)[5],int r,int c){
int i = 0;
int j = 0;
for(i = 0;i < r;i++)
{
for(j = 0;j < c;j++)
{
printf("%d",*(*(arr+i)+j)); //这里打印是写成了指针的形式,也可以写成数组arr[i][j]
}
printf("\n");
}
}
如何理解*(*(arr+i)+j)) 呢,首先,arr+i 表示二维数组第 i 行数组的地址,即&(arr + i),通过解引用就可以得到第 i 行数组名字,也就是第 i 行数组首元素的地址,在 + j 解引用就可以得到第 i 行 第 j 个元素。
11.函数指针变量
11.1函数指针变量的创建
可以类比数组指针,这里其实和数组指针挺相向的,不难发现,函数指针变量就是用来存放函数的地址。可以通过指针来调用函数。
这里和数组有一个区别,函数的地址可以通过 &函数名得到函数的地址,然而,函数名本身也代表函数的地址。可以做个测试,代码如下:
#include <stdio.h>
void test()
{
printf("hehe\n");
}
int main()
{
printf("test: %p\n", test);
printf("&test: %p\n", &test);
return 0;
}
输出结果如下:
因此,函数指针变量定义如下:
int add(int x,int y){
return x + y;
}
int (*pf3)(int x,int y) = add; // 也可以写成 int (*pf3)(int ,int ) = &add; x和y可以省略
函数指针类型解析:
11.2函数指针变量的使⽤
通过函数指针调⽤指针指向的函数,
#include <stdio.h>
int Add(int x, int y)
{
return x+y;
}
int main()
{
int(*pf3)(int, int) = Add;
printf("%d\n", (*pf3)(2, 3)); // * 可以不写,因为函数名就代表函数的地址。
printf("%d\n", pf3(3, 5));
return 0;
}
// 输出结果:
// 5
// 8
11.3 typedef 关键字
typedef 是⽤来类型重命名的,可以将复杂的类型,简单化。
⽐如,你觉得 unsigned int 写起来不⽅便,如果能写成 uint 就⽅便多了,那么我们可以使⽤:
typedef 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(*pf_t)(int);//新的类型名必须在*的右边
12.函数指针数组
根据前面的理解,很容易发现,函数指针数组 其实就是数组,是一个函数指针类型的数组,
那么函数指针数组该如何定义呢?详见如下代码:
int add(int x,int y){
return x + y;
}
int sub(int x,int y){
return x - y;
}
int mul(int x,int y){
return x * y;
}
int div(int x,int y){
return x / y;
}
int (* arr[4])(int ,int) = {add,sub,mul,div};
13.回调函数
13.1 什么是回调函数
回调函数就是⼀个通过函数指针调⽤的函数,
简单理解,就是把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被⽤来调⽤其所指向的函数时,被调⽤的函数就是回调函数。下面是一个典型的回调函数案例。
13.2 qsort函数
qsort是c语言中的一个库函数,用来对数据进行排序,可以对任意类型的数据排序。
qsort ------- quick sort 底层使用的是快速排序的思想。qsort原型如下:
void qsort (void* base, //指向待排序数组的第一个元素的指针(地址)
size_t num, //代表数组中的元素个数
size_t size, //代表数组中一个元素的大小,单位是字节
int (*compar)(const void*,const void*) //函数指针 -- 传递函数的地址
);
compar函数定义原则:
//须和qsort中函数指针中的参数以及返回类型一模一样。
int compar(const void*p1,const void*p2){
}
将两个指针作为参数(都转换为 const void*)。该函数通过返回(以稳定和传递的方式)来定义元素的顺序:
返回值 | 意义 |
---|---|
> 0 | p1指向的元素 > p2指向的元素 |
== 0 | p1指向的元素 = p2指向的元素 |
< 0 | p1指向的元素 < p2指向的元素 |
可以简单理解为:比较 p1指向的元素和p2指向的元素,
- p1 > p2 则返回 1 (可以是任意大于 0 的数)
- p1 < p2 则返回 -1 (可以是任意小于 0 的数)
- p1 = p2 则返回 0
上述p1 p2 只代表 p1 p2 指向的元素,在实际比较中需要先强制类型转换然后解引用后进行比较。
//须和qsort中函数指针中的参数以及返回类型一模一样。
int compar(const void*p1,const void*p2){
;
}
下面是qsort函数的使用案例(使用时需要添加<stdlib.h>头文件):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>.
struct stu {
char name[20];
int age;
};
int cmp_int(const void *p1,const void *p2) {
return *(int*)p1 - *(int*)p2; //void类型指针不能进行解引用和+-整数操作,必须先进行强制类型转换。
}
int cmp_by_name(const void* p1, const void* p2) {
return strcmp(((struct stu*)p1)->name, ((struct stu*)p2)->name);
}
//排序整型数组
void test1() {
int arr[] = { 8,2,7,5,9,1,3,6,4 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr,sz,sizeof(arr[0]),cmp_int);
int i = 0;
for (i = 0; i < sz;i++) {
printf("%d ",arr[i]);
}
}
//排序结构体数组
void test2() {
struct stu arr[] = { {"zhangsang",21},{"wangwu",18},{"lisi",32} };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr,sz,sizeof(arr[0]),cmp_by_name);//根据名字进行排序
int i = 0;
for (i = 0;i < sz;i++) {
printf("%s\n",arr[i].name);
}
}
int main() {
//test1();
test2();
return 0;
}
13.3 模拟qsort函数实现bsort
bsort--bubble sort---底层思想是冒泡排序。代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>.
struct stu {
char name[20];
int age;
};
//void类型指针不能进行解引用和+-整数操作,必须先进行强制类型转换。
int cmp_int(const void *p1,const void *p2) {
return *(int*)p1 - *(int*)p2;
}
int cmp_by_name(const void* p1, const void* p2) {
return strcmp(((struct stu*)p1)->name, ((struct stu*)p2)->name);
}
void swap(const void* p1, const void* p2, int width) {
int i = 0;
char tmp = 0;
for (i = 0;i < width;i++)
{
tmp = *(char*)p1;
*(char*)p1 = *(char*)p2;
*(char*)p2 = tmp;
((char*)p1)++;
((char*)p2)++;
}
}
bsort(void *base,int sz,int width,int (*cmp)(const void *p1,const void *p2)) {
int i = 0;
int j = 0;
for (i = 0;i < sz -1;i++)
{
for (j = 0; j < sz - 1 - i; j++)
{
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
}
}
}
}
//排序整型数组
void test1() {
int arr[] = { 8,2,7,5,9,1,3,6,4 };
int sz = sizeof(arr) / sizeof(arr[0]);
bsort(arr,sz,sizeof(arr[0]),cmp_int);
int i = 0;
for (i = 0; i < sz;i++) {
printf("%d ",arr[i]);
}
}
//排序结构体数组
void test2() {
struct stu arr[] = { {"zhangsang",21},{"wangwu",18},{"lisi",32} };
int sz = sizeof(arr) / sizeof(arr[0]);
bsort(arr,sz,sizeof(arr[0]),cmp_by_name);//根据名字进行排序
int i = 0;
for (i = 0;i < sz;i++) {
printf("%s %d\n",arr[i].name,arr[i].age);
}
}
int main() {
//test1();
test2();
return 0;
}