【C语言】数组和指针_c语言指针数组-程序员宅基地

技术标签: C语言  c语言  

目录

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 数组指针的使用

4. 数组参数和指针参数

4.1 当实参是一维数组的数组名时,形参可以是什么?

4.2 当实参是二维数组的数组名时,形参可以是什么?

4.3 当形参是一级指针时,实参可以是什么?

4.4 当形参是二级指针时,实参可以是什么?

5. 函数、指针和数组

5.1 函数指针

5.2 函数指针数组

5.3 指向函数指针数组的指针

5.4 以下代码分别表示什么

6. 回调函数

6.1 qsort函数

6.1.1 qsort函数排序整型数据

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

6.2 改写冒泡排序函数

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

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

7. 数组练习题

7.1 一维数组

7.2 字符数组

7.3 二维数组

8. 指针练习题


1. 数组

同一类型的变量——元素(element)集中在一起,在内存上排列成一条直线,这就是数组(array)

1.1 一维数组

1.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];

1.1.2 一维数组的初始化

用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;
}

1.1.3 一维数组的使用

#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.1.4 一维数组在内存中的存储

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

1.2 二维数组

以一维数组作为元素的数组是二维数组,以二维数组为元素的数组是三维数组……统称为多维数组。

1.2.1 二维数组的声明

int arr1[3][4]; // [行][列]
char arr2[3][5];
double arr3[2][4];

1.2.2 二维数组的初始化

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;
}

1.2.3 二维数组的使用

#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

1.2.4 二维数组在内存中的存储

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

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

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++:

  1. 先使用p
  2. 再自增p

++p:

  1. 先自增p
  2. 再使用p

(*p)++:

  1. 先使用*p
  2. 再自增*p

*p++或*(p++):

解引用(*)和后置自增(++)优先级相同,结合性都是从右往左,所以*p++等价于*(p++)

  1. 先使用*p
  2. 再自增p

++*p或++(*p):

  1. 先自增(*p)
  2. 再使用*p

*++p或*(++p):

  1. 先自增p
  2. 再使用*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. 指针数组和数组指针

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. 数组参数和指针参数

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. 函数、指针和数组

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)类型的数组

6. 回调函数

回调函数就是一个被作为参数传递的函数。在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. 数组练习题

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;
}

8. 指针练习题

例题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

#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;
}

1(int型)的补码用十六进制表示为0x00000001,小端模式:01 00 00 00(低地址<--->高地址)。

例题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:

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Qiuhan_909/article/details/125795107

智能推荐

vue-cli3的安装和项目创建-程序员宅基地

文章浏览阅读1.8w次,点赞7次,收藏41次。目录一,vue-cli2的安装和项目创建(一)安装vue-cli2(二)创建vue-cli2项目二, vue-cli3的安装和项目创建(一)vue-cli3的安装(二)vue-cli3项目创建1,用dos命令的方式2,图形化界面的方式一,vue-cli2的安装和项目创建(一)安装vue-cli2在安装vue-cli2之前,先要安装cnpm,参...

LLM-项目详解(一):Chinese-LLaMA-Alpaca【transformers/models/llama/modeling_llama.py文件】_transformers modeling_llama.py-程序员宅基地

文章浏览阅读261次。【代码】LLM-项目详解(一):Chinese-LLaMA-Alpaca【modeling_llama.py文件】_transformers modeling_llama.py

408经验贴-程序员宅基地

文章浏览阅读939次,点赞18次,收藏17次。博主双非一战物理跨考上岸了华科软件,初复试排名均是22/55。专业课408分数是126分,来浅谈一下我的经验。

从零开始学习 AJAX:超详细!15 分钟搞定 AJAX 原理和使用方法_ajax学习教程-程序员宅基地

文章浏览阅读3.7k次,点赞13次,收藏84次。本文将会介绍 AJAX 的原理和使用方法,并帮助你在短时间内掌握这一重要的前端技术。我们将从 AJAX 的基本概念入手,深入探讨 AJAX 的核心技术——XMLHttpRequest 对象以及常用的数据交互方式。通过本文的学习,你将能够轻松地了解 AJAX 的工作方式和应用场景,从而为你的 Web 应用程序添加更多的交互性和实时性。_ajax学习教程

