指针

定义

指针是一个变量,存储另一个变量的内存地址,它允许直接访问和操作内存中的数据,使得程序能够以更灵活和高效的方式处理数据和内存。

获取变量地址:使用取地址符 &

访问地址上的数据:使用解引用符 *

例子1

指针是存储另一个变量地址的变量。通过使用取地址符 & 和解引用符 *,我们可以灵活地访问和操作内存中的数据 。

#include <stdio.h>

int main() {
int var = 10; // 定义一个整数变量
int *p = &var; // 定义一个指向整数的指针,并将其初始化为变量 var 的地址 printf("Address of var: %p\n", &var); // 输出变量 var 的地址
printf("Address stored in pointer p: %p\n", p); // 输出指针 p 中存储的地址
printf("Value of var using pointer: %d\n", *p); // 通过指针 p 解引用获取 var 的值 // 修改 var 的值,通过指针 p
*p = 20;
printf("New value of var: %d\n", var); // 输出修改后的 var 的值 return 0;
}

例子2

指针类型决定了它指向的变量类型,以及通过指针可以访问的数据大小。不同类型的指针在操作时会有不同的步长,比如:

int *p = (int *)a;:将 char 类型数组的首地址强制转换为 int 指针类型。由于 int 类型通常占用 4 个字节,因此通过 p 访问数据时,每次会读取 4 个字节的数据。

char *q = a;:将 char 类型数组的首地址赋值给 char 指针类型。char 类型占用 1 个字节,因此通过 q 访问数据时,每次只会读取 1 个字节的数据。

#include <stdio.h>

int main() {
char a[12] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C};
int *p = (int *)a; // 将 char 数组的地址赋值给 int 指针
char *q = a; // 将 char 数组的地址赋值给 char 指针 // 使用 int 指针访问数据
printf("Using int pointer:\n");
for (int i = 0; i < 3; i++) {
printf("p[%d]: 0x%08x\n", i, *(p + i));
} // 使用 char 指针访问数据
printf("Using char pointer:\n");
for (int i = 0; i < 12; i++) {
printf("q[%d]: 0x%02x\n", i, *(q + i));
} return 0;
} /*
输出
Using int pointer:
p[0]: 0x04030201
p[1]: 0x08070605
p[2]: 0x0c0b0a09 Using char pointer:
q[0]: 0x01
q[1]: 0x02
q[2]: 0x03
q[3]: 0x04
q[4]: 0x05
q[5]: 0x06
q[6]: 0x07
q[7]: 0x08
q[8]: 0x09
q[9]: 0x0a
q[10]: 0x0b
q[11]: 0x0c
*/

一级指针和二级指针

一级地址(一级指针)

定义

一级地址指的是指向普通变量的指针,也就是直接存储变量的地址的指针。在C中,通常我们操作的是一级地址,例如指向整数、浮点数或其他基本数据类型的指针。

例子

int *ptr;  // ptr 是一个指向整数的指针,是一级地址
float *ptr_float; // ptr_float 是一个指向浮点数的指针,也是一级地址

二级地址(二级指针)

定义

二级地址是指向指针的指针,也就是存储另一个指针的地址的指针。在C中,可以通过二级指针来操作指向指针的指针,用来间接修改指针指向的值或者传递指针的引用。

例子

int x = 10;
int *ptr1 = &x; // ptr1 是一个指向 x 的指针,是一级地址
int **ptr2 = &ptr1; // ptr2 是一个指向 ptr1 的指针,是二级地址

例子

分析表达式 *(*(&arr + 1) - 1) 的值:

