编程语言|C语言——数组与指针

一、数组

同一类型的变量——元素(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.h>
 
int 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.h>
 
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;
}

一维数组在内存中的存储

一维数组在内存中是连续存储的。

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 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 <stdio.h>
 
int 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.h>
 
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

二维数组在内存中的存储

二维数组在内存中也是连续存储的。

二维数组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

1.3 数组名

数组名表示数组首元素的地址,是一个常量指针,不可以改变指针本身的值,没有自增、自减等操作。

数组名和指向数组首元素的指针都可以通过改变偏移量来访问数组中的元素,但数组名是常量指针,指向数组首元素的指针是一般指针。

以下2种情况下数组名表示整个数组:

sizeof(数组名),计算整个数组的大小,单位是字节。
&数组名,取出的是数组的地址。

#include <stdio.h>
 
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 test(int arr[10])
{}
void test(int arr[])
{}
void test(int* arr)
{}
 
int main()
{
	int arr[10] = { 0 };
	test(arr);
	return 0;
}
// ok
 
void 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.h>
 
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个字节。

2.2 指针类型

int*类型的指针存放int类型变量的地址,char*类型的指针存放char类型变量的地址……

2.2.1 指针+-整数

指针的类型决定了指针的步长(+-1操作的时候,跳过几个字节)。

int*类型的指针+-1跳过4个字节,char*类型的指针+-1跳过1个字节……

#include <stdio.h>
 
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;
}

2.2.2 指针的解引用

指针的类型决定了对指针解引用的时候有多大的权限(能访问几个字节)。

int*类型的指针解引用能访问4个字节,char*类型的指针解引用能访问1个字节……

利用int*类型的指针强制转换成char*类型后只能访问1个字节,来判断当前计算机是大端模式还是小端模式:

#include <stdio.h>
 
int check_sys()
{
	int a = 1;
	return *(char*)&a;
}
 
int main()
{
	int ret = check_sys();
	if (ret == 1)
		printf("小端\n");
	else
		printf("大端\n");
	return 0;
}

1(int型)的补码用十六进制表示为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.h>
 
int 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.h>
 
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;
}

2.4.3 指针的关系运算

可以用关系运算符进行指针比较。只有在两个指针指向同一数组时,用关系运算符进行的指针比较才有意义。比较的结果依赖于数组中两个元素的相对位置。

#include <stdio.h>
 
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;
}

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*4=48Byte。

三、指针数组和数组指针

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.h>
 
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 <stdio.h>
 
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;
}

3.2.2 数组指针的使用

3.2.2.1 遍历一维数组

实参为数组名,形参为数组:

#include <stdio.h>
 
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 <stdio.h>
 
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 <stdio.h>
 
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;
}
3.2.2.2 遍历二维数组

实参为数组名,形参为数组:

#include <stdio.h>
 
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 <stdio.h>
 
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;
}

四、数组参数和指针参数

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.h>
 
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 <stdio.h>
 
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);

5.2 函数指针数组

#include <stdio.h>
 
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 <stdio.h>
 
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 <stdio.h>
 
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;
}

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)
// 函数返回值的规则如下:
// 当进行升序排序时,
// 如果elem1<elem2,则返回值<0
// 如果elem1=elem2,则返回值=0
// 如果elem1>elem2,则返回值>0

6.1.1 qsort函数排序整型数据

#include <stdio.h>
#include <stdlib.h>
 
int 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 9
	return 0;
}

6.1.2 qsort函数排序结构体类型数据

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
struct 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.h>
 
void 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 9
	return 0;
}

借鉴qsort的设计思想,改写冒泡排序函数,实现对任意类型的数据的排序。

6.2.1 整型数据的冒泡排序函数

#include <stdio.h>
 
int 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 9
	return 0;
}

6.2.2 结构体类型数据的冒泡排序函数

#include <stdio.h>
#include <string.h>
 
struct 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.h>
 
