目录
1. 数组
1.1 一维数组
1.1.1 一维数组的声明
1.1.2 一维数组的初始化
1.1.3 一维数组的使用
1.1.4 一维数组在内存中的存储
1.2 二维数组
1.2.1 二维数组的声明
1.2.2 二维数组的初始化
1.2.3 二维数组的使用
1.2.4 二维数组在内存中的存储
1.3 数组名(面试题)
2. 指针
2.1 指针和指针变量
2.2 指针类型
2.2.1 指针+-整数
2.2.2 指针的解引用
2.3 野指针(面试题)
2.3.1 野指针的成因
2.3.2 规避野指针
2.4 指针运算
2.4.1 递增指针
2.4.2 指针-指针
2.4.3 指针的关系运算
2.5 二级指针
3. 指针数组和数组指针
3.1 指针数组
3.2 数组指针
3.2.1 数组指针解引用
3.2.2 数组指针的使用
3.2.2.1 遍历一维数组
3.2.2.2 遍历二维数组
4. 数组参数和指针参数
4.1 当实参是一维数组的数组名时,形参可以是什么?
4.2 当实参是二维数组的数组名时,形参可以是什么?
4.3 当形参是一级指针时,实参可以是什么?
4.4 当形参是二级指针时,实参可以是什么?
5. 函数、指针和数组(面试题)
5.1 函数指针
5.2 函数指针数组
5.3 指向函数指针数组的指针
5.4 以下代码分别表示什么
6. 回调函数
6.1 整型数据的冒泡排序函数
6.2 qsort函数
6.2.1 qsort函数排序整型数据
6.2.2 qsort函数排序结构体数据
6.3 改写冒泡排序函数
6.3.1 整型数据的冒泡排序函数
6.3.2 结构体数据的冒泡排序函数
7. 数组练习题
7.1 一维数组
7.2 字符数组
7.3 二维数组
8. 指针练习题
同一类型的变量——元素(element)集中在一起,在内存上排列成一条直线,这就是数组(array)。
int arr1[10];
int arr2[2 + 8];
int count = 10;
int arr3[count];
// 在C99标准之前,[]中间必须是常量或常量表达式
// C99标准支持了变长数组的概念,数组大小可以是变量
// 变长数组不能初始化
char arr4[10];
float arr5[1];
double arr6[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[10] = "abc"; // a b c /0 /0 /0 /0 /0 /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
int main()
{
int days[12] = { 31,28,[4] = 31,30,31,[1] = 29 };
for (int i = 0; i < 12; i++)
{
printf("%2d %2d\n", i + 1, days[i]);
}
// 1 31
// 2 29
// 3 0
// 4 0
// 5 31
// 6 30
// 7 31
// 8 0
// 9 0
//10 0
//11 0
//12 0
return 0;
}
#include
int 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;
}
一维数组在内存中是连续存储的。
arr[0] arr[1] arr[2] arr[3] arr[4] arr[5] …………
以一维数组作为元素的数组是二维数组,以二维数组为元素的数组是三维数组……
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 6
int arr2[3][4] = { {1,2},{3,4},{5,6} };
// 1 2 0 0
// 3 4 0 0
// 5 6 0 0
int arr3[][2] = { 1,2,3,4 }; // 二维数组如果有初始化,行数可以省略,列数不能省略
// 1 2
// 3 4
#include
int 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
二维数组在内存中也是连续存储的。
arr[0][0] arr[0][1] arr[0][2] … arr[1][0] arr[1][1] arr[1][2] … arr[2][0] arr[2][1] arr[2][2] …………
题目:二维数组X按行顺序存储,其中每个元素占1个存储单元。若X[4][4]的存储地址为0xf8b82140,X[9][9]的存储地址为0xf8b8221c,则X[7][7]的存储地址为?
假设二维数组X有m行n列,第一个元素即X[0][0]的存储地址为start,则:
X[4][4]的存储地址=0xf8b82140=start+4*n*1+4*1 ①
X[9][9]的存储地址=0xf8b8221c=start+9*n*1+9*1 ②
②-①:5n+5=0xdc -> 5n=0xd7=215 -> n=43
X[7][7]的存储地址=start+7*n*1+7*1=(start+4*n*1+4*1)+3*n+3=0xf8b82140+132=0xf8b82140+0x84=0xf8b821c4
数组名表示数组首元素的地址,是一个常量指针,不可以改变指针本身的值,没有自增、自减等操作。
数组名和指向数组首元素的指针都可以通过改变偏移量来访问数组中的元素,但数组名是常量指针,指向数组首元素的指针是一般指针。
以下2种情况下数组名表示整个数组:
#include
int 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运算符获取数组的长度,不能判断数组的长度时,可能会产生数组越界访问。因此传递数组名时,需要一起传递数组的长度。
// err
void test1(int arr[10])
{}
void test1(int arr[])
{}
void test1(int* arr)
{}
int main()
{
int arr[10] = { 0 };
test1(arr);
return 0;
}
// ok
void test1(int arr[10], int n)
{}
void test1(int arr[], int n)
{}
void test1(int* arr, int n)
{}
int main()
{
int arr[10] = { 0 };
int n = sizeof(arr) / sizeof(arr[0]);
test1(arr, n);
return 0;
}
二维数组是一维数组的数组,二维数组的数组名也表示数组首元素(第一个一维数组)的地址。
指针是内存地址。指针变量是用来存放内存地址的变量,但我们叙述时常把指针变量简称为指针。
#include
int 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个字节。
int*类型的指针存放int类型变量的地址,char*类型的指针存放char类型变量的地址……
指针的类型决定了指针的步长(+-1操作的时候,跳过几个字节)。
int*指针+-1跳过4个字节,char*指针+-1跳过1个字节……
#include
int main()
{
int n = 10;
char* pc = (char*)&n;
int* pi = &n;
printf("%p\n", &n); // 000000DADACFF4E4
printf("%p\n", pc); // 000000DADACFF4E4
printf("%p\n", pc + 1); // 000000DADACFF4E5
printf("%p\n", pi); // 000000DADACFF4E4
printf("%p\n", pi + 1); // 000000DADACFF4E8
return 0;
}
指针的类型决定了对指针解引用的时候有多大的权限(能操作几个字节)。
int*指针的解引用能访问4个字节,char*指针的解引用能访问1个字节……
野指针是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。
#include
int main()
{
int* p = NULL;
// ...
int a = 10;
p = &a;
if (p != NULL)
{
*p = 20;
}
return 0;
}
p++:
++p:
(*p)++:
*p++或*(p++):
解引用(*)和后置自增(++)优先级相同,结合性都是从右往左,所以*p++等价于*(p++)
++*p或++(*p):
*++p或*(++p):
指针-指针的绝对值是指针之间元素的个数。指向同一块区间的两个指针才能相减。
高地址-低地址=正数,低地址-高地址=负数。
#include
int 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); // 3
printf("%d\n", p1 - p2); // -3
return 0;
}
可以用关系运算符进行指针比较。只有在两个指针指向同一数组时,用关系运算符进行的指针比较才有意义。比较的结果依赖于数组中两个元素的相对位置。
#include
int 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 < p2
return 0;
}
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*4=48Byte。
指针数组是存放指针的数组。
int* arr1[10]; // 存放整型指针的一维数组
char* arr2[4]; // 存放一级字符指针的一维数组
char** arr3[5]; // 存放二级字符指针的一维数组
int** arr4[3][4]; // 存放二级整型指针的二维数组
数组指针是指向数组的指针。
int* p1[10]; // 指针数组
int(*p2)[10]; // 数组指针
p2先和*结合,说明p2是一个指针变量,然后指针指向的是一个大小为10的整型数组。所以p2是一个指针,指向一个数组,叫数组指针。[]的优先级要高于*的,所以必须加上()来保证p2先和*结合。
int arr[5] = { 0 };
int(*p)[5] = &arr;
// p是数组指针,p解引用(*p)表示什么?
*p表示整个数组,拿到数组所有元素,但这样没有任何意义,编译器会把*p转化为数组首元素的地址。但在sizeof(*p)和&(*p)中*p还是整个数组。所以*p相当于数组名。
#include
int main()
{
int arr[5] = { 0 };
int(*p)[5] = &arr; // p保存的是整个数组的地址
printf("%d\n", sizeof(*p)); // *p是整个数组,大小为5×4=20个字节
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是数组首元素的地址,*p+1跳过4个字节,是数组第二个元素的地址
return 0;
}
#include
int main()
{
int arr[3][4] = { 0 };
int(*p)[4] = arr; // p保存的是首行的地址
printf("%d\n", sizeof(*p)); // *p是首行,大小为4×4=16个字节
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是首行首元素的地址,*p+1跳过4个字节,是首行第二个元素的地址
return 0;
}
实参为数组名,形参为数组:
#include
void 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
void print(int* p, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
// p=arr=数组首元素的地址
// p+i=数组下标为i的元素的地址
// *(p+i)=数组下标为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
void print(int(*p)[10], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", *(*p + i));
// p=&arr=数组的地址
// *p=数组首元素的地址
// *p+i=数组下标为i的元素的地址
// *(*p+i)=数组下标为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
void 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
void 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));
// p=arr=首行的地址
// p+i=i行的地址
// *(p+i)=i行首元素的地址=p[i]
// *(p+i)+j=i行j列元素的地址=p[i]+j
// *(*(p+i)+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;
}
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;
}
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;
}
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;
}
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;
}
&函数名=函数名=函数的地址。
#include
int Add(int x, int y)
{
return x + y;
}
int main()
{
printf("%p\n", &Add); // 00B313D4
printf("%p\n", Add); // 00B313D4
// &Add=Add,表示Add函数的地址
return 0;
}
函数指针是指向函数的指针。
#include
int 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; // Add=&Add
int(*pf)(int, int) = &Add; // 形参可以省略
int(*pf)(int, int) = Add; // Add=&Add,形参可以省略
*/
// 调用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);
#include
int 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); // 5
ret = pfArr[1](2, 3); // Sub(2,3)
printf("%d\n", ret); // -1
return 0;
}
实现两个整数的加减乘除计算器:
使用switch语句:
#include
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;
}
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
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;
}
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;
}
int(*pf)(int, int) = &Add; // 函数指针
int(*pfArr[2])(int, int) = { Add,Sub }; // 函数指针数组
int(*(*ppfArr)[2])(int, int) = &pfArr; // 指向函数指针数组的指针
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)类型的数组
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
#include
void bubble_sort(int arr[], int sz)
{
// 趟数
for (int i = 0; i < sz - 1; i++)
{
// 一趟冒泡排序
for (int j = 0; j < sz - 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 sz)
{
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main()
{
int arr[] = { 2,1,3,7,5,9,6,8,0,4 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz);
print(arr, sz);
return 0;
}
头文件 | stdlib.h |
原型 | void qsort(void* base, size_t num, size_t width, int(__cdecl* compare)(const void* elem1, const void* elem2)) |
说明 | 执行快速排序 |
返回值 | 无 |
qsort函数C语言编译器函数库自带的排序函数。函数参数如下,
base:待排数据的起始地址
num:待排数据的元素个数
width:待排数据的元素大小(单位:字节)
compare:函数指针,指向比较两个元素的函数
比较函数需要自己编写,规定函数原型为int compare(const void* elem1, const void* elem2),函数返回值的规则如下:
当进行升序排序时,
如果elem1 如果elem1=elem2,则返回值=0; 如果elem1>elem2,则返回值>0。 借鉴qsort的设计思想,改写冒泡排序函数,实现对任意类型的数据的排序。 sizeof和strlen的区别: 获取str指向的字符串的长度 1(int型)的补码用十六进制表示为0x00000001,小端模式:01 00 00 00(低地址<--->高地址)。 指针-指针的绝对值是指针之间元素的个数。指向同一块区间的两个指针才能相减。 高地址-低地址=正数,低地址-高地址=负数。 *(p+4)+2-&a[4][2]=-4 **++cpp: *--*++cpp+3: **(cpp-2)+3: *(*(cpp-1)-1)+1:6.2.1 qsort函数排序整型数据
#include
6.2.2 qsort函数排序结构体数据
#include
6.3 改写冒泡排序函数
6.3.1 整型数据的冒泡排序函数
#include
6.3.2 结构体数据的冒泡排序函数
#include
7. 数组练习题
7.1 一维数组
#include
7.2 字符数组
头文件
string.h
原型
size_t strlen(const char* string);
说明
返回值
返回str指向的字符串的长度
#include
#include
#include
7.3 二维数组
#include
8. 指针练习题
8.1
#include
8.2
#include
8.3
#include
8.4
#include
8.5
#include
//-4
//原码:10000000000000000000000000000100
//反码:11111111111111111111111111111011
//补码:11111111111111111111111111111100--十六进制-->FFFFFFFC
8.6
#include
8.7
#include
8.8
#include