int arr[5] = {1, 2, 3, 4, 5};
  1. arr[5] = {1, 2, 3, 4, 5}:这定义了一个包含 5 个整数的数组 arr,其元素分别是 {1, 2, 3, 4, 5}
  2. &arr:这是数组 arr 的地址。需要注意的是,&arr 的类型是 int (*)[5],即指向一个包含 5 个整数的数组的指针。
  3. &arr + 1:这是 &arr 指针加 1。在这个上下文中,&arr 被看作是一个指向整个数组的指针,因此 &arr + 1 将指向紧随 arr 之后的内存位置。也就是说,它指向 arr 后面的内存地址,而不是数组中的下一个元素。&arr + 1 的类型仍然是 int (*)[5]
  4. *(&arr + 1):这是对 &arr + 1 解引用。&arr + 1 是一个指向数组的指针,对其解引用后,得到的仍然是一个指向数组末尾之后的指针。它的类型是 int *,即指向数组末尾之后的指针。
  5. *(&arr + 1) - 1:这将刚才得到的指针减去 1。由于指针的减法是以元素为单位的,这个操作将指针向后移动一个 int 的大小。因为 *(&arr + 1) 指向的是数组 arr 末尾之后的位置,减去 1 后,这个指针将指向数组 arr 的最后一个元素。
  6. *(*(&arr + 1) - 1):最后,对指针 *(&arr + 1) - 1 解引用,即获取这个指针所指向的值。这个指针现在指向 arr 的最后一个元素,所以这个表达式的值就是 arr 的最后一个元素的值。

因此,*(*(&arr + 1) - 1) 的值是 5,即数组 arr 的最后一个元素。

指针的自增运算

*++p

这个表达式是先对指针 p 进行自增操作,然后对新的指针进行解引用,得到新的指针所指向的值。

步骤

  1. ++p:指针 p 先自增,指向下一个元素( 自增的是指针 )。
  2. *:对自增后的指针解引用,得到新指针所指向的值。

++*p

这个表达式是对指针 p 所指向的值进行解引用,然后对解引用得到的值进行自增操作。

步骤

  1. *p:对指针 p 进行解引用,得到指向的值。
  2. ++:对解引用得到的值进行自增操作( 自增的是指针指向的值 )。

函数地址与数组地址

函数地址

函数名就是函数的入口地址,因此可以直接把函数名赋给函数指针,也可以加上取地址符号 &。取地址符号 & 是可选的,它只是显式地说明了编译器隐式执行的任务。获取函数的地址时,加不加取地址符号 & 都可以

#include <stdio.h>

int test(int i) {
return i;
} int main(void) {
int (*p1)(int) = test;
int (*p2)(int) = &test;
printf("%p, %p\n", (void *)p1, (void *)p2);
return 0;
}

数组地址

数组名本身是数组第一个元素的地址。例如,对于 int 类型的数组来说,数组名 arr 的类型是 int*,它是一个整型指针,不是数组指针。如果要取整个数组的地址,必须在数组名前面加上取地址符号 &,这样才能赋值给数组指针。获取数组的地址时,必须加上取地址符号 &

在下面的例子中, p1 是一个整型指针,指向数组的第一个元素,而 p2 是一个数组指针,指向整个数组。对 p1p2 分别进行加 1 操作,p1 会加 4 个字节(假设 int 为 4 字节),而 p2 会加 20 个字节(5 个 int)。

#include <stdio.h>

int main(void) {
int array[5] = {1, 2, 3, 4, 5};
int* p1 = array; // 指向数组第一个元素
int(*p2)[5] = &array; // 指向整个数组
printf("p1 %p\n", (void*)p1);
printf("p2 %p\n", (void*)p2);
printf("p1+1 %p\n", (void*)(p1+1)); // p1 加 1,增加 4 个字节(假设 int 为 4 字节)
printf("p2+1 %p\n", (void*)(p2+1)); // p2 加 1,增加 20 个字节(5 个 int)
return 0;
}

指针和数组

不需要严格区分的情况

访问数组元素

对于数组和指针,都可以使用下标形式或指针形式来访问元素。

#include <stdio.h>