int main()
{
    int a[] = { 1,2,3,4 }; // a是数组名
	printf("%d\n", sizeof(a));         // 16  a是整个数组
	printf("%d\n", sizeof(a + 0));     // 4/8 a是数组首元素的地址,a+0也是数组首元素的地址
	printf("%d\n", sizeof(*a));        // 4   a是数组首元素的地址,*a是数组首元素
	printf("%d\n", sizeof(a + 1));     // 4/8 a是数组首元素的地址,a+1跳过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是数组的地址,&a+1跳过整个数组,也是地址
	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.h>
 
int 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是数组首元素的地址,arr+0还是数组首元素的地址
	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 &arr+1跳过整个数组,也是地址
	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是数组首元素的地址,arr+0还是数组首元素的地址,同上
	printf("%d\n", strlen(*arr));        // err   *arr是数组首元素'a',ASCII码值是97,strlen把97当成地址,会非法访问内存
	printf("%d\n", strlen(arr[1]));      // err   arr[1]是数组第二个元素'b',ASCII码值是98,同上
	printf("%d\n", strlen(&arr));        // 随机值 &arr是整个数组的地址,数组的地址也是指向数组起始位置,同strlen(arr)
	printf("%d\n", strlen(&arr + 1));    // 随机值 &arr+1跳过整个数组,后面是否有\0、在什么位置是不确定的
	printf("%d\n", strlen(&arr[0] + 1)); /* 随机值 &arr[0]是数组首元素的地址,&arr[0]+1跳过1个字节,是数组第二个元素的地址,
	                                              数组中没有\0,后面是否有\0、在什么位置是不确定的*/
 
	return 0;
}
#include <stdio.h>
#include <string.h>
 
int 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是数组首元素的地址,arr+0还是数组首元素的地址
	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 &arr+1跳过整个数组,也是地址
	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是数组首元素的地址,arr+0还是数组首元素的地址,同上
	printf("%d\n", strlen(*arr));       //err    *arr是数组首元素'a',ASCII码值是97,strlen把97当成地址,会非法访问内存
	printf("%d\n", strlen(arr[1]));     //err    arr[1]是数组第二个元素'b',ASCII码值是98,同上
	printf("%d\n", strlen(&arr));       //6      &arr是整个数组的地址,数组的地址也是指向数组起始位置,同strlen(arr)
	printf("%d\n", strlen(&arr + 1));   //随机值 &arr+1跳过整个数组,后面是否有\0、在什么位置是不确定的
	printf("%d\n", strlen(&arr[0] + 1));/*5      &arr[0]是数组首元素的地址,&arr[0]+1跳过1个字节,是数组第二个元素的地址,
	                                             数组中没有\0,后面是否有\0、在什么位置是不确定的*/
 
	return 0;
}
#include <stdio.h>
#include <string.h>
 
int main()
{
	const char* p = "abcdef"; // 把字符串常量首字符a的地址放到指针变量p中
 
	printf("%d\n", sizeof(p));         //4/8 p是首字符的地址
	printf("%d\n", sizeof(p + 1));     //4/8 p+1跳过1个字节,是第二个字符的地址
	printf("%d\n", sizeof(*p));        //1   *p是首字符
	printf("%d\n", sizeof(p[0]));      //1   p[0]=*(p+0)=*p,是首字符
	printf("%d\n", sizeof(&p));        //4/8 &p是指针变量p的地址
	printf("%d\n", sizeof(&p + 1));    //4/8 &p+1跳过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      p+1跳过1个字节,是第二个字符的地址,计算从第二个字符到第一个\0的字符数
	printf("%d\n", strlen(*p));        //err    p是首字符'a',ASCII码值是97,strlen把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));    //随机值 &p+1跳过p,后面是否有\0、在什么位置是不确定的
	printf("%d\n", strlen(&p[0] + 1)); //5      &p[0]是首字符的地址,&p[0]+1跳过1个字节,是第二个字符的地址,同strlen(p+1)
 
	return 0;
}

7.3 二维数组

#include <stdio.h>
 
int 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是首行的地址,a+1跳过一行,是第二行的地址
	printf("%d\n", sizeof(*(a + 1)));     //16  a+1是第二行的地址,*(a+1)是整个第二行
	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.h>
 
int main()
{
	int a[5] = { 1,2,3,4,5 };
	int* ptr = (int*)(&a + 1);
	printf("%d,%d", *(a + 1), *(ptr - 1)); // 2,5
	return 0;
}

  • 例2
#include <stdio.h>
 
struct 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;
}
  • 例3
  • 1(int型)的补码用十六进制表示为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,2000000
	return 0;
}

  • 例4
#include <stdio.h>
 
int 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 0
	int* p;
	p = a[0];
	printf("%d", p[0]); // 1
	return 0;
}
  • 例5
#include <stdio.h>
 
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]); // &p[4][2]=&*(*(p+4)+2)=*(p+4)+2
    // FFFFFFFC,-4
	return 0;
}

指针-指针的绝对值是指针之间元素的个数。指向同一块区间的两个指针才能相减。

高地址-低地址=正数,低地址-高地址=负数。

*(p+4)+2-&a[4][2]=-4