PCL学习笔记(27)——Harris角点检测_点云 harris角点检测 r_normal r_keypoint-程序员宅基地

文章浏览阅读838次。源码#include <iostream>#include <pcl\io\pcd_io.h>#include <pcl/point_cloud.h>#include <pcl/visualization/pcl_visualizer.h>#include <pcl/io/io.h>#include <pcl/keypoints/harris_3D.h>//harris特征点估计类头文件声明#include <cst_点云 harris角点检测 r_normal r_keypoint

【数字电路】MacBook使用iverilog进行数字电路仿真_verilog mac-程序员宅基地

文章浏览阅读886次,点赞9次,收藏12次。MacBook使用iverilog进行数字电路仿真_verilog mac

随便推点

前端——基础认知(1)_前端页面的认识-程序员宅基地

文章浏览阅读309次。自 黑马程序员。_前端页面的认识

实战指定pod分散部署节点之pod反亲和性(podAntiAffinity)-程序员宅基地

文章浏览阅读5.3k次。目录使用背景和场景pod亲和性和反亲和性的区别podAntiAffinity实战部署反亲和性分软性要求和硬性要求附完整的deployment.yaml配置注意使用背景和场景业务中的某个关键服务,配置了多个replica,结果在部署时,发现多个相同的副本同时部署在同一个主机上,结果主机故障时,所有副本同时漂移了,导致服务间断性中断基于以上背景,实现一个服务的多个副本分散到不同的主机上,使每个主机有且只能运行服务的一个副本,这里用到的是Pod anti-affinit_podantiaffinity

python和小爱同学_小爱mini与小爱同学除了外观,还有什么较大的区别?-程序员宅基地

文章浏览阅读228次。参加完发布会刚到家,先占个坑,等我吃饱了再来回答。~~~~~~~~~~~~~~~~~~~~小爱mini已开箱,电源改成usb接口了,5V2A。扬声器在音箱底部,顶部的麦克风只有4个。灯光放到了中间,而且灯光颜色,光斑大小会变化。按键做得比较烂,mini版省钱省在这上面了。小爱同学中间有个play键,可以迅速按一下停止或开始播放,但是mini版似乎把这个按键给精减了。其它几个按键按下之后反应比较慢,..._python控制小爱音箱

基于单链表实现直接插入排序算法详解_单链表插入排序-程序员宅基地

文章浏览阅读7.7k次,点赞17次,收藏77次。插入排序属于稳定排序法,是一种常用的排序算法。直接插入排序算法可以利用静态数组来实现,也可以使用静态链表或者单链表来实现。本文给出了直接插入算法的单链表实现方法。_单链表插入排序

从零开始实现TinyWebServer_tinywebserver c++-程序员宅基地

文章浏览阅读5.6k次,点赞21次,收藏127次。从0到服务器开发——TinyWebServer前言:修改、完整注释、添加功能的项目代码:https://github.com/white0dew/WebServer它是个什么项目?——Linux下C++轻量级Web服务器,助力初学者快速实践网络编程,搭建属于自己的服务器。使用 线程池 + 非阻塞socket + epoll(ET和LT均实现) + 事件处理(Reactor和模拟Proactor均实现) 的并发模型 使用状态机解析HTTP请求报文,支持解析GET和POST请求 访问服务_tinywebserver c++

select an archetype 空白--eclipse新建maven项目的bug_select an archetype空白-程序员宅基地

文章浏览阅读1.2w次,点赞14次,收藏23次。笔者软件环境: eclipse neno; jdk1.8.0_101; maven 3.3.9安装maven之后,在eclipse中新建maven project时: 看到的画面与教程不一样: 根本找不到archetype的模板;选择catalog,根本没反应;这样子进行不下去啊,宝宝好慌啊;百度google齐上阵,为_select an archetype空白