int main(void) {
char array[] = "hello world!";
char* p = array; array[1] = 'x'; // 使用下标访问数组
*(array + 1) = 'x'; // 使用指针形式访问数组 p[1] = 'y'; // 使用下标访问指针
*(p + 1) = 'y'; // 使用指针形式访问指针 printf("%s\n", array); // 输出: hyllo world!
printf("%s\n", p); // 输出: hyllo world! return 0;
}

作为函数参数传递

实参传递数组时,形参可以是数组或指针。实参传递指针时,形参也可以是数组或指针。编译器会将数组参数退化为指针。

#include <stdio.h>

void printArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
} int main(void) {
int array[] = {1, 2, 3, 4, 5};
printArray(array, 5); // 传递数组
return 0;
}

需要严格区分的情况

使用 sizeof 计算长度

sizeof(array) 返回数组的字节数。sizeof(pointer) 返回指针的大小(在64位系统中为8字节,在32位系统中为4字节)。

#include <stdio.h>

int main(void) {
char array[] = "hello world!";
char* p = array; printf("sizeof(array) = %lu\n", sizeof(array)); // 输出: 13
printf("sizeof(p) = %lu\n", sizeof(p)); // 输出: 8 (在64位系统中) return 0;
}

计算数组长度

当数组作为参数传递时,在函数内部不能使用 sizeof 计算数组长度,因为数组会退化为指针。

#include <stdio.h>

void test(int arg[]) {
printf("sizeof(arg) = %lu\n", sizeof(arg)); // 输出指针大小,例如在64位系统中为8
} int main(void) {
int array[10];
test(array); // 传递数组
return 0;
}

声明外部变量

在一个文件中定义指针 p,在另一个文件中不能声明为数组。

//file1.c:

int* p;

// file2.c

extern int* p; // 正确:声明为指针
// extern int p[]; // 错误:不能声明为数组

指针常量和常量指针

指针常量

定义

不能改变指向的指针。也就是说,一旦初始化后,就不能再改变指向其它地址,但可以修改指针所指向地址上的内容。

例子

比如指针 p,因为它被 const 修饰,所以 p 不能被修改,它只能指向 str。如果强行对 p 进行 p++ 操作,编译时就会报错。我们称指针 p 为指针常量。尽管 p 本身不能被修改,但可以通过 p 来修改 str 的内容,例如 *p = 'H';

char str[] = "hello world!";
char *const p = str;

常量指针

定义

不能改变所指向的值的指针。也就是说,通过这个指针不能修改它所指向的值,但可以修改指针的指向。

例子

p 是一个指向 char 类型常量的指针。可以修改 p 的指向,例如 p = another_str;,但不能通过 p 修改其指向的内容,例如 *p = 'H'; 是不允许的。

const char *p = "hello world!";
char const *p = "hello world!";

指针数组和数组指针

指针数组

定义

本质是一个数组,数组的每个元素都是指针。

例子1

int *p[4]; 这一声明中,p 先与中括号结合,表示 p 是一个有四个元素的数组,每个元素的类型都是 int *(指向整型的指针)。因此,我们称 p 为指针数组。

int *p[4];

例子2

int a = 1, b = 2, c = 3, d = 4;
int *p[4] = { &a, &b, &c, &d }; for (int i = 0; i < 4; i++) {
printf("%d ", *p[i]);
}
// 输出:1 2 3 4

数组指针

定义

本质是一个指针,指向一个数组。

例子1

int (*p)[4]; 这一声明中,p 先与 * 结合,表示 p 是一个指针,然后与中括号结合,表示 p 指向一个有四个元素的数组,每个元素的类型都是 int。因此,我们称 p 为数组指针。

int (*p)[4];

例子2

int arr[4] = {1, 2, 3, 4};
int (*p)[4] = &arr; for (int i = 0; i < 4; i++) {
printf("%d ", (*p)[i]);
}
// 输出:1 2 3 4

函数指针和指针函数

函数指针

定义

