编程语言|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(…

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

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

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

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

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

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

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

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

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…

Gitlab 实现仓库完全迁移,包括所有提交记录、分支、标签

1 方案一&#xff1a;命令 cd <项目目录> git fetch --all git fetch --tags git remote rename origin old-origin #可以不保留 git remote add origin http://***(项目的新仓库地址) #git remote set-url origin <项目的新仓库地址> git push origin --all git…

项目管理:项目进度管理的五大关键步骤

作为项目经理&#xff0c;要想做好项目进度管理&#xff0c;可以遵循以下五个关键步骤&#xff1a; 一、确定范围和分解目标 1、明确项目目标&#xff1a;首先&#xff0c;要清晰地定义项目的总体目标和预期成果。 2、范围界定&#xff1a;详细列出项目所需完成的所有任务和…

LDL^H分解求逆矩阵与MATLAB仿真(Right-Looking)

通过分解将对称正定厄米特矩阵分解成下三角矩阵L和对角矩阵D来求其逆矩阵 目录 前言 一、LDL^H基本算法 二、LDL^H Right-Looking算法 三、D矩阵求逆 四、L矩阵求逆 五、A矩阵求逆 六、计算量分析 七、MATLAB仿真 八、参考资料 总结 前言 在线性代数中&#xff0c;LDL…

HarmonyOS入门--配置环境 + IDE汉化

文章目录 下载安装DevEco Studio配置环境先认识DevEco Studio界面工程目录工程级目录模块级目录 app.json5module.json5main_pages.json通知栏预览区 运行模拟器IED汉化 下载安装DevEco Studio 去官网下载DevEco Studio完了安装 配置环境 打开已安装的DevEco Studio快捷方式…

Java中有哪些容器(集合类)?

Java中的集合类主要由Collection和Map这两个接口派生而出&#xff0c;其中Collection接口又派生出三个子接 口&#xff0c;分别是Set、List、Queue。所有的Java集合类&#xff0c;都是Set、List、Queue、Map这四个接口的实现 类&#xff0c;这四个接口将集合分成了四大类&#…

蓝桥杯 - 小明的背包1(01背包)

解题思路&#xff1a; 本题属于01背包问题&#xff0c;使用动态规划 dp[ j ]表示容量为 j 的背包的最大价值 注意&#xff1a; 需要时刻提醒自己dp[ j ]代表的含义&#xff0c;不然容易晕头转向 注意越界问题&#xff0c;且 j 需要倒序遍历 如果正序遍历 dp[1] dp[1 - vo…

Android应用程序的概念性描述

1.概述 Android 应用程序包含了工程文件、代码和各种资源&#xff0c;主要由 Java 语言编写&#xff0c;每一个应用程序将被编译成Android 的一个 Java 应用程序包&#xff08;*.apk&#xff09;。 由于 Android 系统本身是基于 Linux 操作系统运行的&#xff0c;因此 …

SpringBoot Redis 之Lettuce 驱动

一、前言 一直以为SpringBoot中 spring-boot-starter-data-redis使用的是Jredis连接池&#xff0c;直到昨天在部署报价系统生产环境时&#xff0c;因为端口配置错误造成无法连接&#xff0c;发现报错信息如下&#xff1a; 一了解才知道在SpringBoot2.X以后默认是使用Lettuce作…

蓝桥杯 2022 省A 选数异或

一种比较无脑暴力点的方法&#xff0c;时间复杂度是(nm)。 (注意的优先级比^高&#xff0c;记得加括号(a[i]^a[j])x&#xff09; #include <iostream> #include <vector> #include <bits/stdc.h> // 包含一些 C 标准库中未包含的特定实现的函数的头文件 usi…

成都市酷客焕学新媒体科技有限公司:实现品牌的更大价值!

成都市酷客焕学新媒体科技有限公司专注于短视频营销&#xff0c;深知短视频在社交媒体中的巨大影响力。该公司巧妙地将品牌信息融入富有创意和趣味性的内容中&#xff0c;使观众在轻松愉悦的氛围中接受并传播这些信息。凭借独特的创意和精准的营销策略&#xff0c;成都市酷客焕…

第二证券|打新股有风险吗?

打新股有危险&#xff0c;其主要危险是破发&#xff0c;其间呈现以下状况&#xff0c;新股可能会破发&#xff1a; 1、估值过高 新股的估值过高&#xff0c;与其价值不相契合&#xff0c;其泡沫性较大&#xff0c;然后导致个股在上市之后&#xff0c;稳健投资者以及主力大量地…

10个替代Sketch的软件大盘点!第一款震撼来袭!

Sketch是Mac平台上专门为用户界面设计的矢量图形绘制工具。Sketch简单的界面背后有优秀的矢量绘制能力和丰富的插件库。但遗憾的是&#xff0c;Sketch只能在Mac平台上使用和浏览&#xff0c;而且是本地化的工具&#xff0c;云共享功能并不完善。在本文中&#xff0c;我们评估了…

金三银四面试题(一):JVM类加载与垃圾回收

面试过程中最经典的一题&#xff1a; 请你讲讲在JVM中类的加载过程以及垃圾回收&#xff1f; 加载过程 当Java虚拟机&#xff08;JVM&#xff09;启动时&#xff0c;它会通过类加载器&#xff08;ClassLoader&#xff09;加载Java类到内存中。类加载是Java程序运行的重要组成…

python(一)网络爬取

在爬取网页信息时&#xff0c;需要注意网页爬虫规范文件robots.txt eg:csdn的爬虫规范文件 csdn.net/robots.txt User-agent: 下面的Disallow规则适用于所有爬虫&#xff08;即所有用户代理&#xff09;。星号*是一个通配符&#xff0c;表示“所有”。 Disallow&…