医美行业网站建设,网页制作工具程,资源下载网站建设,宜宾网站建设宜宾一、数组 同一类型的变量——元素#xff08;element#xff09;集中在一起#xff0c;在内存上排列成一条直线#xff0c;这就是数组#xff08;array#xff09;。 1.1 一维数组 一维数组的声明 int arr1[10];
int arr2[2 8];#define N 10
int arr3[N];int count 10;…一、数组 同一类型的变量——元素element集中在一起在内存上排列成一条直线这就是数组array。 1.1 一维数组 一维数组的声明 int arr1[10];
int arr2[2 8];#define N 10
int arr3[N];int count 10;
int arr4[count];
// 在C99标准之前[]中间必须是常量表达式
// C99标准支持了变长数组的概念数组大小可以是变量
// 变长数组不能初始化char arr5[10];
float arr6[1];
double arr7[20]; 一维数组的初始化。用0对没有赋初始值的元素进行初始化。/0的ASCII码值是0。 int arr1[10] { 1,2,3 }; // 不完全初始化剩余的元素默认初始化为0
int arr2[10] { 0 }; // 所有元素都为0
int arr3[] { 1,2,3,4 }; // 没有指定数组的大小数组的大小根据初始化的内容来确定
int arr4[5] { 1,2,3,4,5 };
char arr5[3] { a,98,c };
char arr6[10] { a,b,c }; // a b c /0 /0 /0 /0 /0 /0 /0
char arr7[5] abc; // a b c /0 /0// 不能通过赋值语句进行初始化错误写法
int arr8[3];
arr8 { 1,2,3 };
C99增加了一个新特性指定初始化器designated initializer。利用该特性可以初始化指定的数组元素。
// 顺序初始化
int arr[6] { 0,0,0,0,0,80 };
// 指定初始化
int arr[6] { [5] 80 }; // 把arr[5]初始化为80未初始化的元素都为0
#include stdio.hint main()
{int arr[10] { 5,6,[4] 8,9,7,1,[9] 3 };for (int i 0; i 10; i){printf(arr[%d] %d\n, i, arr[i]);}return 0;
} 一维数组的使用 #include stdio.hint main()
{int arr[] { 1,2,3,4,5,6,7,8,9,10 };// 下标从0开始 0 1 2 3 4 5 6 7 8 9// 计算数组元素的个数int sz sizeof(arr) / sizeof(arr[0]);// 遍历一维数组for (int i 0; i sz; i){printf(%d , arr[i]);}return 0;
} 一维数组在内存中的存储 一维数组在内存中是连续存储的。 1.2 二维数组
以一维数组作为元素的数组是二维数组以二维数组为元素的数组是三维数组……统称为多维数组。 二维数组的声明 int arr1[3][4]; // [行][列]
char arr2[3][5];
double arr3[2][4]; 二维数组的初始化 int arr1[3][4] { 1,2,3,4,2,3,4,5,3,4,5,6 };
// 1 2 3 4
// 2 3 4 5
// 3 4 5 6int arr2[3][4] { {1,2},{3,4},{5,6} };
// 1 2 0 0
// 3 4 0 0
// 5 6 0 0int arr3[][2] { 1,2,3,4 }; // 二维数组如果有初始化行数可以省略列数不能省略
// 1 2
// 3 4
指定初始化器对多维数组也有效。
#include stdio.hint main()
{int arr[4][4] { 5,6,[1][3] 8,9,7,1,[3][2] 3 };for (int i 0; i 4; i){for (int j 0; j 4; j){printf(arr[%d][%d] %d\n, i, j, arr[i][j]);}} return 0;
} 二维数组的使用 #include stdio.hint main()
{int arr[3][4] { 1,2,3,4,2,3,4,5,3,4,5,6 };for (int i 0; i 3; i){// 打印一行for (int j 0; j 4; j){printf(%d , arr[i][j]);}printf(\n); // 打印一行后换行}return 0;
}
// 1 2 3 4
// 2 3 4 5
// 3 4 5 6 二维数组在内存中的存储 二维数组在内存中也是连续存储的。 二维数组X按行顺序存储其中每个元素占1个存储单元。若X[4][4]的存储地址为0xf8b82140X[9][9]的存储地址为0xf8b8221c则X[7][7]的存储地址为
假设二维数组X有m行n列第一个元素即X[0][0]的存储地址为start则
X[4][4]的存储地址0xf8b82140start4*n*14*1 ①
X[9][9]的存储地址0xf8b8221cstart9*n*19*1 ②
②-①5n50xdc - 5n0xd7215 - n43
X[7][7]的存储地址start7*n*17*1(start4*n*14*1)3*n30xf8b821401320xf8b821400x840xf8b821c4
1.3 数组名 数组名表示数组首元素的地址是一个常量指针不可以改变指针本身的值没有自增、自减等操作。 数组名和指向数组首元素的指针都可以通过改变偏移量来访问数组中的元素但数组名是常量指针指向数组首元素的指针是一般指针。 以下2种情况下数组名表示整个数组 sizeof(数组名)计算整个数组的大小单位是字节。 数组名取出的是数组的地址。 #include stdio.hint main()
{int arr[10] { 0 };printf(%p\n, arr); // 0096F7CC 数组首元素的地址printf(%p\n, arr 1); // 0096F7D0 指针1跳过4个字节printf(%p\n, arr[0]); // 0096F7CC 数组首元素的地址printf(%p\n, arr[0] 1); // 0096F7D0 指针1跳过4个字节printf(%p\n, arr); // 0096F7CC 数组的地址printf(%p\n, arr 1); // 0096F7F4 指针1跳过整个数组的大小40个字节return 0;
} 当数组名作为函数参数传递时就失去了原有特性退化为一般指针。此时不能通过sizeof运算符获取数组的长度不能判断数组的长度时可能会产生数组越界访问。因此传递数组名时需要一起传递数组的长度。 // errvoid test(int arr[10])
{}
void test(int arr[])
{}
void test(int* arr)
{}int main()
{int arr[10] { 0 };test(arr);return 0;
}
// okvoid test(int arr[10], int n)
{}
void test(int arr[], int n)
{}
void test(int* arr, int n)
{}int main()
{int arr[10] { 0 };int n sizeof(arr) / sizeof(arr[0]);test(arr, n);return 0;
} 二维数组是一维数组的数组二维数组的数组名也表示数组首元素第一个一维数组的地址。 二、指针
2.1 指针和指针变量 指针是内存地址。指针变量是用来存放内存地址的变量但我们叙述时常把指针变量简称为指针。 #include stdio.hint main()
{int a 10; // 在内存中开辟一块空间int* p a; // 操作符取出a的地址// a占用4个字节的空间这里是将a的4个字节的第1个字节的地址存放在p中p就是一个指针变量return 0;
}
在32位的机器上地址是由32个0或者1组成的二进制序列用4个字节的空间来存储所以一个指针变量的大小是4个字节。
在64位的机器上地址是由64个0或者1组成的二进制序列用8个字节的空间来存储所以一个指针变量的大小是8个字节。
2.2 指针类型 int*类型的指针存放int类型变量的地址char*类型的指针存放char类型变量的地址…… 2.2.1 指针-整数 指针的类型决定了指针的步长-1操作的时候跳过几个字节。 int*类型的指针-1跳过4个字节char*类型的指针-1跳过1个字节…… #include stdio.hint main()
{int n 10;char* pc (char*)n;int* pi n;printf(%p\n, n); // 000000DADACFF4E4printf(%p\n, pc); // 000000DADACFF4E4printf(%p\n, pc 1); // 000000DADACFF4E5printf(%p\n, pi); // 000000DADACFF4E4printf(%p\n, pi 1); // 000000DADACFF4E8return 0;
}
2.2.2 指针的解引用 指针的类型决定了对指针解引用的时候有多大的权限能访问几个字节。 int*类型的指针解引用能访问4个字节char*类型的指针解引用能访问1个字节…… 利用int*类型的指针强制转换成char*类型后只能访问1个字节来判断当前计算机是大端模式还是小端模式 #include stdio.hint check_sys()
{int a 1;return *(char*)a;
}int main()
{int ret check_sys();if (ret 1)printf(小端\n);elseprintf(大端\n);return 0;
} 1int型的补码用十六进制表示为0x00000001。 大端模式00 00 00 01 低地址---高地址 小端模式01 00 00 00 低地址---高地址 *(char*)a表示取出a的地址然后强制类型转换为char*再解引用此时只能访问一个字节的内容。如果这一个字节的内容为0为大端模式如果这一个字节的内容为1为小端模式。 2.3 野指针 野指针是指针指向的位置是不可知的随机的、不正确的、没有明确限制的。 2.3.1 野指针的成因 指针未初始化指针越界访问指针指向的空间释放 2.3.2 规避野指针 指针初始化小心指针越界指针指向空间释放及时置NULL避免返回局部变量的地址指针使用之前检查有效性 #include stdio.hint main()
{int* p NULL;// ...int a 10;p a;if (p ! NULL){*p 20;}return 0;
}
2.4 指针运算
2.4.1 指针自增 p 先使用p再自增p p 先自增p再使用p (*p) 先使用*p再自增*p *p或*(p) 解引用*和后置自增优先级相同结合性都是从右往左所以*p等价于*(p)先使用*p再自增p *p或(*p) 先自增(*p)再使用*p *p或*(p) 先自增p再使用*p 2.4.2 指针-指针 指针-指针的绝对值是指针之间元素的个数。指向同一块区间的两个指针才能相减。 高地址-低地址正数低地址-高地址负数。 #include stdio.hint main()
{int arr[5] { 1,2,3,4,5 };int* p1 arr; // 指向arr[0]int* p2 arr 3; // 指向arr[3]printf(%d\n, p2 - p1); // 3printf(%d\n, p1 - p2); // -3return 0;
}
2.4.3 指针的关系运算 可以用关系运算符进行指针比较。只有在两个指针指向同一数组时用关系运算符进行的指针比较才有意义。比较的结果依赖于数组中两个元素的相对位置。 #include stdio.hint main()
{int arr[5] { 1,2,3,4,5 };int* p1 arr;int* p2 arr[3];if (p1 p2){printf(p1 p2\n);}else{printf(p1 p2\n);}// p1 p2return 0;
}
2.5 二级指针
int a 10;
int* pa a;
int** ppa pa; a的地址存放在pa中pa的地址存放在ppa中pa是一级指针ppa是二级指针。 32位系统中定义**a[3][4]则变量占用内存空间为 a是一个大小为3*4、存放着二级指针的数组。在32位系统中指针的大小为4Byte。所以该数组占用的内存空间大小为3*4*448Byte。 三、指针数组和数组指针
3.1 指针数组 指针数组是存放指针的数组。 int* arr1[10]; // 存放一级整型指针的一维数组
char* arr2[4]; // 存放一级字符指针的一维数组
char** arr3[5]; // 存放二级字符指针的一维数组
int** arr4[3][4]; // 存放二级整型指针的二维数组
3.2 数组指针 数组指针是指向数组的指针。 int* p1[10]; // 指针数组
int(*p2)[10]; // 数组指针 p2先和*结合说明p2是一个指针变量然后指针指向的是一个大小为10的整型数组。所以p2是一个指针指向一个数组叫数组指针。[]的优先级要高于*的所以必须加上()来保证p2先和*结合。 3.2.1 数组指针解引用
int arr[5] { 0 };
int(*p)[5] arr;
// p是数组指针p解引用*p表示什么 *p表示整个数组拿到数组所有元素但这样没有任何意义编译器会把*p转化为数组首元素的地址。但在sizeof(*p)和(*p)中*p还是整个数组。所以*p相当于数组名。 #include stdio.hint main()
{int arr[5] { 0 };int(*p)[5] arr; // p保存的是整个数组的地址printf(%d\n, sizeof(*p)); // *p是整个数组大小为5×420个字节printf(%d\n, sizeof(*p 0)); // *p是数组首元素的地址大小为4/8个字节32/64位机器printf(%p\n, (*p)); // 010FFAA8 *p是整个数组(*p)是整个数组的地址printf(%p\n, (*p) 1); // 010FFABC (*p)是整个数组的地址(*p)1跳过整个数组20个字节printf(%p\n, *p 1); // 010FFAAC *p是数组首元素的地址*p1跳过4个字节是数组第二个元素的地址return 0;
}
#include stdio.hint main()
{int arr[3][4] { 0 };int(*p)[4] arr; // p保存的是首行的地址printf(%d\n, sizeof(*p)); // *p是首行大小为4×416个字节printf(%d\n, sizeof(*p 0)); // *p是首行首元素的地址大小为4/8个字节32/64位机器printf(%p\n, (*p)); // 009EFB24 *p是首行(*p)是首行的地址printf(%p\n, (*p) 1); // 009EFB34 (*p)是首行的地址(*p)1跳过一行16个字节printf(%p\n, *p 1); // 009EFB28 *p是首行首元素的地址*p1跳过4个字节是首行第二个元素的地址return 0;
}
3.2.2 数组指针的使用
3.2.2.1 遍历一维数组 实参为数组名形参为数组 #include stdio.hvoid print(int arr[10], int sz)
{int i 0;for (i 0; i sz; i){printf(%d , arr[i]);}printf(\n);
}int main()
{int arr[10] { 1,2,3,4,5,6,7,8,9,10 };int sz sizeof(arr) / sizeof(arr[0]);print(arr, sz);return 0;
} 实参为数组名形参为指针 #include stdio.hvoid print(int* p, int sz)
{int i 0;for (i 0; i sz; i){printf(%d , *(p i));// parr数组首元素的地址// pi数组下标为i的元素的地址// *(pi)数组下标为i的元素的值p[i]// printf(%d , p[i]);}printf(\n);
}int main()
{int arr[10] { 1,2,3,4,5,6,7,8,9,10 };int sz sizeof(arr) / sizeof(arr[0]);print(arr, sz);return 0;
} 实参为数组的地址形参为数组指针 #include stdio.hvoid print(int(*p)[10], int sz)
{int i 0;for (i 0; i sz; i){printf(%d , *(*p i));// parr数组的地址// *p数组首元素的地址// *pi数组下标为i的元素的地址// *(*pi)数组下标为i的元素的值(*p)[i]// printf(%d , (*p)[i]);}printf(\n);
}int main()
{int arr[10] { 1,2,3,4,5,6,7,8,9,10 };int sz sizeof(arr) / sizeof(arr[0]);print(arr, sz);return 0;
}
3.2.2.2 遍历二维数组 实参为数组名形参为数组 #include stdio.hvoid print(int arr[3][5], int r, int c)
{ int i 0;for (i 0; i r; i){int j 0;for (j 0; j c; j){printf(%d , arr[i][j]);}printf(\n);}
}int main()
{int arr[3][5] { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };print(arr, 3, 5);return 0;
} 实参为数组名形参为数组指针 二维数组的数组名表示数组首元素第一个一维数组的地址所以可以用数组指针来接收指针指向元素个数为5的整型数组。 #include stdio.hvoid print(int(*p)[5], int r, int c)
{int i 0;for (i 0; i r; i){int j 0;for (j 0; j c; j){printf(%d , *(*(p i) j));// parr首行的地址// pii行的地址// *(pi)i行首元素的地址p[i]// *(pi)ji行j列元素的地址p[i]j// *(*(pi)j)i行j列元素的值*(p[i]j)p[i][j]// printf(%d , p[i][j]);}printf(\n);}
}int main()
{int arr[3][5] { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };print(arr, 3, 5);return 0;
}
四、数组参数和指针参数
4.1 当实参是一维数组的数组名时形参可以是什么
void test1(int arr[10], int n) // ok 形参是一维数组
{}
void test1(int arr[], int n) // ok 形参是一维数组数组大小可以省略
{}
void test1(int* arr, int n) // ok 形参是一级指针
{}void test2(int* arr2[20], int n) // ok 形参是一维指针数组
{}
void test2(int* arr2[], int n) // ok 形参是一维指针数组数组大小可以省略
{}
void test2(int** arr2, int n) // ok 形参是二级指针
{}int main()
{int arr1[10] { 0 }; // 一维数组int n1 sizeof(arr1) / sizeof(arr1[0]);int* arr2[20] { 0 }; // 一维指针数组int n2 sizeof(arr2) / sizeof(arr2[0]);test1(arr1, n1);test2(arr2, n2);return 0;
}
4.2 当实参是二维数组的数组名时形参可以是什么
void test(int arr[3][5], int n) // ok 形参是二维数组
{}
void test(int arr[][5], int n) // ok 形参是二维数组行数可以省略
{}
void test(int arr[3][], int n) // err 形参是二维数组列数不可以省略
{}
void test(int arr[][], int n) // err 形参是二维数组列数不可以省略
{}void test(int(*arr)[5], int n) // ok 形参是数组指针指向二维数组的首元素首行即一个大小为5的一维数组
{}
void test(int* arr, int n) // err 形参不可以是一级指针
{}
void test(int* arr[5], int n) // err 形参不可以是一级指针数组
{}
void test(int** arr, int n) // err 形参不可以是二级指针
{}int main()
{int arr[3][5] { 0 }; // 二维数组int n sizeof(arr) / sizeof(arr[0]);test(arr, n);return 0;
}
4.3 当形参是一级指针时实参可以是什么
void test(int* p) // 形参是一级整型指针
{}
void test(int* p, int n) // 形参是一级整型指针
{}int main()
{int a 0;test(a); // ok 实参是整型变量地址int* p a;test(p); // ok 实参是一级整型指针int arr[10] { 0 };int n sizeof(arr) / sizeof(arr[0]);test(arr, n); // ok 实参是一维整型数组的数组名return 0;
}
4.4 当形参是二级指针时实参可以是什么
void test(int** p) // 形参是二级整型指针
{}
void test(int** p,int n) // 形参是二级整型指针
{}int main()
{int a 0;int* pa a;test(pa); // ok 实参是一级整型指针地址int** ppa pa;test(ppa); // ok 实参是二级整型指针int* arr[10] { 0 };int n sizeof(arr) / sizeof(arr[0]);test(arr, n); // ok 实参是一维整型指针数组的数组名return 0;
}
五、函数、指针和数组
5.1 函数指针 函数名函数名函数的地址。 #include stdio.hint Add(int x, int y)
{return x y;
}int main()
{printf(%p\n, Add); // 00B313D4printf(%p\n, Add); // 00B313D4// AddAdd表示Add函数的地址return 0;
} 函数指针是指向函数的指针。 #include stdio.hint Add(int x, int y)
{return x y;
}int main()
{// 函数指针变量pf保存了Add函数的地址变量类型为int (*)(int, int)int (*pf)(int x, int y) Add;/*int (*pf)(int x, int y) Add; // AddAddint (*pf)(int, int) Add; // 形参可以省略int (*pf)(int, int) Add; // AddAdd形参可以省略*/// 调用Add函数int sum (*pf)(3, 5);/*int sum pf(3, 5); // pf(3, 5) (*pf)(3, 5)int sum Add(3, 5);*/printf(%d\n, sum);return 0;
} 《C陷阱与缺陷》中的两段代码 代码1 (*(void(*)())0)(); void(*)()是一个函数指针类型指向的函数没有参数返回类型为void。 (void(*)())0表示把0强制类型转换为void(*)()类型把0当做一个函数的地址。 (*(void(*)())0)()表示调用0地址处的函数。 代码2 void(*signal(int, void(*)(int)))(int); 这是函数声明声明的函数是signal。 signal(int, void(*)(int))表示signal函数的第一个参数是int类型第二个参数是void(*)(int)类型即一个函数指针类型该函数指针指向的函数参数是int返回类型是void。 void(*signal(int, void(*)(int)))(int)表示signal函数的返回类型是void(*)(int)类型即一个函数指针类型该函数指针指向的函数参数是int返回类型是void。 简化代码2 void(*signal(int, void(*)(int)))(int);
typedef void(*pf_t)(int); // 将void(*)(int)类型重命名为pf_t类型
pf_t signal(int, pf_t);
5.2 函数指针数组
#include stdio.hint Add(int x, int y)
{return x y;
}int Sub(int x, int y)
{return x - y;
}int main()
{int (*pfArr[2])(int, int) { Add,Sub }; // 函数指针数组int ret pfArr[0](2, 3); // Add(2, 3)printf(%d\n, ret); // 5ret pfArr[1](2, 3); // Sub(2, 3)printf(%d\n, ret); // -1return 0;
} 实现两个整数的加减乘除计算器 使用switch语句 #include stdio.hint 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;
}void menu()
{printf(***************************\n);printf(***** 1. add 2. sub *****\n);printf(***** 3. mul 4. div *****\n);printf(***** 0. exit ****\n);printf(***************************\n);
}int main()
{int input 0;int x 0;int y 0;int ret 0;do{menu();printf(请选择:);scanf(%d, input);switch (input){case 1:printf(请输入2个操作数:);scanf(%d %d, x, y);ret Add(x, y);printf(%d\n, ret);break;case 2: printf(请输入2个操作数:);scanf(%d %d, x, y);ret Sub(x, y);printf(%d\n, ret);break;case 3:printf(请输入2个操作数:);scanf(%d %d, x, y);ret Mul(x, y);printf(%d\n, ret);break;case 4:printf(请输入2个操作数:);scanf(%d %d, x, y);ret Div(x, y);printf(%d\n, ret);break;case 0:printf(退出计算器\n);break;default:printf(选择错误\n);break;}} while (input);return 0;
} 使用函数指针数组 #include stdio.hint 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;
}void menu()
{printf(***************************\n);printf(***** 1. add 2. sub *****\n);printf(***** 3. mul 4. div *****\n);printf(***** 0. exit ****\n);printf(***************************\n);
}int main()
{int input 0;int x 0;int y 0;int ret 0;int (*pfArr[])(int, int) { 0,Add,Sub,Mul,Div };do{menu();printf(请选择:);scanf(%d, input);if (input 0){printf(退出计算器\n);break;}if (input 1 input 4){printf(请输入2个操作数:);scanf(%d %d, x, y);ret pfArr[input](x, y);printf(%d\n, ret);}else{printf(选择错误\n);} } while (input);return 0;
}
5.3 指向函数指针数组的指针
int (*pf)(int, int) Add; // 函数指针
int (*pfArr[2])(int, int) { Add,Sub }; // 函数指针数组
int (*(*ppfArr)[2])(int, int) pfArr; // 指向函数指针数组的指针
5.4 以下代码分别表示什么 int *p[10]; // 指针数组数组大小是10数组元素是int*类型的指针 int (*p)[10]; // 数组指针指针指向一个数组大小是10数组元素是int类型的数组 int *p(int); // 函数声明函数名是p参数是int类型返回值是int*类型 int (*p)(int); // 函数指针指针指向一个参数是int类型返回值是int类型的函数 int (*p[10])(int); // 函数指针数组数组大小是10数组元素是int(*)(int)类型的数组 int (*(*p)[10])(int, int); // 指向函数指针数组的指针指针指向一个数组大小是10数组元素是int(*)(int)类型的数组 六、回调函数 回调函数就是一个被作为参数传递的函数。在C语言中回调函数只能使用函数指针实现。 6.1 qsort函数
#include stdlib.h
void qsort(void* base, size_t num, size_t size, int (*compar)(const void*, const void*));
// 执行快速排序
// base 待排数据的起始地址
// num 待排数据的元素个数
// size 待排数据的元素大小单位字节
// compar 函数指针指向比较两个元素的函数// 比较函数需要自己编写规定函数原型为
// int compar(const void* elem1, const void* elem2)
// 函数返回值的规则如下
// 当进行升序排序时
// 如果elem1elem2则返回值0
// 如果elem1elem2则返回值0
// 如果elem1elem2则返回值0
6.1.1 qsort函数排序整型数据
#include stdio.h
#include stdlib.hint cmp_int(const void* e1, const void* e2)
{return (*(int*)e1 - *(int*)e2); // void*类型的变量必须强制类型转换成其他类型才能解引用
}void print(int arr[], int n)
{for (int i 0; i n; i){printf(%d , arr[i]);}printf(\n);
}int main()
{int arr[] { 2,1,3,7,5,9,6,8,0,4 };int n sizeof(arr) / sizeof(arr[0]);qsort(arr, n, sizeof(arr[0]), cmp_int);print(arr, n); // 0 1 2 3 4 5 6 7 8 9return 0;
}
6.1.2 qsort函数排序结构体类型数据
#include stdio.h
#include stdlib.h
#include string.hstruct Stu
{char name[20];int age;
};int cmp_stu_by_name(const void* e1, const void* e2)
{return strcmp(((struct Stu*)e1)-name, ((struct Stu*)e2)-name);
}/*
int cmp_stu_by_age(const void* e1, const void* e2)
{return ((struct Stu*)e1)-age - ((struct Stu*)e2)-age;
}
*/int main()
{struct Stu s[] { {zhangsan,20}, {lisi,55}, {wangwu,40} };int n sizeof(s) / sizeof(s[0]);// 按照名字排序qsort(s, n, sizeof(s[0]), cmp_stu_by_name);// 按照年龄排序// qsort(s, n, sizeof(s[0]), cmp_stu_by_age);printf(%s %d\n, s[0].name, s[0].age);printf(%s %d\n, s[1].name, s[1].age);printf(%s %d\n, s[2].name, s[2].age);return 0;
}
6.2 改写冒泡排序函数 常规冒泡排序函数 #include stdio.hvoid bubble_sort(int arr[], int n)
{// 趟数for (int i 0; i n - 1; i){// 一趟冒泡排序for (int j 0; j n - 1 - i; j){if (arr[j] arr[j 1]){int tmp arr[j];arr[j] arr[j 1];arr[j 1] tmp;}}}
}void print(int arr[], int n)
{for (int i 0; i n; i){printf(%d , arr[i]);}printf(\n);
}int main()
{int arr[] { 2,1,3,7,5,9,6,8,0,4 };int n sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, n);print(arr, n); // 0 1 2 3 4 5 6 7 8 9return 0;
} 借鉴qsort的设计思想改写冒泡排序函数实现对任意类型的数据的排序。 6.2.1 整型数据的冒泡排序函数
#include stdio.hint cmp_int(const void* e1, const void* e2)
{return (*(int*)e1 - *(int*)e2); // void*类型的变量必须强制类型转换成其他类型才能解引用
}void swap(char* buf1, char* buf2, int size)
{for (int i 0; i size; i){char tmp *buf1;*buf1 *buf2;*buf2 tmp;buf1;buf2;}
}void bubble_sort2(void* base, int num, int size, int (*cmp)(const void*, const void*))
{// 趟数for (int i 0; i num - 1; i){// 一趟冒泡排序for (int j 0; j num - 1 - i; j){if (cmp((char*)base j * size, (char*)base (j 1) * size) 0){// 交换swap((char*)base j * size, (char*)base (j 1) * size, size);}}}
}void print(int arr[], int n)
{for (int i 0; i n; i){printf(%d , arr[i]);}printf(\n);
}int main()
{int arr[] { 2,1,3,7,5,9,6,8,0,4 };int n sizeof(arr) / sizeof(arr[0]);bubble_sort2(arr, n, sizeof(arr[0]), cmp_int);print(arr, n); // 0 1 2 3 4 5 6 7 8 9return 0;
}
6.2.2 结构体类型数据的冒泡排序函数
#include stdio.h
#include string.hstruct Stu
{char name[20];int age;
};int cmp_stu_by_name(const void* e1, const void* e2)
{return strcmp(((struct Stu*)e1)-name, ((struct Stu*)e2)-name);
}/*
int cmp_stu_by_age(const void* e1, const void* e2)
{return ((struct Stu*)e1)-age - ((struct Stu*)e2)-age;
}
*/void swap(char* buf1, char* buf2, int size)
{for (int i 0; i size; i){char tmp *buf1;*buf1 *buf2;*buf2 tmp;buf1;buf2;}
}void bubble_sort2(void* base, int num, int size, int (*cmp)(const void*, const void*))
{// 趟数for (int i 0; i num - 1; i){// 一趟冒泡排序for (int j 0; j num - 1 - i; 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()
{struct Stu s[] { {zhangsan, 20}, {lisi, 55}, {wangwu, 40} };int n sizeof(s) / sizeof(s[0]);// 按照名字排序bubble_sort2(s, n, sizeof(s[0]), cmp_stu_by_name);// 按照年龄排序// bubble_sort2(s, n, sizeof(s[0]), cmp_stu_by_age);printf(%s %d\n, s[0].name, s[0].age);printf(%s %d\n, s[1].name, s[1].age);printf(%s %d\n, s[2].name, s[2].age);return 0;
}
七、数组练习题
7.1 一维数组
#include stdio.hint main()
{int a[] { 1,2,3,4 }; // a是数组名printf(%d\n, sizeof(a)); // 16 a是整个数组printf(%d\n, sizeof(a 0)); // 4/8 a是数组首元素的地址a0也是数组首元素的地址printf(%d\n, sizeof(*a)); // 4 a是数组首元素的地址*a是数组首元素printf(%d\n, sizeof(a 1)); // 4/8 a是数组首元素的地址a1跳过4个字节是数组第二个元素的地址printf(%d\n, sizeof(a[1])); // 4 a[1]是数组第二个元素printf(%d\n, sizeof(a)); // 4/8 a是整个数组的地址printf(%d\n, sizeof(*a)); // 16 *a是整个数组printf(%d\n, sizeof(a 1)); // 4/8 a是数组的地址a1跳过整个数组也是地址printf(%d\n, sizeof(a[0])); // 4/8 a[0]是数组首元素的地址printf(%d\n, sizeof(a[0] 1)); // 4/8 a[0]是数组首元素的地址a[0]1跳过4个字节是数组第二个元素的地址return 0;
}
7.2 字符数组 sizeof和strlen的区别 sizeof运算符计算数据类型或变量长度单位字节strlen函数计算字符串长度从字符串开始到\0之间的字符数不包括\0本身 #include stdio.h
#include string.hint main()
{char arr[] { a,b,c,d,e,f };printf(%d\n, sizeof(arr)); // 6 arr是整个数组printf(%d\n, sizeof(arr 0)); // 4/8 arr是数组首元素的地址arr0还是数组首元素的地址printf(%d\n, sizeof(*arr)); // 1 arr是数组首元素的地址*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 arr1跳过整个数组也是地址printf(%d\n, sizeof(arr[0] 1)); // 4/8 arr[0]是数组首元素的地址arr[0]1跳过1个字节是数组第二个元素的地址printf(%d\n, strlen(arr)); // 随机值 arr是数组首元素的地址数组中没有\0后面是否有\0、在什么位置是不确定的printf(%d\n, strlen(arr 0)); // 随机值 arr是数组首元素的地址arr0还是数组首元素的地址同上printf(%d\n, strlen(*arr)); // err *arr是数组首元素aASCII码值是97strlen把97当成地址会非法访问内存printf(%d\n, strlen(arr[1])); // err arr[1]是数组第二个元素bASCII码值是98同上printf(%d\n, strlen(arr)); // 随机值 arr是整个数组的地址数组的地址也是指向数组起始位置同strlen(arr)printf(%d\n, strlen(arr 1)); // 随机值 arr1跳过整个数组后面是否有\0、在什么位置是不确定的printf(%d\n, strlen(arr[0] 1)); /* 随机值 arr[0]是数组首元素的地址arr[0]1跳过1个字节是数组第二个元素的地址数组中没有\0后面是否有\0、在什么位置是不确定的*/return 0;
}
#include stdio.h
#include string.hint main()
{char arr[] abcdef; // 等价于char arr[] { a,b,c,d,e,f,\0 };printf(%d\n, sizeof(arr)); //7 arr是整个数组printf(%d\n, sizeof(arr 0)); //4/8 arr是数组首元素的地址arr0还是数组首元素的地址printf(%d\n, sizeof(*arr)); //1 arr是数组首元素的地址*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 arr1跳过整个数组也是地址printf(%d\n, sizeof(arr[0] 1));//4/8 arr[0]是数组首元素的地址arr[0]1跳过1个字节是数组第二个元素的地址printf(%d\n, strlen(arr)); //6 arr是数组首元素的地址计算从数组首元素到第一个\0的字符数printf(%d\n, strlen(arr 0)); //6 arr是数组首元素的地址arr0还是数组首元素的地址同上printf(%d\n, strlen(*arr)); //err *arr是数组首元素aASCII码值是97strlen把97当成地址会非法访问内存printf(%d\n, strlen(arr[1])); //err arr[1]是数组第二个元素bASCII码值是98同上printf(%d\n, strlen(arr)); //6 arr是整个数组的地址数组的地址也是指向数组起始位置同strlen(arr)printf(%d\n, strlen(arr 1)); //随机值 arr1跳过整个数组后面是否有\0、在什么位置是不确定的printf(%d\n, strlen(arr[0] 1));/*5 arr[0]是数组首元素的地址arr[0]1跳过1个字节是数组第二个元素的地址数组中没有\0后面是否有\0、在什么位置是不确定的*/return 0;
}
#include stdio.h
#include string.hint main()
{const char* p abcdef; // 把字符串常量首字符a的地址放到指针变量p中printf(%d\n, sizeof(p)); //4/8 p是首字符的地址printf(%d\n, sizeof(p 1)); //4/8 p1跳过1个字节是第二个字符的地址printf(%d\n, sizeof(*p)); //1 *p是首字符printf(%d\n, sizeof(p[0])); //1 p[0]*(p0)*p是首字符printf(%d\n, sizeof(p)); //4/8 p是指针变量p的地址printf(%d\n, sizeof(p 1)); //4/8 p1跳过p也是地址printf(%d\n, sizeof(p[0] 1)); //4/8 p[0]是首字符的地址p[0]1跳过1个字节p[0]1是第二个字符的地址printf(%d\n, strlen(p)); //6 p是首字符的地址计算从首字符到第一个\0的字符数printf(%d\n, strlen(p 1)); //5 p1跳过1个字节是第二个字符的地址计算从第二个字符到第一个\0的字符数printf(%d\n, strlen(*p)); //err p是首字符aASCII码值是97strlen把97当成地址会非法访问内存printf(%d\n, strlen(p[0])); //err p[0]是首字符a同上printf(%d\n, strlen(p)); //随机值 p是指针变量p的地址后面是否有\0、在什么位置是不确定的printf(%d\n, strlen(p 1)); //随机值 p1跳过p后面是否有\0、在什么位置是不确定的printf(%d\n, strlen(p[0] 1)); //5 p[0]是首字符的地址p[0]1跳过1个字节是第二个字符的地址同strlen(p1)return 0;
}
7.3 二维数组
#include stdio.hint main()
{int a[3][4] { 0 }; // a是二维数组的数组名a[i]是下标为i的一维数组的数组名printf(%d\n, sizeof(a)); //48 a是整个数组printf(%d\n, sizeof(a[0][0])); //4 a[0][0]是首行首列元素printf(%d\n, sizeof(a[0])); //16 a[0]是整个首行printf(%d\n, sizeof(a[0] 1)); //4/8 a[0]是首行首元素的地址a[0]1跳过4个字节是首行第二个元素的地址printf(%d\n, sizeof(*(a[0] 1))); //4 a[0]1是首行第二个元素的地址*(a[0]1)是首行第二个元素printf(%d\n, sizeof(a 1)); //4/8 a是首行的地址a1跳过一行是第二行的地址printf(%d\n, sizeof(*(a 1))); //16 a1是第二行的地址*(a1)是整个第二行printf(%d\n, sizeof(a[0] 1)); //4/8 a[0]是整个首行的地址a[0]1跳过一行是第二行的地址printf(%d\n, sizeof(*(a[0] 1))); //16 a[0]1是第二行的地址*(a[0]1)是整个第二行printf(%d\n, sizeof(*a)); //16 a是首行的地址*a是整个首行printf(%d\n, sizeof(a[3])); /*16 a[3]理论上是第4行虽然没有第4行但类型能够确定大小就是确定的sizeof只是计算a[3]的大小并不会访问对应内存所以不会报错*/return 0;
}
八、指针练习题 例题1 #include stdio.hint main()
{int a[5] { 1,2,3,4,5 };int* ptr (int*)(a 1);printf(%d,%d, *(a 1), *(ptr - 1)); // 2,5return 0;
} 例2 #include stdio.hstruct Test
{int Num;char* pcName;short sDate;char cha[2];short sBa[4];
}* p; // p是一个结构体指针变量// X86环境下演示
// 假设p的值为0x100000。 如下表表达式的值分别为多少
// 已知结构体Test类型的变量大小是20个字节int main()
{p (struct Test*)0x100000;printf(%p\n, p 0x1); // 0x100014 struct Test*类型1跳过20个字节printf(%p\n, (unsigned long)p 0x1); // 0x100001 整型1直接计算printf(%p\n, (unsigned int*)p 0x1); // 0x100004 unsigned int*类型1跳过4个字节return 0;
} 例31int型的补码用十六进制表示为0x00000001小端模式01 00 00 00低地址---高地址。 #include stdio.h// 假设机器为小端存储模式int main()
{int a[4] { 1,2,3,4 };int* ptr1 (int*)(a 1);int* ptr2 (int*)((int)a 1); // 把数组首元素的地址的数值1再转换为地址printf(%x,%x, ptr1[-1], *ptr2); // ptr1[-1]*(ptr1-1)// 4,2000000return 0;
} 例4 #include stdio.hint main()
{int a[3][2] { (0, 1),(2, 3),(4, 5) };// exp1,exp2,exp3,...,expN逗号表达式从左向右依次执行整个表达式的结果是最后一个表达式的结果// 不等于int a[3][2] {{0,1}, {2,3}, {4,5}};// 等价于int a[3][2] { 1, 3, 5 };// 1 3// 5 0// 0 0int* p;p a[0];printf(%d, p[0]); // 1return 0;
} 例5 #include stdio.hint 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]); // p[4][2]*(*(p4)2)*(p4)2// FFFFFFFC,-4return 0;
} 指针-指针的绝对值是指针之间元素的个数。指向同一块区间的两个指针才能相减。
高地址-低地址正数低地址-高地址负数。
*(p4)2-a[4][2]-4
//-4
//原码10000000000000000000000000000100
//反码11111111111111111111111111111011
//补码11111111111111111111111111111100--十六进制--FFFFFFFC 例6 #include stdio.hint 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,5return 0;
} 例7 #include stdio.hint main()
{char* a[] { work,at,alibaba };char** pa a;pa;printf(%s\n, *pa); // atreturn 0;
} 例8 #include stdio.hint main()
{char* c[] { ENTER,NEW,POINT,FIRST };char** cp[] { c 3,c 2,c 1,c };char*** cpp cp;printf(%s\n, **cpp); // POINTprintf(%s\n, *-- * cpp 3); // ERprintf(%s\n, *cpp[-2] 3); // *cpp[-2]3**(cpp-2)3 STprintf(%s\n, cpp[-1][-1] 1); // cpp[-1][-1]1*(*(cpp-1)-1)1 EWreturn 0;
}
**cpp *--*cpp3 **(cpp-2)3 *(*(cpp-1)-1)1