本质是一个指针,指向一个函数。每个函数在内存中都有一个地址,函数调用就是跳转到这个地址开始执行,函数指针记录了这个地址的变量。

例子

p 是一个指针,指向一个函数,该函数有一个 int 类型的参数,返回值是 int可以通过函数名 test(1) 来调用函数,也可以通过指针 p 来调用 p(1)或者(*p)(1)

#include <stdio.h>

int test(int i) {
return i;
} int main(void) {
int res = 0;
int (*p)(int) = test;
res = test(1);
printf("%d\n", res);
res = p(1);
printf("%d\n", res);
res = (*p)(1);
printf("%d\n", res);
return 0;
}

应用

往结构体中放入函数

C语言的结构体不支持成员函数,但可以通过函数指针实现类似的功能。结构体中可以定义一个函数指针,用来保存函数地址。函数名可以直接赋值给函数指针。C语言的结构体在使用时必须加上 struct 关键字,而 C++ 可以省略。

#include <stdio.h>
#include <stdlib.h> // 定义一个打印函数
void print(int a, int b) {
printf("%d %d\n", a, b);
} // 定义一个结构体,其中包含一个函数指针
struct Test {
void (*p)(int, int);
}; int main(void) {
// 动态分配结构体内存
struct Test* t = (struct Test *)malloc(sizeof(struct Test));
if (t == NULL) {
fprintf(stderr, "Memory allocation failed\n");
return 1;
} // 将函数指针指向打印函数
t->p = print; // 调用函数指针
t->p(3, 4); // 输出: 3 4 // 释放动态分配的内存
free(t); return 0;
}

回调函数

是一种通过函数指针实现的技术,允许在一个函数中调用另一个函数。通常用于事件驱动编程或处理异步操作。

#include <stdio.h>

// 定义一个回调函数类型
typedef void (*Callback)(int); // 定义一个回调函数
void myCallback(int value) {
printf("Callback called with value: %d\n", value);
} // 定义一个执行操作并调用回调函数的函数
void performOperation(int x, Callback callback) {
printf("Performing operation with value: %d\n", x);
// 调用回调函数
callback(x);
} int main(void) {
// 使用回调函数
performOperation(5, myCallback);
return 0;
}

动态函数调用

在需要根据某些条件动态选择和调用不同函数时,函数指针非常有用。例如,在一个计算器程序中可以动态选择不同的操作函数。

#include <stdio.h>

// 定义操作函数
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
int divide(int a, int b) { return a / b; } int main(void) {
// 定义一个函数指针数组
int (*operations[4])(int, int) = { add, subtract, multiply, divide }; int a = 10, b = 5;
char op = '+'; // 根据操作符选择对应的函数
int (*operation)(int, int) = NULL;
switch (op) {
case '+': operation = operations[0]; break;
case '-': operation = operations[1]; break;
case '*': operation = operations[2]; break;
case '/': operation = operations[3]; break;
} if (operation != NULL) {
printf("Result: %d\n", operation(a, b));
} else {
printf("Invalid operation\n");
} return 0;
}

实现多态行为

通过函数指针数组,可以在C语言中实现类似C++的多态行为。这种技术广泛应用于设计模式和框架中。

#include <stdio.h>

// 定义一个基类结构体
struct Shape {
void (*draw)(struct Shape*);
}; // 定义一个派生类结构体
struct Circle {
struct Shape base; // 基类
int radius;
}; // 定义一个绘制函数
void drawCircle(struct Shape* shape) {
struct Circle* circle = (struct Circle*)shape;
printf("Drawing a circle with radius: %d\n", circle->radius);
} int main(void) {
// 创建一个 Circle 对象
struct Circle c;
c.base.draw = drawCircle;
c.radius = 5; // 调用绘制函数
c.base.draw((struct Shape*)&c); return 0;
}

实现状态机

是一种在不同状态之间转换的编程模式。可以通过函数指针实现状态之间的动态切换。

