当前位置: 首页 > news >正文

C 语言 “神秘魔杖”—— 指针初相识,解锁编程魔法大门(一)

文章目录

  • 一、概念
    • 1、取地址操作符(&)
    • 2、解引用操作符(*)
    • 3、指针变量
      • 1、 声明和初始化
      • 2、 用途
  • 二、内存和地址
  • 三、指针变量类型的意义
    • 1、 指针变量类型的基本含义
    • 2、 举例说明不同类型指针变量的意义
  • 四、const修饰指针
    • 1、const修饰指针的三种情况
  • 五、指针运算
    • 1、指针的算术运算
    • 2、指针的关系运算
  • 六、野指针
    • 1、野指针的定义
    • 2、野指针的示例
    • 3、造成野指针的原因
    • 4、如何避免野指针
  • 七、assert断言
    • 1、assert断言的基本概念
  • 八、指针的使用和传址调用
    • 1、指针的使用
    • 2、传址调用

一、概念

  • 指针是C语言中的一个重要概念,它是一个变量,其值为另一个变量的地址。简单来说,指针“指向”了内存中的某个位置,这个位置存储着其他变量的值。就好像你有一个房间号(指针),通过这个房间号可以找到房间里存放的东西(变量的值);也就是可以通过告诉我们房间号(指针),快速的找到房间里的内容(变量的值)。

1、取地址操作符(&)

  • 含义
    • 取地址操作符&用于获取变量在内存中的地址。在C语言中,每个变量都被存储在内存中的某个位置,这个位置有一个唯一的地址。通过&操作符,我们可以得到这个地址的值。
  • 示例
    • 以下是一个简单的示例代码,展示如何使用取地址操作符。
#include <stdio.h>
int main() {int num = 10;// 使用取地址操作符获取num的地址int *ptr = &num;printf("The address of num is: %p\n", (void*)&num);return 0;
}
 - 在这个例子中,定义了一个整型变量`num`并初始化为`10`。- 然后通过`&num`获取`num`的地址,并将这个地址赋值给一个指针变量`ptr`。- `%p`是用于打印指针(地址)的格式化说明符,由于`printf`函数的参数要求,将`&num`强制转换为`(void*)`类型。

2、解引用操作符(*)

  • 含义
    • 解引用操作符*用于访问指针所指向的内存位置中的值。当我们有一个指针变量,它存储了一个变量的地址,通过解引用操作符可以获取该地址所对应的实际值。
  • 示例
    • 以下是一个示例,演示了解引用操作符的使用。
#include <stdio.h>
int main() {int num = 10;int *ptr = &num;// 使用解引用操作符获取ptr指向的内存中的值printf("The value pointed to by ptr is: %d\n", *ptr);return 0;
}
 - 在这里,首先定义了一个整型变量`num`和一个指向`int`类型的指针`ptr`,并将`num`的地址赋值给`ptr`。- 然后通过`*ptr`来解引用指针,获取`ptr`所指向的内存位置(也就是`num`所在的内存位置)中的值,- 并将其打印出来。这个值就是`10`,因为`ptr`指向`num`,而`num`的值为`10`。

总的来说,取地址操作符&和解引用操作符*是C语言中操作指针的重要工具,它们经常一起配合使用,使得程序员能够灵活地处理内存中的数据。

3、指针变量

  • 指针变量是一种特殊的变量,它存储的是另一个变量的内存地址。在C语言中,变量在内存中有自己的存储位置,这个位置可以通过地址来表示。指针变量就像是一个指向其他变量的“指示器”,它本身占用一定的内存空间,用于存放目标变量的地址。

1、 声明和初始化

  • 声明格式
    • 数据类型 *指针变量名;
    • 例如,int *p;声明了一个名为p的指针变量,它可以用来存储int类型变量的地址。这里的*是一个说明符,表示p是一个指针变量,它指向的数据类型是int
  • 初始化
    • 指针变量在使用前最好进行初始化。可以通过取地址操作符&来获取变量的地址并赋值给指针变量。例如:
