编程语言|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 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.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运算符获取数组的长度,不能判断数组的长度时,可能会产生数组越界访问。因此传递数组名时,需要一起传递数组的长度。

// 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.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);     // 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.h>int check_sys()
{int a = 1;return *(char*)&a;
}int main()
{int ret = check_sys();if (ret == 1)printf("小端\n");elseprintf("大端\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); //  3printf("%d\n", p1 - p2); // -3return 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 < 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*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); // 00B313D4printf("%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=&Addint (*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);      // 5ret = pfArr[1](2, 3); // Sub(2, 3)printf("%d\n", ret);  // -1return 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 9return 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 9return 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 9return 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,5return 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,2000000return 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 0int* p;p = a[0];printf("%d", p[0]); // 1return 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,-4return 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,5return 0;
}

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

*--*++cpp+3:

**(cpp-2)+3:

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深度学习 - PyTorch基本流程 (代码)

直接上代码 import torch import matplotlib.pyplot as plt from torch import nn# 创建data print("**** Create Data ****") weight 0.3 bias 0.9 X torch.arange(0,1,0.01).unsqueeze(dim 1) y weight * X bias print(f"Number of X samples: {len(…

蓝桥杯2016年第十三届省赛真题-生日蜡烛

一、题目 生日蜡烛. 某君从某年开始每年都举办一次生日party&#xff0c;并且每次都要吹熄与年龄相同根数的蜡烛。 现在算起来&#xff0c;他一共吹熄了236根蜡烛。 请问&#xff0c;他从多少岁开始过生日party的&#xff1f; 请填写他开始过生日party的年龄数。 注意&#xff…

python笔记(6)String(字符串)

目录 访问字符串中的值 Python字符串运算符 Python 字符串格式化 str.format() 数字格式化 多行注释 f-string Unicode 字符串 Python 的字符串内建函数 我们可以用单引号或者双引号"来创建字符串。 创建字符串很简单&#xff0c;给变量分配一个值即可例如 ahell…

基于资源的约束委派(下)

webclient http self relay Web 分布式创作和版本控制 (WebDAV) 是超文本传输协议 (HTTP) 的扩展&#xff0c;它定义了如何使用 HTTP ( docs.microsoft.com )执行复 制、移动、删除和创建等基本文件功能 需要启用 WebClient 服务才能使基于 WebDAV 的程序和功能正常工作。事实…

全国中学基础信息 API 数据接口

全国中学基础信息 API 数据接口 基础数据&#xff0c;高校高考&#xff0c;提供全国初级高级中学基础数据&#xff0c;定时更新&#xff0c;多维度筛选。 1. 产品功能 2024 年数据已更新&#xff1b;提供最新全国中学学校基本信息&#xff1b;包含全国初级中学与高等中学&…

Rust机器学习框架Candle

一、概述 Candle 是由知名开源组织 Hugging Face 开发的一个极简的机器学习框架。它专为 Rust 语言打造&#xff0c;致力于提供高性能和易用性的完美结合。Candle 的诞生为 Rust 生态在机器学习领域带来了新的选择&#xff0c;让 Rust 开发者能够更轻松地构建和部署机器学习应…

家庭琐事对工作效率的影响及应对策略

在快节奏的现代生活中&#xff0c;工作与家庭生活之间的界限日益模糊&#xff0c;人们往往难以将两者完全割裂开来。有时候&#xff0c;我们正在全身心投入工作时&#xff0c;却可能被突如其来的家庭琐事打扰&#xff0c;这不仅影响了心情&#xff0c;更会波及到工作效率和质量…

silk-v3-decoder将sil转为mp3

一、新建临时目录 新建临时目录&#xff0c;可自定义&#xff0c;本次新建目录为 /opt/packages mkdir /opt/packages二、下载、安装lame # cd /opt/packages# wget http://downloads.sourceforge.net/lame/lame-3.100.tar.gz# tar -zxvf lame-3.100.tar.gz# cd lame-3.100#…

git之目前的主流版本

官方文档 简介 我们都知道&#xff0c;在开发过程中&#xff0c;版本控制是至关重要的。Git作为目前最为流行的版本控制系统&#xff0c;已经成为了开发者们的标配。出于好奇&#xff0c;本人对git目前主流几大版本&#xff08;GitLab、GitHub、Gitee 和 GitCode&#xff09;…

虚拟现实(VR)项目的开发工具

虚拟现实&#xff08;VR&#xff09;项目的开发涉及到多种工具&#xff0c;这些工具可以帮助开发者从建模、编程到最终内容的发布。以下是一些被广泛认可的VR开发工具&#xff0c;它们覆盖了从3D建模到交互设计等多个方面。北京木奇移动技术有限公司&#xff0c;专业的软件外包…

取消svn关联脚本

写在前面&#xff0c;该脚本由朋友提供&#xff0c;来源与网络&#xff0c;侵删。 取消svn关联脚本 创建一个文件&#xff0c;后缀名为reg&#xff0c;将下面的脚本复制到文件里。 Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Folder\shel…

spring boot中使用spring cache

原因 项目原来越慢&#xff0c;为了提升效率加入spring cache 初步想法把数据库的压力减轻一点。 引入 pom 中加入&#xff1a; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId&g…

机器学习_集成学习_梯度提升_回归_决策树_XGBoost相关概念

目录 1. 机器学习 使用监督吗&#xff1f;什么又是监督学习&#xff1f; 2. 与XGBoost 类似的机器学习方法有哪些&#xff1f; 3. 随机森林方法 和 梯度提升方法 有什么区别&#xff1f; 分别应用于什么场景&#xff1f; 4. 决策树回归方法 和 Gradient Boosting类回归方法…

为什么我的微信小程序 窗口背景色backgroundColor设置参数 无效的问题处理记录!

当我们在微信小程序 json 中设置 backgroundColor 时&#xff0c;实际在电脑的模拟器中根本看不到效果。 这是因为 backgroundColor 指的窗体背景颜色&#xff0c;而不是页面的背景颜色&#xff0c;即窗体下拉刷新或上拉加载时露出的背景。在电脑的模拟器中是看不到这个动作的…

发挥ChatGPT潜力:高效撰写学术论文技巧

ChatGPT无限次数:点击直达 发挥ChatGPT潜力&#xff1a;高效撰写学术论文技巧 在当今信息爆炸的时代&#xff0c;如何高效撰写学术论文成为许多研究者关注的焦点。而随着人工智能技术的不断发展&#xff0c;如何利用ChatGPT这一先进的技术工具来提升论文写作效率&#xff0c;成…

Elasticsearch 面试题及参考答案:深入解析与实战应用

在大数据时代,Elasticsearch 以其强大的搜索能力和高效的数据处理性能,成为了数据架构师和开发者必备的技能之一。本文将为您提供一系列精选的 Elasticsearch 面试题及参考答案,帮助您在面试中脱颖而出,同时也为您的大数据架构设计提供实战参考。 目录 1. 为什么要使用 E…

Acwing_795前缀和 【一维前缀和】+【模板】二维前缀和

Acwing_795前缀和 【一维前缀和】 题目&#xff1a; 代码&#xff1a; #include <bits/stdc.h> #define int long long #define INF 0X3f3f3f3f #define endl \n using namespace std; const int N 100010; int arr[N];int n,m; int l,r; signed main(){std::ios::s…

Flink基于Hudi维表Join缺陷解析及解决方案

Hudi&#xff0c;这个近年来备受瞩目的数据存储解决方案&#xff0c;无疑是大数据领域的一颗耀眼新星。其凭借出色的性能和稳定性&#xff0c;以及对于数据湖场景的深度适配&#xff0c;赢得了众多企业和开发者的青睐。然而&#xff0c;正如任何一项新兴技术&#xff0c;Hudi在…

服务器不能DELETE和PUT

问题描述&#xff1a;前端VUE、后端JAVA&#xff0c;代码放在本地可以完美运行&#xff0c;放在服务器外网不能运行delete和put&#xff0c;get和post不能运行 经过摸索总结&#xff0c;在不改变原有RESTful的情况下&#xff0c;亲身实验&#xff0c;得到两种解决办法&#xff…

力扣爆刷第107天之CodeTop100五连刷21-25

力扣爆刷第107天之CodeTop100五连刷21-25 文章目录 力扣爆刷第107天之CodeTop100五连刷21-25一、103. 二叉树的锯齿形层序遍历二、92. 反转链表 II三、54. 螺旋矩阵四、160. 相交链表五、23. 合并 K 个升序链表 一、103. 二叉树的锯齿形层序遍历 题目链接&#xff1a;https://…