#include <stdio.h>

// 定义状态函数类型
typedef void (*StateFunction)(); // 定义状态函数
void stateA() {
printf("State A\n");
}
void stateB() {
printf("State B\n");
}
void stateC() {
printf("State C\n");
} int main(void) {
// 定义一个状态函数指针数组
StateFunction states[3] = { stateA, stateB, stateC }; int currentState = 0;
for (int i = 0; i < 5; i++) {
// 调用当前状态函数
states[currentState]();
// 切换到下一个状态
currentState = (currentState + 1) % 3;
} return 0;
}

代码跳转到指定位置执行

比如((void(*)())0)();代表让程序跳转到地址为0的地方去运行。

解析步骤

  1. (void (*)()):这是一个类型转换,表示一个指向返回类型为 void、无参数的函数的指针。具体来说,void (*)() 是一种函数指针类型,它指向的函数没有参数并返回 void
  2. 0:这是一个整数常量 0。在C中,可以将整数常量 0 作为空指针常量。
  3. (void (*)())0:这将整数 0 转换为类型为 void (*)() 的函数指针。换句话说,这是将 0 解释为一个指向无参数、返回类型为 void 的函数的指针。
  4. ((void (*)())0):这只是对前面步骤的类型转换进行一次包裹,结果仍然是一个类型为 void (*)() 的函数指针,指向 0 地址。
  5. ((void (*)())0)():这表示对 0 地址的函数指针进行调用。具体来说,这是试图调用位于地址 0 处的函数。

指针函数

定义

本质是一个函数,其返回值是一个指针。

例子

错误示例

下面这个例子是一个典型的错误,因为不能返回一个局部变量的地址。函数调用完毕后,局部变量的内存会被释放,即使返回了这个地址也不能使用。

int* test() {
int array[5] = {0}; // 局部变量
return array; // 错误:返回局部变量的地址
} int main(void) {
int* p = test(); // 不安全
return 0;
}

返回堆空间地址

#include <stdio.h>
#include <stdlib.h> int* test() {
int* array = (int*)malloc(sizeof(int) * 5); // 动态分配堆空间
if (array != NULL) {
for (int i = 0; i < 5; ++i) {
array[i] = i; // 初始化数组
}
}
return array; // 返回堆空间地址
} int main(void) {
int* p = test();
if (p != NULL) {
for (int i = 0; i < 5; ++i) {
printf("%d ", p[i]); // 输出:0 1 2 3 4
}
free(p); // 释放动态分配的内存
}
return 0;
}

返回全局变量地址

#include <stdio.h>

int array[5];  // 全局变量

int* test() {
for (int i = 0; i < 5; ++i) {
array[i] = i; // 初始化数组
}
return array; // 返回全局变量地址
} int main(void) {
int* p = test();
for (int i = 0; i < 5; ++i) {
printf("%d ", p[i]); // 输出:0 1 2 3 4
}
return 0;
}

返回静态变量地址

#include <stdio.h>

int* test() {
static int array[5]; // 静态变量
for (int i = 0; i < 5; ++i) {
array[i] = i; // 初始化数组
}
return array; // 返回静态变量地址
} int main(void) {
int* p = test();
for (int i = 0; i < 5; ++i) {
printf("%d ", p[i]); // 输出:0 1 2 3 4
}
return 0;
}

指针函数指针

定义

是一个指向返回指针的函数的指针。它不仅是一个指向函数的指针,而且该函数返回的也是一个指针。

例子