int num = 10;
int *p = &num;
 - 在这个例子中,先定义了一个整型变量`num`并赋值为`10`,然后定义了一个指针变量`p`,- 并通过`&num`将`num`的地址赋值给`p`,这样`p`就指向了`num`。

2、 用途

  • 访问变量的值
    • 通过解引用指针变量(使用*操作符)可以访问它所指向变量的值。例如:
#include <stdio.h>
int main() {int num = 10;int *p = &num;// 解引用指针p来获取num的值printf("The value of num through pointer is %d\n", *p);return 0;
}
 - 这里,`*p`获取了`p`所指向的变量(即`num`)的值,然后将其打印出来。
  • 函数参数传递
    • 指针变量在函数参数传递中有重要作用。当我们想要在函数内部修改外部变量的值时,可以将变量的地址传递给函数,函数通过指针来访问和修改该变量。例如:
#include <stdio.h>
// 函数用于交换两个整数的值
void swap(int *a, int *b) {int temp;temp = *a;*a = *b;*b = temp;
}
int main() {int x = 5, y = 10;printf("Before swap: x = %d, y = %d\n", x, y);swap(&x, &y);printf("After swap: x = %d, y = %d\n", x, y);return 0;
}
 - 在这个例子中,`swap`函数接受两个指针变量`a`和`b`,通过解引用这些指针来交换它们所指向的变量的值。- 在`main`函数中,通过`&x`和`&y`将`x`和`y`的地址传递给`swap`函数,从而实现了`x`和`y`值的交换。
  • 了解指针中的操作符,我们来举一个例子:假设有一个整型变量int num = 10;,可以定义一个指针来指向这个变量。int *p;这里p就是一个指针,它可以用来存储num的地址。通过p=&num;操作,就使得p指向了num在这里插入图片描述

二、内存和地址

  • 在计算机中,内存就像是一个个小格子(字节),每个格子都有一个唯一的编号,这个编号就是地址。变量存储在内存中,指针存储的就是这些变量的内存地址。也就是说,指针就是这个内存单元的地址。
    在这里插入图片描述

  • 例如,对于上面的num变量,它在内存中有一个地址。如果num的地址是0x100(这是假设的十六进制地址),那么p的值在执行p = &num;后就会变为0x100。可以通过printf("%p\n", p);来打印p所指向的地址。
    在这里插入图片描述

三、指针变量类型的意义

1、 指针变量类型的基本含义

  • 在C语言中,指针变量的类型非常重要。指针变量的类型决定了它所指向的数据类型。例如,int *类型的指针是用来指向int类型的数据,char *类型的指针是用来指向char类型的数据。
  • 这种类型规定了指针在进行解引用操作时,编译器会按照该类型所对应的字节数来访问内存。不同数据类型在内存中占用的字节数不同,比如在大多数32位系统中,int类型通常占用4个字节,char类型占用1个字节。