//-4
//原码:10000000000000000000000000000100
//反码:11111111111111111111111111111011
//补码:11111111111111111111111111111100--十六进制-->FFFFFFFC
  • 例6
#include <stdio.h>
 
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)); // 10,5
	return 0;
}

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

  • 例8
#include <stdio.h>
 
int main()
{
	char* c[] = { "ENTER","NEW","POINT","FIRST" };
	char** cp[] = { c + 3,c + 2,c + 1,c };
	char*** cpp = cp;
	printf("%s\n", **++cpp); // POINT
	printf("%s\n", *-- * ++cpp + 3); // ER
	printf("%s\n", *cpp[-2] + 3); // *cpp[-2]+3=**(cpp-2)+3 ST
	printf("%s\n", cpp[-1][-1] + 1); // cpp[-1][-1]+1=*(*(cpp-1)-1)+1 EW
	return 0;
}

**++cpp:

*--*++cpp+3:

**(cpp-2)+3:

*(*(cpp-1)-1)+1:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/502479.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JavaScript的学习笔记

<script src"index.js" defer></script>&#xff0c;defer的作用是延迟加载index.js文件 定义变量 变量的类型分为两大类&#xff1a;基本类型和复合类型 JavaScript是一种弱类型语言&#xff0c;所以没有强类型语言所具有的int,float,char等等&#x…

无药可医还能怎么办?越没本事的人,越喜欢从别人身上找原因!——早读(逆天打工人爬取热门微信文章解读)

无药可医的病该怎么办呢&#xff1f; 引言Python 代码第一篇 洞见 《骆驼祥子》&#xff1a;越没本事的人&#xff0c;越喜欢从别人身上找原因第二篇 人民日报 来啦 新闻早班车要闻社会政策 结尾 “吾日三省吾身&#xff0c;而后深知自助者天助之。” 在人生的迷宫中 遭遇困境时…

域环境共享文件夹,容量配额管理

首先&#xff0c;我们先创建一个新的磁盘&#xff0c;必须在服务器关机的状态下创建&#xff0c;只有在关机状态下才能创建NVMe类型的磁盘。 打开此电脑&#xff0c;右击创建的磁盘&#xff0c;点击属性。 点击共享&#xff0c;点击高级共享。 将共享此文件夹勾选上&#xff0c…

蓝桥杯 第2945题 课程抢购 C++ Java Python

目录 题目 思路和解题方法 c 代码 Java 版本&#xff08;仅供参考&#xff09; Python 版本&#xff08;仅供参考&#xff09; 代码细节&#xff1a; C 代码细节解释: Python 代码细节解释: lenyan算法笔记 语雀 《lenyan算法笔记》 个人笔记日常更新。含金量不高。/…

ZNC3罗德与施瓦茨ZNC3网络分析仪

181/2461/8938产品概述&#xff1a; 罗德与施瓦茨 ZNC3 网络分析仪的工作频率范围为 9 kHz 至 3 GHz&#xff0c;面向移动无线电和电子产品行业的应用。它具有双向测试装置&#xff0c;用于测量有源和无源 DUT 的所有四个 S 参数。此外&#xff0c;它还提供适合开发和生产中各…

2023年第十四届蓝桥杯大赛软件类省赛C/C++研究生组真题(代码完整题解)

C题-翻转⭐ 标签:贪心 简述:如果 S 中存在子串 101 或者 010,就可以将其分别变为 111 和 000,操作可以无限重复。最少翻转多少次可以把 S 变成和 T 一样。 链接: 翻转 思路:要求步骤最少->S每个位置最多修改一次->从头开始遍历不匹配就翻转->翻转不了就-1 …

esp32中vscode的开发环境

vscode中安装esp32开发环境请参考&#xff1a;CSDN 1、调出esp32的控制面板View ->Command Paletter&#xff0c;或者快捷键&#xff08;ctrshitp&#xff09; 调出esp-idf的样例工程 选择ESP-IDF所在的安装路径 选择一个样例工程&#xff0c;作为工程模板 创建的新工程如…

基于springboot实现课程作业管理系统项目【项目源码+论文说明】

基于springboot实现课程作业管理系统演示 摘要 随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势&#xff0c;课程作业管理系统当然也不能排除在外。课程作业管理系统是以实际运用为开发背景…

swift中的autoreleasepool(自动释放池)有用么?