#include <stdio.h>
#include <stdlib.h> // 定义一个返回整数指针的函数
int* allocateArray(int size) {
int* array = (int*)malloc(size * sizeof(int)); // 动态分配内存
if (array != NULL) {
for (int i = 0; i < size; ++i) {
array[i] = i; // 初始化数组
}
}
return array; // 返回堆空间地址
} // 定义一个指向返回整数指针的函数的指针类型
typedef int* (*ArrayAllocator)(int); int main(void) {
// 定义一个指向返回整数指针的函数的指针
ArrayAllocator allocator = allocateArray; int size = 5;
// 使用指针函数指针调用函数
int* array = allocator(size);
if (array != NULL) {
for (int i = 0; i < size; ++i) {
printf("%d ", array[i]); // 输出:0 1 2 3 4
}
printf("\n");
free(array); // 释放动态分配的内存
}
return 0;
}

悬挂指针和野指针

悬挂指针

定义

悬挂指针是指向已经释放(通过 free 函数释放)或者已经超出作用域的内存的指针。当我们试图通过这样的指针访问或操作内存时,可能会导致未定义行为,因为那块内存可能已经被操作系统重新分配给其它程序使用了。

#include <stdio.h>
#include <stdlib.h> void dangling_pointer_example() {
int *p = (int *)malloc(sizeof(int));
*p = 42;
printf("Value: %d\n", *p); // 输出42 free(p); // 释放内存 // p现在是悬挂指针,访问*p将导致未定义行为
// printf("Value: %d\n", *p); // 不安全,可能导致崩溃或未定义行为
} int main() {
dangling_pointer_example();
return 0;
}

解决方法

立即将指针设为NULL:在释放内存后,将指针设置为NULL。

free(p);
p = NULL;

避免返回局部变量的指针:不要返回局部变量的指针,因为它们在函数返回后将超出作用域。

int* incorrect_function() {
int x = 42;
return &x; // 返回局部变量的指针,错误
}

野指针

定义

野指针是一个未初始化的指针,它的值是未知的,可能指向任意内存地址。当我们试图通过这样的指针访问或操作内存时,可能会导致未定义行为。

#include <stdio.h>

void wild_pointer_example() {
int *p; // 未初始化的指针
// *p = 42; // 不安全,可能导致崩溃或未定义行为
} int main() {
wild_pointer_example();
return 0;
}

解决方法

在声明指针时初始化:声明指针时将其初始化为NULL或有效地址。

int *p = NULL;

确保在使用前分配内存:在使用指针之前,确保它已经被正确初始化和分配内存。

int *p = (int *)malloc(sizeof(int));
if (p != NULL) {
*p = 42;
}

C语言指针知识总结的更多相关文章

  1. C语言指针和数组知识总结(上)

    C语言指针和数组知识总结(上) 一.指针的基础 1.C语言中,变量的值能够通过指针来改变,打印指针的语句符号可以是:  %08x 2.指针的本质 指针的本质就是变量,那么既然是变量,那么一定会分配地址 ...

  2. C语言指针入门知识

    C语言指针往往是C语言学习过程中最困难的地方, 最近重新理解了一下C语言的指针知识, 在此整理一下, 如果有错误请留言指正. 对于刚入门的人来说, 指针涉及方方面面, 从简单的数组到结构体, 都会用到 ...

  3. C语言复习:指针知识

    指针知识体系搭建 指针强化 指针是一种数据类型 指针也是一种变量,占有内存空间,用来保存内存地址 测试指针变量占有内存空间大小:sizeof(指针名); 2)*p操作内存 在指针声明时,*号表示所声明 ...

  4. [转]C语言指针学习经验总结浅谈

    指针是C语言的难点和重点,但指针也是C语言的灵魂 . 这篇C语言指针学习经验总结主要是我入职以来学习C指针过程中的点滴记录.文档里面就不重复书上说得很清楚的概念性东西,只把一些说得不清楚或理解起来比较 ...

  5. OC语言基础知识

    OC语言基础知识 一.面向对象 OC语言是面向对象的,c语言是面向过程的,面向对象和面向过程只是解决问题的两种思考方式,面向过程关注的是解决问题涉及的步骤,面向对象关注的是设计能够实现解决问题所需功能 ...

  6. C语言指针学习

    C语言学过好久了,对于其中的指针却没有非常明确的认识,趁着有机会来好好学习一下,总结一下学过的知识,知识来自C语言指针详解一文 一:指针的概念 指针是一个特殊的变量,里面存储的数值是内存里的一个地址. ...

  7. 李洪强iOS开发之OC语言基础知识

    OC语言基础知识 一.面向对象 OC语言是面向对象的,c语言是面向过程的,面向对象和面向过程只是解决问题的两种思考方式,面向过程关注的是解决问题涉及的步骤,面向对象关注的是设计能够实现解决问题所需功能 ...

  8. (转载)c语言指针学习

    前言 近期俄罗斯的陨石.四月的血月.五月北京的飞雪以及天朝各种血腥和混乱,给人一种不详的预感.佛祖说的末法时期,五浊恶世 ,十恶之世,人再无心法约束,道德沦丧,和现在正好吻合.尤其是在天朝,空气,水, ...

  9. Golang 入门系列(三)Go语言基础知识汇总

    前面已经了 Go 环境的配置和初学Go时,容易遇到的坑,大家可以请查看前面的文章 https://www.cnblogs.com/zhangweizhong/category/1275863.html ...

  10. C语言--指针详解

    这段时间在看 Linux 内核,深觉 C 语言功底不扎实,很多代码都看不太懂,深入学习巩固 C 语言的知识很有必要.先从指针开始. 一.什么是指针 C语言里,变量存放在内存中,而内存其实就是一组有序字 ...