2、 举例说明不同类型指针变量的意义

  • 指向整数的指针(int *
    • 示例代码:
#include <stdio.h>
int main() {int num = 10;int *ptr = &num;// 解引用指针,获取整数的值printf("The value of num through pointer is %d\n", *ptr);// 指针加法运算ptr++;// 此时ptr指向的地址向后偏移了一个int类型的大小(通常为4字节)// 这里只是展示指针类型对偏移量的影响,实际这样使用可能会导致未定义行为// 因为ptr已经指向了num之后的内存位置,而这个位置可能是未分配给程序使用的return 0;
}
 - 在这个例子中,`int *ptr`声明了一个指向`int`类型的指针`ptr`。当`ptr`被初始化为`&num`后,通过`*ptr`解引用可以获取`num`的值。- 当执行`ptr++`时,指针`ptr`会在内存中向后移动`sizeof(int)`个字节(在常见系统中是4字节),这是因为`ptr`是`int *`类型,- 编译器知道`int`类型数据的大小,所以会按照这个大小来移动指针。
  • 指向字符的指针(char *
    • 示例代码:
#include <stdio.h>
int main() {char ch = 'A';char *cptr = &ch;printf("The character through pointer is %c\n", *cptr);cptr++;// 此时cptr指向的地址向后偏移了一个char类型的大小(1字节)return 0;
}
 - 对于`char *cptr`,它指向`char`类型的数据。当`cptr`被初始化为`&ch`后,通过`*cptr`可以获取`ch`的值。执行`cptr++`时,- 指针会在内存中向后移动`sizeof(char)`个字节(1字节),这是因为`char`类型数据在内存中占用1字节,- 指针的移动是根据其指向的数据类型来确定的。
  • 指向结构体的指针(struct xxx *
    • 假设我们有一个简单的结构体:
#include <stdio.h>
struct Student {char name[20];int age;
};
int main() {struct Student stu = {"John", 20};struct Student *sptr = &stu;// 解引用结构体指针,访问结构体成员printf("The student's name is %s and age is %d\n", sptr->name, sptr->age);return 0;
}
 - 在这里,`struct Student *sptr`是一个指向`struct Student`类型结构体的指针。- 当它被初始化为`&stu`后,通过`->`操作符(这是一种用于结构体指针访问成员的便捷操作符,- 等价于`(*sptr).name`和`(*sptr).age`)可以访问结构体`stu`中的成员。如果要移动这个指针,- 编译器会按照`struct Student`类型结构体的大小(这个大小是`name`成员数组的大小加上`age`成员占用的大小)来移动指针。

四、const修饰指针

1、const修饰指针的三种情况

  • 指向常量的指针(const修饰所指对象)
    • 含义
      • 这种情况下,指针所指向的数据被视为常量,不能通过该指针来修改所指向的数据,但指针本身可以重新指向其他内存单元。
    • 语法形式
      • const 数据类型 *指针变量名;,例如const int *p;
    • 示例
#include <stdio.h>
int main() {int num1 = 10;int num2 = 20;const int *p = &num1;// 下面这行代码是错误的,因为不能通过p修改它所指向的数据// *p = 30;p = &num2;  // 这是可以的,指针p可以重新指向num2printf("The value pointed to by p is %d\n", *p);return 0;
}
 - 在这个例子中,`p`是一个指向`const int`类型的指针,也就是`p`所指向的`int`数据被当作常量。- 当试图通过`*p = 30`修改`p`所指向的数据时,编译器会报错。但可以将`p`重新指向`num2`,因为只是改变了指针的指向,- 而没有违反`const`的限制。
  • 常量指针(const修饰指针本身)
    • 含义
      • 指针本身是常量,它不能再重新指向其他内存单元,但可以通过该指针修改它所指向的数据。
    • 语法形式
      • 数据类型 *const指针变量名;,例如int *const p;
    • 示例
#include <stdio.h>
int main() {int num1 = 10;int *const p = &num1;*p = 30;  // 这是可以的,能够修改p所指向的数据// 下面这行代码是错误的,因为p是常量指针,不能重新指向其他内存单元// p = &num2;printf("The value of num1 after modification is %d\n", num1);return 0;
}
 - 这里,`p`是一个常量指针,它被初始化为指向`num1`后,就不能再指向其他变量了。- 不过,可以通过`*p = 30`修改`p`所指向的`num1`的值,因为`const`修饰的是指针本身,而不是它所指向的数据。
  • 指向常量的常量指针(const同时修饰指针和所指对象)
    • 含义
      • 指针本身不能重新指向其他内存单元,并且不能通过该指针修改所指向的数据。
    • 语法形式
      • const 数据类型 *const指针变量名;,例如const int *const p;
    • 示例
#include <stdio.h>
int main() {int num1 = 10;const int *const p = &num1;// 下面这行代码是错误的,不能通过p修改所指向的数据// *p = 30;// 下面这行代码也是错误的,p不能重新指向其他内存单元// p = &num2;return 0;
}
 - 在这个例子中,`p`是一个指向常量的常量指针。既不能通过`*p`修改`p`所指向的数据,也不能让`p`重新指向其他内存单元,- `p`的指向和它所指向的数据都被`const`修饰固定了。
  • 指针可以进行算术运算,如加法、减法等。当指针加上或减去一个整数时,它实际移动的字节数是该整数乘以指针所指向数据类型的大小。
  • 例如,对于一个int *p,如果p的值是0x100,执行p++;后,p的值会增加sizeof(int)字节。在32位系统中,int占4字节,所以p的值会变为0x100 + 4 = 0x104
  • 指针还可以进行减法运算,用来计算两个指针之间的距离。例如,有两个指针p1p2指向同一个数组中的元素,int *p1, *p2;p1 - p2的结果是两个指针之间元素的个数。

五、指针运算

1、指针的算术运算

  • 指针与整数相加/相减
    • 含义
      • 当指针与一个整数进行加法或减法运算时,指针移动的字节数是该指针所指向的数据类型大小乘以这个整数。例如,对于一个int *类型的指针(假设int类型占4字节),如果与整数n相加,那么指针在内存中实际向前移动4 * n字节。
    • 示例
#include <stdio.h>
int main() {int arr[] = {1, 2, 3, 4, 5};int *p = &arr[0];// 指针p向后移动2个int类型的长度p = p + 2;printf("The value pointed to by p after addition is %d\n", *p);// 指针p向前移动1个int类型的长度p = p - 1;printf("The value pointed to by p after subtraction is %d\n", *p);return 0;
}
 - 在这个例子中,首先定义了一个整型数组`arr`,并让指针`p`指向数组的第一个元素`arr[0]`。- 当执行`p = p + 2`时,由于`p`是`int *`类型,`int`类型通常占4字节,- 所以`p`在内存中向后移动了`2 * 4 = 8`字节,此时`p`指向`arr[2]`,输出为`3`。- 接着执行`p = p - 1`,`p`向前移动了4字节,此时`p`指向`arr[1]`,输出为`2`。
  • 两个指针相减
    • 含义
      • 两个相同类型的指针相减,结果是两个指针之间相隔的元素个数,而不是字节数。这个运算的结果类型是ptrdiff_t(在<stddef.h>头文件中定义的有符号整数类型)。这个运算通常用于计算数组中两个元素之间的距离。
    • 示例
#include <stdio.h>
#include <stddef.h>
int main() {int arr[] = {1, 2, 3, 4, 5};int *p1 = &arr[1];int *p2 = &arr[3];ptrdiff_t diff = p2 - p1;printf("The difference between p2 and p1 is %td\n", diff);return 0;
}
 - 在这里,定义了数组`arr`,`p1`指向`arr[1]`,`p2`指向`arr[3]`。- 当计算`p2 - p1`时,结果是`2`,因为`p2`和`p1`之间相隔2个`int`类型的元素。

2、指针的关系运算

  • 含义
    • 指针可以进行关系运算(如><>=<===!=),用于比较两个指针在内存中的位置关系。通常用于判断指针是否指向同一个数组中的元素,或者判断一个指针是否超出了某个数组的范围。
  • 示例
#include <stdio.h>
int main() {int arr[] = {1, 2, 3, 4, 5};int *p1 = &arr[0];int *p2 = &arr[3];if (p2 > p1) {printf("p2 points to a later element in the array than p1\n");}return 0;
}
 - 在这个例子中,定义了数组`arr`,`p1`指向`arr[0]`,`p2`指向`arr[3]`。- 通过比较`p2 > p1`,因为`p2`在内存中指向的位置在`p1`之后,所以条件成立,会输出相应的信息。

需要注意的是,指针运算应该在合理的范围内进行,特别是对于非数组元素的指针进行算术运算可能会导致未定义行为。

六、野指针

1、野指针的定义

  • 野指针是指指针变量指向的内存位置是不确定的(随机的、不可预知的)。野指针不是NULL指针,NULL指针明确地指向地址为0的内存空间,而野指针指向的位置是未定义的,可能是已经被释放的内存区域、未分配的内存区域或者其他非法的内存地址。使用野指针可能会导致程序崩溃、数据损坏或者产生不可预测的行为。

2、野指针的示例

  • 下面是一个产生野指针的简单示例:
#include <stdio.h>
int main() {int *p;// 此时p是一个未初始化的指针,它的值是随机的,是野指针*p = 10;return 0;
}
  • 在这个例子中,定义了一个指针变量p,但是没有对它进行初始化。然后就试图通过*p = 10来修改p所指向的内存位置的值。由于p没有被初始化,它指向的是一个随机的内存地址,这个操作可能会导致程序崩溃或者出现其他错误。

3、造成野指针的原因

  • 指针变量未初始化
    • 这是最常见的原因之一。当定义一个指针变量时,如果没有给它赋初值,它的值是随机的,就会成为野指针。例如上面的例子中,int *p;定义后没有初始化p就直接使用。
  • 指针所指向的内存被释放后,指针没有置为NULL
    • 当使用free()函数(在动态内存分配中)释放了指针所指向的内存后,指针本身的值(即内存地址)并没有改变,它仍然指向原来的内存位置。而这个内存位置已经被释放,再通过这个指针访问内存就会出现问题。例如:
#include <stdio.h>
#include <stdlib.h>
int main() {int *p = (int *)malloc(sizeof(int));if (p!= NULL) {*p = 10;// 释放内存free(p);// 此时p成为野指针,因为它仍然指向刚刚被释放的内存位置*p = 20; }return 0;
}
  • 在这个例子中,通过malloc函数分配了一块内存给p,然后给这块内存赋值为10。接着使用free函数释放了这块内存,但是p仍然指向这块被释放的内存。当试图再次通过*p = 20修改这个内存位置的值时,就会出现错误,因为这块内存已经被释放,此时p是野指针。

4、如何避免野指针

  • 初始化指针变量
    • 尽量在定义指针变量时就对它进行初始化。如果暂时不知道指针应该指向哪里,可以将它初始化为NULL。例如int *p = NULL;,这样在使用p之前就可以通过判断p == NULL来确定是否可以安全地进行解引用操作。
  • 及时将释放内存后的指针置为NULL
    • 在使用free()函数释放指针所指向的内存后,应该立即将指针赋值为NULL。例如:
#include <stdio.h>
#include <stdlib.h>
int main() {int *p = (int *)malloc(sizeof(int));if (p!= NULL) {*p = 10;free(p);p = NULL;// 此时p为NULL,后续代码如果不小心使用p,就可以通过判断p == NULL避免错误操作}return 0;
}
  • 注意指针的作用域和生命周期
    • 确保指针在其指向的对象的生命周期内有效。例如,在一个函数内部定义的局部指针,在函数返回后,如果这个指针指向的是函数内部的局部变量,那么这个指针就不应该再被使用,因为函数返回后,局部变量的内存已经被释放。# 七、assert断言
  • assert是一个宏,用于在程序调试阶段检查一个条件是否为真。当条件为假时,程序会终止并输出错误信息。在涉及指针时,可以使用assert来检查指针是否为NULL等情况。
  • 例如:
    int *p = NULL;
    assert(p!= NULL);  // 程序会在这里终止,因为p为NULL
    
  • 通常在程序开发过程中,使用assert可以帮助发现一些潜在的指针错误,提高程序的健壮性。不过在发布版本中,可能会因为性能等原因关闭assert检查。

七、assert断言

1、assert断言的基本概念

  • 定义
    • assert是一个在C语言标准库<assert.h>中定义的宏。它用于在程序开发过程中进行调试检查,帮助程序员验证程序中的假设是否成立。其主要功能是检查一个表达式的值是否为真,如果表达式的值为假(即为0),则程序会以一种“标准错误”的方式终止,并输出相关的错误信息,包括断言失败的表达式、文件名和行号等内容。
  • 目的
    • 用于捕捉程序中的逻辑错误,特别是在程序开发阶段,可以帮助定位那些不应该出现的错误情况。例如,在使用指针时,可以通过assert来检查指针是否为NULL,以避免因为空指针解引用而导致的程序崩溃。
  1. 使用assert断言的示例
    • 检查指针是否为NULL
      • 示例代码如下:
#include <stdio.h>
#include <assert.h>
int main() {int *p = NULL;// 使用assert检查指针p是否为NULLassert(p!= NULL);// 如果p为NULL,程序会在这里终止,并输出错误信息// 因为p是NULL,所以下面这行代码不会执行*p = 10;return 0;
}
 - 在这个例子中,首先定义了一个指针`p`并将其初始化为`NULL`。- 然后使用`assert(p!= NULL)`来检查`p`是否不为`NULL`。由于`p`实际上是`NULL`,所以这个断言会失败。- 当断言失败时,程序会终止,并输出类似于以下的错误信息(具体格式可能因编译器和环境而异):- `Assertion failed: p!= NULL, file main.c, line 7`- 这表明在`main.c`文件的第7行,`p!= NULL`这个断言不成立。- 这样就可以很容易地定位到错误发生的位置,并且知道是由于指针为`NULL`导致的问题。
  • 检查数组索引是否越界(结合指针运算)
    • 假设我们有一个函数,用于遍历一个整数数组并打印元素,代码如下:
#include <stdio.h>
#include <assert.h>
void print_array(int *arr, int size) {int i;// 对于每个合法的索引,打印数组元素for (i = 0; i < size; i++) {assert((arr + i) >= arr);// 检查指针是否还在数组范围内printf("%d ", *(arr + i));}
}
int main() {int arr[] = {1, 2, 3, 4, 5};int size = sizeof(arr) / sizeof(arr[0]);print_array(arr, size);return 0;
}
 - 在`print_array`函数中,`for`循环用于遍历数组。- 在每次循环中,使用`assert((arr + i) >= arr)`来检查`arr + i`这个指针是否还在数组范围内。- `arr + i`表示数组`arr`中第`i`个元素的地址,它应该总是大于或等于数组的起始地址`arr`。- 如果在循环过程中由于某种原因(例如数组索引计算错误)导致`arr + i`小于`arr`,- 断言就会失败,程序会终止并输出错误信息,帮助我们发现数组索引越界的问题。

八、指针的使用和传址调用

1、指针的使用

  • 访问变量
    • 基本原理
      • 通过指针可以间接访问它所指向的变量。首先要获取变量的地址,然后将这个地址存储在指针变量中,最后通过解引用指针来访问变量的值。
    • 示例
#include <stdio.h>
int main() {int num = 10;int *p = &num;// 解引用指针p获取num的值printf("The value of num through pointer is %d\n", *p);return 0;
}
 - 在这个例子中,`int *p = &num;`声明了一个指针`p`并使其指向变量`num`。`*p`用于解引用指针,从而获取`p`所指向变量(即`num`)的值。
  • 动态内存分配
    • 基本原理
      • C语言中的malloccallocrealloc函数用于动态分配内存,返回的是指向所分配内存块的指针。malloc函数分配指定字节数的内存,calloc函数在分配内存的同时将内存块初始化为0,realloc函数用于重新分配已经分配过的内存块的大小。
    • 示例(使用malloc
#include <stdio.h>
#include <stdlib.h>
int main() {int *arr;int n = 5;// 分配能容纳5个整数的内存空间arr = (int *)malloc(n * sizeof(int));if (arr == NULL) {// 检查内存分配是否成功printf("Memory allocation failed!\n");return -1;}// 使用分配的内存for (int i = 0; i < n; i++) {arr[i] = i + 1;}for (int i = 0; i < n; i++) {printf("%d ", arr[i]);}// 释放内存free(arr);return 0;
}
 - 这里,`arr = (int *)malloc(n * sizeof(int));`分配了足够容纳`n`个`int`类型数据的内存空间。- 如果`arr`不为`NULL`,则表示内存分配成功,可以像使用普通数组一样使用`arr`。- 最后,使用`free(arr)`释放分配的内存。

2、传址调用

  • 基本原理
    • 在C语言中,函数参数传递默认是值传递,即函数会复制一份实参的值作为形参。传址调用则是将变量的地址作为参数传递给函数。这样,函数内部可以通过指针来访问和修改外部变量的值。
  • 示例(交换两个数)
#include <stdio.h>
// 交换两个整数的函数,使用传址调用
void swap(int *a, int *b) {int temp;temp = *a;*a = *b;*b = temp;
}
int main() {int x = 5, y = 10;printf("Before swap: x = %d, y = %d\n", x, y);swap(&x, &y);printf("After swap: x = %d, y = %d\n", x, y);return 0;
}
 - 在这个例子中,`swap`函数接受两个指针参数`a`和`b`。在`main`函数中,通过`swap(&x, &y)`将`x`和`y`的地址传递给`swap`函数。在`swap`函数内部,通过解引用指针`*a`和`*b`来访问和交换`x`和`y`的值。这样,就实现了在函数内部修改外部变量的值。
  • 通过传址调用返回多个值
    • 示例(计算一个数的平方和立方)
#include <stdio.h>
// 计算一个数的平方和立方,通过指针返回结果
void square_and_cube(int num, int *square, int *cube) {*square = num * num;*cube = num * num * num;
}
int main() {int num = 3;int square, cube;square_and_cube(num, &square, &cube);printf("The square of %d is %d and the cube is %d\n", num, square, cube);return 0;
}
 - 这里,`square_and_cube`函数接受一个整数`num`和两个指针`square`和`cube`。- 函数内部通过解引用指针来将计算结果(`num`的平方和立方)- 存储在`main`函数中定义的`square`和`cube`变量中,从而实现了通过一个函数返回多个值的功能。

http://www.mrgr.cn/news/79017.html

相关文章:

  • MySQL 高效批量删除海量数据策略解析
  • FreeSWITCH mod_conference 的按键会控
  • c++ AC自动机
  • 三维测量与建模笔记 - 5.3 光束法平差(Bundle Adjustment)
  • 【人工智能】用Python实现卷积神经网络(CNN)进行图像分类:从零开始的深度学习教程
  • Linux:进程间通信之system V
  • [docker中首次配置git环境与时间同步问题]
  • Spring Cloud Alibaba(六)
  • Java NIO channel
  • 【教学类-43-25】20241203 数独3宫格的所有可能-使用模版替换(12套样式,空1格-空8格,每套510张,共6120小图)
  • Bert+CRF的NER实战
  • OpenSSL 自建CA 以及颁发证书(网站部署https双向认证)
  • 细说STM32单片机用定时器触发DAC输出三角波并通过串口观察波形的方法
  • RealESRGAN技术详解(附代码)
  • DIY搭建网站(学术个人介绍主页)
  • HTML5+JavaScript实现连连看游戏
  • FreeRTOS之ARM CR5栈结构操作示意图
  • MATLAB提供的窗函数
  • Alibaba Druid(简称Druid)
  • Android KEY的哪些事儿
  • CTF-PWN: WEB_and_PWN [第一届“吾杯”网络安全技能大赛 Calculator] 赛后学习(不会)
  • 链表的分类以及双向链表的实现
  • 十二、消息队列-MQ
  • 国产CPU 安装Windows可行性操作
  • Redis 之持久化
  • 将空白背景透明化