想到一个问题 swift中的autoreleasepool(自动释放池)有用么? 我们进行验证一下 首先我们写一个加载图片的方法,保证会真正用到真实的IMP内存func loadBigData(string: String?) {if let path Bundle.main.path(forResource: "big", ofType: "png") {for…

[数据库]windows环境安装mysql数据库服务

mysql介绍 MySQL是一个流行的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;它是由瑞典的MySQL AB公司开发&#xff0c;后来被Sun Microsystems收购&#xff0c;再后来Sun被Oracle收购。MySQL以其稳定性、可靠性、高性能和开放源代码的特性而闻名&#xff0c…

完整部署一套k8s-v.1.28.0版本的集群

一、系统情况 虚拟机版本&#xff1a;esxi 6.7 系统版本&#xff1a;centos7.9_2009_x86 配置&#xff1a;4核8G&#xff08;官网最低要求2核2G&#xff09; 192.168.0.137 master节点 192.168.0.139 node2节点 192.168.0.138 node1节点&#xff08;节点扩容练习&#xf…

剑指Offer题目笔记21(计数排序)

面试题74&#xff1a; 问题&#xff1a; ​ 输入一个区间的集合&#xff0c;将重叠的区间合并。 解决方案&#xff1a; ​ 先将所有区间按照起始位置排序&#xff0c;然后比较相邻两个区间的结束位置就能知道它们是否重叠。如果它们重叠就将它们合并&#xff0c;然后判断合并…

C++ namespace 使用梳理

前言 发现 namespace 这个小细节在工作项目中处理的有一点点小混乱&#xff0c;于是稍微梳理了一下关于 C namespace 使用相关的使用内容&#xff0c;为日后项目的重构做准备&#xff0c;虽然是很小的点&#xff0c;但是也是值得注意的&#xff0c;毕竟代码的质量体现在每一个…

Java项目实战笔记--基于SpringBoot3.0开发仿12306高并发售票系统--(二)项目实现-第五篇-核心功能车票预定开发及nacos集成

本文参考自 Springboot3微服务实战12306高性能售票系统 - 慕课网 (imooc.com) 本文是仿12306项目实战第&#xff08;二&#xff09;章——项目实现 的第五篇&#xff0c;本篇讲解该项目的核心功能——余票查询、车票预定功能的基础版开发&#xff0c;以及讲解项目与Nacos的集成…

linux 下固定摄像头的设备名字

为什么写着一篇文章 在做基于ARM-Linux的垃圾分类垃圾桶的时候&#xff0c;在不小心松动usb摄像头的或者是重新连接的时候&#xff0c;摄像头的编号会改变。有时候etc/udev/video2 &#xff0c;有时etc/udev/video3这样使得每次运行的时候都需要修改编号。 什么是udev规则 u…

Pillow教程03:图像处理的基本步骤+分离split+合并merge+混合blend+composite遮罩

--------------Pillow教程集合--------------- Python项目18&#xff1a;使用Pillow模块&#xff0c;随机生成4位数的图片验证码 Python教程93&#xff1a;初识Pillow模块&#xff08;创建Image对象查看属性图片的保存与缩放&#xff09; Pillow教程02&#xff1a;图片的裁剪…

mysql进阶知识总结

1.存储引擎 1.1MySQL体系结构 1).连接层 最上层是一些客户端和链接服务&#xff0c;包含本地sock通信和大多数基于客户端/服务端工具实现的类似于TCP/IP的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念&#xff0c;为通过认证…

关于v114之后的chromedriver及存放路径

使用selenium调用浏览器时&#xff0c;我一直调用谷歌浏览器&#xff0c;可浏览器升级后&#xff0c;就会再次遇到以前遇到过的各种问题&#xff0c;诸如&#xff1a;1、怎么关闭浏览器更新&#xff1b;2、去哪儿下载chromedriver&#xff1b;3、114版本之后的驱动去哪儿下载&a…

面试题:JVM的垃圾回收

一、GC概念 为了让程序员更专注于代码的实现&#xff0c;而不用过多的考虑内存释放的问题&#xff0c;所以&#xff0c;在Java语言中&#xff0c;有了自动的垃圾回收机制&#xff0c;也就是我们熟悉的GC(Garbage Collection)。 有了垃圾回收机制后&#xff0c;程序员只需要关…

力扣2. 两数相加

Problem: 2. 两数相加 文章目录 题目描述思路复杂度Code 题目描述 思路 1.创建虚拟头节点dummy&#xff0c;用于存储相加的结果数字&#xff1b; 2.让指针p1、p2、tail分别指向l1、l2、dummy&#xff0c;定义int变量carry记录每次相加的进位值&#xff1b; 3.当p1不为空或者p2不…