随机推荐

  1. python之Faker库如果构造用户信息测试数据

    代码链接1:https://blog.csdn.net/qq_38484679/article/details/115244711 补充代码链接0:https://blog.csdn.net/weix ...

  2. 一文读懂Spring的SPI机制

    一. 从类加载说起 Java中的类加载器负载加载来自文件系统.网络或者其他来源的类文件.jvm的类加载器默认使用的是双亲委派模式.三种默认的类加载器Bootstrap ClassLoader.Exte ...

  3. Python 自动化爬虫利器 Playwright

    Python 自动化爬虫利器 Playwright Python Playwright 是一个基于 Node.js 的自动化测试库,它支持多种浏览器(Chrome.Firefox.Safari.Edg ...

  4. Vue——模板语法

    Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层组件实例的数据.所有 Vue.js 的模板都是合法的 HTML,所以能被遵循规范的浏览器和 HTML 解析器解析 ...

  5. 11种排序算法(Python实现)

    10种排序算法(Python实现) 冒泡排序 1. 两重循环,每次都将一个点移动到最终位置 def BubbleSort(lst): n=len(lst) if n<=1: return lst ...

  6. pandas基础--层次化索引

    pandas含有是数据分析工作变得更快更简单的高级数据结构和操作工具,是基于numpy构建的. 本章节的代码引入pandas约定为:import pandas as pd,另外import numpy ...

  7. Dva.js 快速上手指南

    先说些废话 最近在开发React技术栈的项目产品,对于数据状态的管理使用了Dva.js,作为一个资深的ow玩家,我看到这个名字第一反应就是----这不是ow里的一个女英雄吗?仔细阅读了官方文档之后,发 ...

  8. P6259

    problem 考虑使用 dfs 模拟. 由于一个程序可能在不进入无限循环的情况下运行很多步,这将会非常缓慢.因此,接下来要加速模拟,可以用记忆化搜索. 在网格中,机器人的可能状态(位置和朝向)只有 ...

  9. 获取前(后)x月的日期

    package com.jesims.busresume.web; import org.springframework.stereotype.Service; import java.text.Da ...

  10. leetcode | 103. 二叉树的锯齿形层序遍历 | JavaScript实现

    题目 给你二叉树的根节点 root ,返回其节点值的 锯齿形层序遍历 .(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行). 思路 按照正常的层序遍历,然后再对下标为奇数的数组进 ...