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

指针:程序员的望远镜

指针:程序员的望远镜

    • 一、什么是指针
      • 1.1 指针的定义
      • 1.2 指针和普通变量的区别
      • 1.3 指针的作用
      • 1.4 指针的优点和缺点
    • 二、指针的基本操作
      • 2.1 取地址运算符"&"
      • 2.2 指针的声明与定义
      • 2.3 指针的初始化
      • 2.4 指针的解引用
      • 2.5 指针的赋值
      • 2.6 指针的运算
      • 2.7 指针的比较
      • 2.8 指针的运用
    • 三、指针的高级操作
      • 3.1 指针数组
      • 3.2 指向指针的指针
      • 3.3 函数指针
      • 3.4 动态内存分配与释放
      • 3.5 空指针和野指针
    • 四、指针与数组的关系
      • 4.1 数组元素的地址
      • 4.2 数组名和指针的关系
      • 4.3 指针与多维数组
    • 五、C语言程序中指针的应用实例
      • 5.1 指针作为函数参数
      • 5.2 指针作为函数返回值
      • 5.3 指针作为参数的变长参数函数
      • 5.4 通过指针实现递归
      • 5.5 指针的应用实例
    • 六、指针的常见错误和解决方法
      • 6.1 指针越界
      • 6.2 内存泄漏
      • 6.3 指针类型不匹配
      • 6.4 空指针解引用
      • 6.5 野指针
    • 七、总结
      • 7.1 C语言指针的优缺点
      • 7.2 C语言指针的应用前景

一、什么是指针

1.1 指针的定义

在C语言中,指针是一种特殊的变量,它存储了一个变量的地址,可以用来访问该变量。指针变量在定义时需要指定它所指向变量的数据类型。

指针变量的定义通常按照以下格式:

data_type *pointer_name;

其中,data_type表示指向的变量的数据类型,*表示该变量是一个指针变量,pointer_name为指针变量的名称。

例如,定义一个指向整型变量的指针变量num_ptr,可以这样写:

int *num_ptr;

这个语句定义了一个指向整型变量的指针变量num_ptr。

1.2 指针和普通变量的区别

C语言中,普通变量和指针是两种不同类型的数据。

  1. 普通变量是存放数据的内存空间的名称,可以直接访问和修改数据。普通变量必须先定义,才能使用。
  2. 指针是一个变量,它存放的是另一个变量的地址。指针可以访问和修改变量的值,也可以操作变量的地址。
  3. 普通变量有自己的数据类型,而指针也有自己的数据类型。一个指针只能指向与它类型相同的变量。
  4. 普通变量在内存中有一个固定的地址,而指针本身也有一个地址,内部也存有另一个地址,也就是指向的变量的地址。
  5. 普通变量的值可以直接赋值和比较,指针变量的值也可以直接赋值和比较,除此之外,指针的操作也可以通过“取地址”、 “解引用”等方式进行。

总的来说,普通变量和指针都是用来存储数据的,但是它们有不同的应用场景和操作方式。在C语言程序中,合理运用变量和指针,可以提高程序的效率和灵活性。

1.3 指针的作用

在C语言中,指针是一种特殊的变量,它存储的是一个内存地址,可以通过指针操作内存中的数据。指针可以在程序中实现以下功能:

  1. 动态分配内存:可以使用指针动态分配内存,例如动态数组的创建和释放,可以使用malloc()和free()函数实现。
  2. 传递参数:指针可以传递参数,可以将一个变量的地址传递给函数,在函数内部可以通过指针访问和修改该变量的值。
  3. 访问数组元素:数组名本身就是指向数组第一个元素的指针,可以通过指针访问数组中的其他元素。
  4. 优化程序:指针可以帮助优化程序性能,例如可以使用指针访问大量的数据或者在程序中传递大量的数据时,可以通过指针实现更高效的操作。
  5. 实现数据结构:指针可以实现各种数据结构,例如链表、树等。通过指针可以方便地动态地创建和管理数据结构。

总的来说,指针是C语言中的一个非常重要的概念,是实现很多高级数据结构和算法的基础。熟练地掌握指针的使用,可以帮助我们编写更高效、更灵活的程序。

1.4 指针的优点和缺点

指针是C语言中的一种重要数据类型,其优点和缺点如下:

优点:

  1. 动态内存分配:使用指针可以动态地分配内存,避免了静态内存分配的限制。
  2. 直接访问内存:指针可以直接访问内存中的数据,因此可以提高程序的执行效率。
  3. 函数参数传递:指针可以作为函数参数传递,可以使函数调用更加灵活,可以减少内存的占用。

缺点:

  1. 指针容易出现错误:指针使用不当可能会导致程序崩溃或内存泄漏等问题。
  2. 代码的可读性差:指针对于初学者来说比较难理解,代码的可读性比较差。
  3. 代码的可维护性差:使用指针的程序比较复杂,代码的可维护性比较差。

二、指针的基本操作

2.1 取地址运算符"&"

在C语言中,取地址运算符可以用来获取一个变量或者对象在内存中的地址。它的符号是&,放置在变量或者对象名字的前面。

例如:

int x = 10;
int* ptr = &x;

以上代码中,&x表示获取变量x在内存中的地址,这个地址被存储在ptr指针变量中,ptr指向的就是x的内存位置。

取地址运算符经常和指针一起使用。在上述例子中,ptr就是一个指向int类型变量的指针,通过将变量x的地址赋值给ptr,可以通过ptr访问x的值。

除了变量名,还可以用取地址运算符来获取数组元素在内存中的地址:

int arr[] = {1, 2, 3};
int* ptr = &arr[0];

以上代码中,&arr[0]表示获取数组第一个元素的地址,这个地址被存储在ptr指针变量中。

总之,取地址运算符&在C语言中非常重要,它可以帮助我们获取变量、数组、结构体等对象在内存中的地址,让我们可以通过指针来操作这些对象。

2.2 指针的声明与定义

在C语言中,指针是一种非常重要的数据类型,它存储了一个变量或对象在内存中的地址。声明和定义指针需要使用特殊的语法来区分指针的类型和所指向的数据类型。

  1. 指针声明

指针变量声明时需要在变量名前面加上一个*,表示这个变量是一个指针变量。例如:

int* ptr;
char* ch_ptr;

以上代码声明了两个指针变量,一个是指向int类型的指针,一个是指向char类型的指针。

  1. 指针定义

指针变量的定义需要在声明的基础上再加上一个地址,表示这个指针指向的是哪个变量或对象的地址。例如:

int num = 10;
int* ptr = #

以上代码定义了一个指向int类型变量的指针ptr,并将变量num的地址赋值给了ptr。

  1. 指针初始化

指针变量初始化可以在声明或定义的时候完成。例如:

int* ptr = NULL; // 将指针初始化为NULL
int num = 10;
int* ptr2 = # // 将指针初始化为变量num的地址
  1. 多级指针声明和定义

C语言中还可以定义多级指针,即指针的指针。多级指针的声明和定义需要在指针变量名前面加上多个*。例如:

int** ptr_ptr; // 二级指针
int*** ptr_ptr_ptr; // 三级指针

以上代码声明了一个二级指针ptr_ptr和一个三级指针ptr_ptr_ptr。

指针在C语言中非常重要,它可以通过指针访问和修改变量和对象的值,动态地分配内存空间,实现数据结构等。但是,在使用指针的时候需要非常小心,因为指针会直接操作内存,如果指针使用不当会导致内存泄漏和程序崩溃等问题。

2.3 指针的初始化

在C语言中,指针变量的初始化很重要,因为指针变量需要指向某个对象的地址才能使用。以下是几种指针变量的初始化方式:

  1. 将指针变量初始化为NULL

在定义指针变量时,可以将其初始化为NULL,表示该指针变量不指向任何有效的地址。例如:

int* ptr = NULL;

当指针变量指向NULL时,就不能通过这个指针变量来访问任何数据,否则会导致运行时错误。

  1. 将指针变量初始化为对象的地址

指针变量也可以初始化为某个对象的地址,例如:

int a = 10;
int* ptr = &a;

以上代码将指针变量ptr初始化为变量a的地址。这样指针变量ptr就可以通过解引用操作访问变量a的值。

  1. 动态分配内存并将地址赋值给指针变量

在C语言中,可以使用malloc函数动态地分配内存空间,然后将分配的内存地址赋值给指针变量。例如:

int* ptr = (int*) malloc(sizeof(int));

以上代码动态地分配了一个int类型大小的内存空间,并将分配的内存地址赋值给指针变量ptr。这样指针变量ptr就可以通过解引用操作来访问动态分配的内存空间了。

总之,在使用指针变量之前,一定要确保其指向的地址是有效的,否则会导致程序运行时错误。

2.4 指针的解引用

指针的解引用就是使用*操作符访问指针所指向的变量或对象。

比如,有一个整数变量 a,和一个整型指针变量 p,它存储了变量 a 的地址,那么使用指针的解引用来访问 a 的值可以这样写:

int a = 100;
int *p = &a;
printf("a = %d\n", *p);  // 输出 a 的值:100

在这个例子中,*p 就是指针 p 所指向的变量 a。

注意,要正确解引用指针,必须先确保指针指向了合法的内存地址,否则解引用会导致程序崩溃或者产生不可预知的行为。

2.5 指针的赋值

在C语言中,指针是一种特殊的变量类型,存储的是另外一个变量的内存地址。指针变量本身也有自己的内存地址。指针赋值是指将一个指针变量的值(即另一个变量的内存地址)赋给另一个指针变量。

指针赋值的语法如下:

指针变量名 = &变量名;

其中,&是取地址符号,用于获取变量的地址。

例如,有个整型变量a,声明如下:

int a = 10;

如果需要一个指针变量p指向变量a的内存地址,可以这样赋值:

int *p;    // 声明指针变量p
p = &a;    // 将变量a的内存地址赋给指针变量p

此时,指针变量p指向了变量a的内存地址。

如果需要将指针变量p赋给另一个指针变量q,可以这样赋值:

int *q;    // 声明指针变量q
q = p;     // 将指针变量p的值赋给指针变量q

此时,指针变量q也指向了变量a的内存地址。

2.6 指针的运算

在C语言中,指针是一种非常强大的工具,它可以让我们直接访问内存地址,并且可以通过指针进行各种操作。指针运算是指对指针本身进行操作,包括递增、递减、加法和减法等。

指针递增和递减运算符:

指针递增运算符“++”可以让指针指向下一个地址,指针递减运算符“–”可以让指针指向上一个地址。这两个运算符可以放在指针前面或者后面,分别表示先递增(递减)指针,再使用指针,或者先使用指针,再递增(递减)指针。

代码举例:

#include <stdio.h>int main() 
{int arr[] = {1, 2, 3, 4, 5};int *p = arr;printf("%d\n", *p);   // 输出1p++;                  // 指向下一个地址printf("%d\n", *p);   // 输出2return 0;
}

指针加法和减法运算符:

指针加法和减法运算符可以让指针指向任意的地址,包括指针本身的地址和指针指向的地址之间的空间。指针加法运算符“+”用法如下:

p + n  // 表示p向后移动n个元素

指针减法运算符“-”用法如下:

p - n  // 表示p向前移动n个元素

代码举例:

#include <stdio.h>int main() 
{int arr[] = {1, 2, 3, 4, 5};int *p = arr;printf("%d\n", *(p + 2));   // 输出3printf("%d\n", *(p - 1));   // 输出5return 0;
}

需要注意的是,在使用指针加法和减法运算符时,要确保指针指向的地址在数组范围内,否则会导致不可预测的结果。

指针-指针:

在C语言中,指针减指针操作是计算两个指针之间的距离,返回一个整数值,单位为指针类型所占的字节数。这样的计算通常用于数组中,可以方便地计算两个元素之间的偏移量。

例如,如果有一个int类型的数组,ptr1和ptr2是指向数组中不同元素的指针,那么ptr2 - ptr1将计算出ptr1和ptr2之间的元素个数。假设数组中每个元素占4个字节,那么结果将是一个整数,表示ptr2比ptr1多了几个元素。

示例代码如下:

int arr[5] = {0, 1, 2, 3, 4};
int *ptr1 = arr + 1;
int *ptr2 = arr + 3;
int distance = ptr2 - ptr1;
printf("Distance between ptr1 and ptr2 is %d\n", distance);

上述代码中,ptr1指向数组的第二个元素,ptr2指向数组的第四个元素,计算它们之间的距离得到结果为2,即ptr2比ptr1多2个元素。

指针运算总结:

指针运算是C语言中非常重要的一部分,指针的灵活运用能够大大提高程序的效率。需要注意的是,在使用指针运算时一定要确保指针指向的地址在合法的范围内,避免造成程序崩溃或者不可预测的结果。

2.7 指针的比较

在C语言中,指针是一个非常重要的概念。指针是一个变量,它存储了一个变量的地址,可以让程序员直接访问内存中的数据。指针可以进行比较,比较指针的值大小。

下面是一些关于指针比较的例子:

#include <stdio.h>int main() 
{int arr[5] = {1, 2, 3, 4, 5};int *p1 = &arr[2];int *p2 = &arr[4];int *p3 = &arr[1];// 指针比较if (p1 < p2) {printf("p1 < p2\n"); // p1指向的地址在p2指向的地址之前}if (p2 > p3) {printf("p2 > p3\n"); // p2指向的地址在p3指向的地址之后}// 指针和整数比较if (p1 < 0x7fff0000) {printf("p1 < 0x7fff0000\n"); // 比较指针和一个地址常量}if (p3 > 0x7fff0000) {printf("p3 > 0x7fff0000\n"); // 比较指针和一个地址常量}return 0;
}

在上面的例子中,我们定义了一个整型数组arr,并定义了三个指向数组元素的指针p1、p2、p3。我们比较了指针之间的大小关系,还比较了指针和地址常量之间的大小关系。

需要注意的是,指针比较只能比较同一类型的指针。如果比较不同类型的指针,则结果是不确定的。此外,指针与整数比较时,整数常量被视为地址常量。

2.8 指针的运用

C语言中指针最常见的应用之一是动态分配内存。动态分配内存指的是在程序运行时,根据需要分配所需的内存空间,这种分配方式可以极大地提高程序的灵活性,比如在开发中经常使用的链表、堆、栈数据结构中,都需要使用动态内存分配技术。

下面是一个示例程序,展示了指针动态分配内存的使用:

#include <stdio.h>
#include <stdlib.h>int main() 
{int* ptr;int size;printf("请输入要分配的元素个数:");scanf("%d", &size);// 申请动态内存ptr = (int*)malloc(size * sizeof(int));if (ptr == NULL) {printf("内存分配失败!");exit(1);}// 使用动态内存for (int i = 0; i < size; i++) {ptr[i] = i + 1;}// 输出动态内存printf("分配的动态内存为:");for (int i = 0; i < size; i++) {printf("%d ", ptr[i]);}// 释放动态内存free(ptr);return 0;
}

在上面的示例程序中,我们首先定义了一个指针ptr和一个整数变量size。接着,我们使用malloc()函数来分配内存,该函数的第一个参数表示需要分配的内存大小,第二个参数是分配内存的数据类型的大小。当malloc()函数返回的指针为空时,表示内存分配失败,需要退出程序。接着,我们使用指针ptr来操作动态分配的内存,最后使用free()函数释放动态内存。

除了动态分配内存,指针在C语言中还有很多其他的应用,比如指针可以用来访问数组元素,也可以用来作为函数参数,传递地址信息,等等。

三、指针的高级操作

3.1 指针数组

指针数组是指一个数组,这个数组的每个元素都是一个指针。在C语言中,指针数组可以用来存储一组指向不同数据类型的指针,或者是一组指针变量的地址。

例如,下面的代码声明了一个指针数组p,它包含了三个指针变量的地址:

int a = 1, b = 2, c = 3;
int* p[3] = { &a, &b, &c };

在这个例子中,p[0]包含了变量a的地址,p[1]包含了变量b的地址,p[2]包含了变量c的地址。我们可以通过下标的方式访问这些指针变量,例如:

printf("%d %d %d", *p[0], *p[1], *p[2]);

这行代码会输出 1 2 3。

指针数组在C语言中广泛应用,特别是在需要处理一组数据的时候,例如字符串、结构体、函数等。

3.2 指向指针的指针

在C语言中,指向指针的指针是一种比较高级的技术,它也被称为“二级指针”。通俗地说,指向指针的指针就是一个指针,它存储的值是另一个指针的地址。

使用指向指针的指针,可以对指针本身进行修改。例如,可以通过指向指针的指针来动态地分配内存。语法上,可以使用两个星号(**)来定义指向指针的指针。例如:

int **ppi;

上面的代码定义了一个指向指针的指针ppi。这个指针可以指向一个指针,而这个指针又可以指向一个int类型的变量。使用指向指针的指针时,需要注意它所指向的指针一定要有合法的内存地址,否则访问会出现崩溃。

指向指针的指针在很多底层的编程中都会用到,例如操作系统、网络编程等。掌握它可以让你更加熟悉C语言的内存管理和指针使用。

3.3 函数指针

C语言中的函数指针是指向函数的指针变量。它可以指向任何函数,只要函数的返回类型和参数列表与函数指针的声明匹配即可。

函数指针的语法格式为:

返回值类型 (*指针变量名) (参数列表);

其中,返回值类型和参数列表是函数的返回值类型和参数列表,指针变量名就是函数指针的名称。例如:

int (*pFunc)(int, int);

上面的代码定义了一个名为pFunc的函数指针,它可以指向返回值为int类型、有两个int类型参数的函数。

函数指针可以用来作为函数的参数,也可以作为函数的返回值。它可以让程序在运行时动态地选择需要调用的函数,从而增加程序的灵活性。函数指针还可以用来实现回调函数(Callback Function),在某些情况下尤为有用。

以下是一个简单的示例:定义一个函数指针,用来指向一个求和的函数,然后调用函数指针指向的函数:

#include <stdio.h>int sum(int a, int b) 
{return a + b;
}int main() 
{int (*pSum)(int, int);pSum = &sum;int result = (*pSum)(3, 5);printf("%d\n", result);return 0;
}

输出结果为:

8

在上面的代码中,我们首先定义了一个类型为int、参数为两个int类型的函数sum(),然后定义了一个函数指针pSum,它可以指向类型为int、参数为两个int类型的函数。接着,我们将函数sum()的地址赋给了pSum,最后调用pSum指向的函数,将结果保存在result变量中并输出。

3.4 动态内存分配与释放

在C语言中,动态内存分配和释放是通过malloc()和free()函数来实现的。

malloc()函数用于在内存堆区中分配一块指定大小的内存空间,并返回该内存空间的首地址。以下是malloc()函数的语法格式:

void* malloc(size_t size);

其中,size_t是一个无符号整型,代表要分配的内存空间的大小,单位是字节。malloc()函数返回一个void类型的指针,如果分配失败,返回NULL。

例如,分配10个int类型的内存空间:

int *p = (int*)malloc(10 * sizeof(int));

上面的代码将分配10个int类型的内存空间,并将其首地址赋给了指针变量p。由于malloc()函数返回的是void类型的指针,所以需要将其转换为int类型的指针。

free()函数用于释放动态分配的内存空间。以下是free()函数的语法格式:

void free(void* ptr);

其中,ptr是一个指向待释放内存的指针。调用free()函数后,该指针指向的内存空间就被释放,可以被其他程序使用。

例如,释放上面分配的内存空间:

free(p);

上面的代码将释放指针p指向的内存空间。

需要注意的是,在使用malloc()函数分配内存空间后,一定要使用free()函数来释放内存空间,否则会导致内存泄漏,影响程序性能,严重时甚至导致程序崩溃。同时也要注意不要释放已经释放过的内存空间,这也会导致程序崩溃。

3.5 空指针和野指针

在C语言中,空指针和野指针都是指针类型的变量。

空指针指的是没有指向任何有效对象或函数的指针。在C语言中,空指针可以用“NULL”来表示,也可以用“0”来表示。通常情况下,我们可以将一个指针初始化为NULL来表示它是一个空指针,例如:

int *ptr = NULL;

野指针指的是没有被初始化或已经被释放的指针,它指向的地址是不可知的或者已经被释放的内存中的任意位置。野指针可能会导致程序崩溃或者产生意外的行为,因此在使用指针之前应该先将其正确地初始化或者赋值。例如:

int *ptr; // 声明了一个指针,但没有初始化
*ptr = 10; // 野指针被赋值,这会导致程序崩溃

需要注意的是,空指针和野指针是两种不同的概念,前者是可以安全使用的,而后者则应该尽可能的避免。因此,在使用指针时,我们应该养成一个好习惯,即在定义指针变量时,尽可能将其初始化为NULL,这样可以避免出现野指针的问题。

四、指针与数组的关系

4.1 数组元素的地址

在C语言中,数组元素的地址可以使用下标运算符和取地址运算符来获取。

例如,对于一个名为arr的数组,我们可以使用以下方式获取其第i个元素的地址:

&arr[i]

另外,由于数组的元素在内存中是连续存储的,因此可以使用以下方式获取下一个元素的地址:

&arr[i+1]

注意,这里的下标从0开始。另外,对于多维数组,也可以使用类似的方式获取其中某个元素的地址。

4.2 数组名和指针的关系

在C语言中,数组名和指针有着密切的关系。数组名是数组第一个元素的地址,也就是数组名是一个指向数组第一个元素的指针。可以将数组名作为指针使用,对数组名进行指针运算,例如++、–、+=等操作,实际上是对指针所指向的地址进行运算。

在函数中,数组名作为参数传递时,实际上传递的是数组的首地址,也就是数组的第一个元素的地址。这样函数可以使用指针的方式来访问数组中的元素。

需要注意的是,数组名是一个常量指针,也就是不能修改数组名所指向的地址,例如以下代码是不合法的:

int arr[3] = {1, 2, 3};
int *p = arr;  // 合法,数组名赋值给指针变量
arr++;  // 不合法,修改了数组名所指向的地址

4.3 指针与多维数组

在C语言中,指针和多维数组之间也有着紧密的联系。

多维数组本质上是一维数组,只不过它的每个元素又是一个数组。例如,一个二维数组int arr[3][4]可以看作是3个一维数组int arr[0][4]、int arr[1][4]、int arr[2][4]的集合。

可以使用指向数组的指针来访问多维数组,也就是将多维数组的名字作为数组指针来使用,例如以下代码:

int arr[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
int (*p)[4];  // 定义指向含有4个元素的一维数组的指针
p = arr;  // 将二维数组名赋值给指针

在这个例子中,p是一个指向含有4个元素的一维数组的指针,也就是指向arr数组的每个元素(一维数组)的指针。这样,可以通过指针p来访问arr数组中的每个元素,例如:

printf("%d\n", *(*(p + 2) + 3));  // 输出12,访问arr[2][3]的值

需要注意的是,指向二维数组的指针的类型必须是指向含有4个元素的一维数组的指针,因为arr数组的每个元素是一个含有4个元素的一维数组。

五、C语言程序中指针的应用实例

5.1 指针作为函数参数

C语言中,指针作为函数参数是非常常见的用法,它可以用来传递数组、字符串和结构体等较大的数据,避免在函数调用时进行大块数据的复制操作,提高程序的效率。

在函数中,如果需要修改指针所指向的数据,就需要使用指针的地址来传递指针参数。例如以下代码:

void swap(int *a, int *b) 
{int temp = *a;*a = *b;*b = temp;
}int main() 
{int x = 1, y = 2;printf("x = %d, y = %d\n", x, y);  // 输出x = 1, y = 2swap(&x, &y);printf("x = %d, y = %d\n", x, y);  // 输出x = 2, y = 1return 0;
}

在这个例子中,swap函数接受两个指针参数,通过指针来交换两个整数的值,需要使用指针的地址来传递指针参数,在调用swap函数时传入&x和&y,而不是x和y的值。

5.2 指针作为函数返回值

在C语言中,指针也可以作为函数的返回值来使用。通过返回一个指针值,可以让函数返回一个指向数组、字符串等复杂数据类型的指针,方便程序的编写。需要注意的是,函数返回的指针指向的数据必须生命周期长于函数本身,否则出现了未定义行为。

以下是一个使用指针作为函数返回值的例子:

#include <stdio.h>
#include <stdlib.h>int *create_array(int n) 
{int *arr = malloc(n * sizeof(int));    // 动态分配内存for (int i = 0; i < n; i++) {arr[i] = i;   // 对数组进行初始化}return arr;    // 返回指向数组首元素的指针
}int main() 
{int n = 5;int *arr = create_array(n);   // 调用函数创建数组for (int i = 0; i < n; i++) {printf("%d ", arr[i]);    // 输出数组元素}free(arr);   // 释放数组内存return 0;
}

上述程序定义了一个create_array函数,接受一个整数参数n,返回一个大小为n的动态分配的数组,数组元素从0到n-1。在main函数中,调用create_array函数来创建一个大小为5的数组,并输出数组元素。

需要注意的是,在使用完毕动态分配的数组后,必须使用free函数来释放内存,避免内存泄漏的问题。

5.3 指针作为参数的变长参数函数

C语言中,可以使用指针作为变长参数函数的参数类型,例如printf函数的格式化字符串参数就是一个指针类型的变长参数。使用指针作为变长参数函数的参数类型需要使用stdarg.h头文件中的变长参数处理函数。

下面是一个使用指针作为变长参数函数参数类型的例子:

#include <stdio.h>
#include <stdarg.h>int sum(int count, ...) 
{int s = 0;va_list args;     // 定义va_list类型的变量argsva_start(args, count);   // 以count为界,初始化argsfor (int i = 0; i < count; i++) {s += va_arg(args, int);   // 取出args中的下一个参数并求和}va_end(args);     // 结束argsreturn s;
}int main() 
{printf("%d\n", sum(3, 1, 2, 3));    // 输出6printf("%d\n", sum(4, 10, 20, 30, 40));    // 输出100return 0;
}

在这个例子中,sum函数接受一个整数参数count,后面跟着多个整数值,使用了指针作为变长参数函数的参数类型。在函数内部,通过va_list类型的变量args来获取多个变长参数的值,并进行求和操作。在调用sum函数时,先传入整数参数count,后面再传入count个整数值,sum函数就可以使用va_list类型的变量args来获取这些整数值,进行求和操作。

需要注意,在使用完毕va_list类型的变量args后,必须使用va_end函数来结束变长参数的获取过程。同时,变长参数的类型和数量都是不确定的,因此在使用时需要格外小心,避免出现内存错误和类型错误等问题。

5.4 通过指针实现递归

在 C 语言中,可以利用指针来实现递归。递归是一种通过自身调用自身的方式来解决问题的方法。利用指针实现递归通常涉及到指向自身的指针,也称为自引用指针。

下面是一个使用指针实现递归的示例程序:

#include <stdio.h>// 定义自引用结构体
struct Node 
{int data;struct Node* next;
};// 递归函数
void recursive(struct Node* node) 
{if (node == NULL) {// 递归结束条件return;} else {printf("%d ", node->data);recursive(node->next); // 自身调用}
}int main() 
{// 创建链表struct Node node1 = {1, NULL};struct Node node2 = {2, &node1};struct Node node3 = {3, &node2};// 调用递归函数recursive(&node3);return 0;
}

该程序定义了一个自引用结构体 Node,其中包含一个整型变量 data 和一个指向自身的指针 next。在 main 函数中创建了一个包含3个结点的链表,并将链表头结点的指针传递给 recursive 函数。在 recursive 函数中,通过对结点指针的自身调用实现了递归的效果。

该程序的输出结果为:

3 2 1

这说明递归函数按照链表结点的顺序依次输出了每一个结点的数据。

5.5 指针的应用实例

举一个指针应用实例:链表数据结构。链表通常由一组节点组成,每个节点有一个数据域和一个指向下一个节点的指针域。以下是一个示例代码,其中包含链表的创建、遍历、插入和删除操作:

#include <stdio.h>
#include <stdlib.h>// 链表节点结构体
struct Node 
{int data;struct Node* next;
};// 创建链表
struct Node* create_list() 
{int n, i;struct Node *head = NULL, *tail = NULL, *p = NULL;printf("请输入链表中节点的个数:\n");scanf("%d", &n);for (i = 0; i < n; i++) {p = (struct Node*)malloc(sizeof(struct Node));printf("请输入第 %d 个节点的值:\n", i + 1);scanf("%d", &(p->data));p->next = NULL;if (head == NULL) {head = p;tail = p;} else {tail->next = p;tail = p;}}return head;
}// 遍历链表
void traverse_list(struct Node* head) 
{struct Node* p = head;while (p != NULL) {printf("%d ", p->data);p = p->next;}printf("\n");
}// 查找节点
struct Node* search_node(struct Node* head, int value) 
{struct Node* p = head;while (p != NULL) {if (p->data == value) {return p;}p = p->next;}return NULL;
}// 插入节点
void insert_node(struct Node** head, int value, int position) 
{struct Node *p = NULL, *new_node = NULL;new_node = (struct Node*)malloc(sizeof(struct Node));new_node->data = value;new_node->next = NULL;if (position < 1) {position = 1;}if (position == 1) {new_node->next = *head;*head = new_node;} else {p = *head;while (position > 2 && p != NULL) {position--;p = p->next;}if (p == NULL) {// 超出链表范围,插入到链表尾部p = *head;while (p->next != NULL) {p = p->next;}p->next = new_node;} else {new_node->next = p->next;p->next = new_node;}}
}// 删除节点
void delete_node(struct Node** head, int value) 
{struct Node *p = *head, *prev = NULL;while (p != NULL && p->data != value) {prev = p;p = p->next;}if (p == NULL) {// 没有找到匹配的节点return;}if (p == *head) {// 删除头节点*head = p->next;} else {prev->next = p->next;}free(p);
}int main() 
{struct Node* head = NULL;head = create_list();printf("链表创建成功,节点个数为:\n");traverse_list(head);int value;printf("请输入要查找的节点值:\n");scanf("%d", &value);struct Node* found_node = search_node(head, value);if (found_node != NULL) {printf("找到了值为 %d 的节点\n", value);} else {printf("没有找到值为 %d 的节点\n", value);}printf("请输入要插入的节点值:\n");scanf("%d", &value);int position;printf("请输入要插入的位置:\n");scanf("%d", &position);insert_node(&head, value, position);printf("插入节点后的链表为:\n");traverse_list(head);printf("请输入要删除的节点值:\n");scanf("%d", &value);delete_node(&head, value);printf("删除节点后的链表为:\n");traverse_list(head);return 0;
}

六、指针的常见错误和解决方法

6.1 指针越界

指针越界是指指针访问了不属于自己的内存空间,这通常会导致程序崩溃或者出现不可预期的行为。以下是一些指针越界错误的示例及解决方法:

  1. 访问未分配的内存空间

这是最常见的指针越界错误之一。当程序试图访问未分配的内存空间时,会导致崩溃或者出现不可预期的行为。

例如,以下代码试图访问未分配的内存空间:

int* p;
*p = 10;

这段代码会导致程序崩溃,因为指针p没有被初始化,没有指向任何内存空间。

解决方法:在使用指针前一定要分配足够的内存空间,可以使用malloc或calloc等函数动态分配内存空间,或者使用静态数组。例如:

int* p = (int*)malloc(sizeof(int));
*p = 10;
free(p);
  1. 访问已释放的内存空间

当程序释放了内存空间后,如果继续访问这些内存空间,就会导致指针越界错误。

例如,以下代码试图访问已释放的内存空间:

int* p = (int*)malloc(sizeof(int));
free(p);
*p = 10;

这段代码会导致程序崩溃,因为指针p指向的内存空间已经被释放。

解决方法:在释放内存空间后,一定要将指针赋值为空指针,以避免继续访问已释放的内存空间。例如:

int* p = (int*)malloc(sizeof(int));
free(p);
p = NULL;
  1. 访问数组越界

当程序访问的数组下标超出了数组范围,就会导致指针越界错误。

例如,以下代码试图访问数组越界:

int arr[5] = {1, 2, 3, 4, 5};
int* p = arr;
printf("%d", *(p + 6));

这段代码会导致程序崩溃,因为指针p指向的内存空间超出了数组范围。

解决方法:在访问数组时,一定要确保下标在数组范围内,可以使用for循环或while循环等控制结构,避免数组越界。例如:

int arr[5] = {1, 2, 3, 4, 5};
int* p = arr;for (int i = 0; i < 5; i++) 
{printf("%d ", *(p + i));
}
  1. 错误的指针类型转换

当程序将一个指针转换为不兼容的指针类型时,就会导致指针越界错误。

例如,以下代码试图将一个int类型的指针转换为char类型的指针:

int* p = (int*)malloc(sizeof(int));
char* q = (char*)p;

这段代码会导致指针越界错误,因为char类型的指针只能访问一个字节,而int类型的指针需要4个字节。

解决方法:在进行指针类型转换时,一定要确保两种类型是兼容的,可以使用强制类型转换或者intptr_t类型来转换指针类型。例如:

int* p = (int*)malloc(sizeof(int));
intptr_t q = (intptr_t)p;

以上就是C语言中指针越界的错误和解决方法的介绍,要避免这些错误可以采取相应的措施,使程序更加健壮。

6.2 内存泄漏

内存泄漏是指程序分配的内存空间没有被正确地释放,导致这些空间无法再被程序使用,从而造成内存资源的浪费。以下是一些内存泄漏的示例及解决方法:

  1. 没有释放动态分配的内存空间

当程序使用malloc函数、calloc函数或realloc函数动态分配内存空间时,如果没有使用free函数释放这些空间,就会导致内存泄漏。

例如,以下代码没有释放动态分配的内存空间:

int* p = (int*)malloc(sizeof(int));

解决方法:在程序不再使用动态分配的内存空间时,一定要使用free函数释放这些空间。例如:

int* p = (int*)malloc(sizeof(int));
free(p);
  1. 没有关闭文件指针

当程序打开了一个文件并读取了其中的数据,如果没有使用fclose函数关闭该文件指针,就会导致内存泄漏。

例如,以下代码没有关闭文件指针:

FILE* fp = fopen("data.txt", "r");
char buf[100];
fgets(buf, 100, fp);

解决方法:在程序不再使用文件指针时,一定要使用fclose函数关闭该指针。例如:

FILE* fp = fopen("data.txt", "r");
char buf[100];
fgets(buf, 100, fp);
fclose(fp);
  1. 没有释放结构体中的指针

当程序使用结构体中的指针分配了内存空间,如果没有使用free函数释放该指针所指向的内存空间,就会导致内存泄漏。

例如,以下代码没有释放结构体中的指针:

struct Person 
{char* name;int age;
};struct Person* p = (struct Person*)malloc(sizeof(struct Person));
p->name = (char*)malloc(sizeof(char) * 10);

解决方法:在程序不再使用结构体指针时,一定要使用free函数释放该指针中的所有动态分配的内存空间。例如:

struct Person 
{char* name;int age;
};struct Person* p = (struct Person*)malloc(sizeof(struct Person));
p->name = (char*)malloc(sizeof(char) * 10);
free(p->name);
free(p);

以上就是C语言中内存泄漏的问题及解决方法的介绍,要避免这些问题可以相应地采取措施,使程序更加健壮。

6.3 指针类型不匹配

在C语言中,指针类型不匹配的错误通常发生在尝试将不同类型的指针进行赋值或者类型转换的时候。当程序运行时出现这样的错误,通常会报出"error: incompatible types when assigning to type"或者"error: invalid conversion from type"的错误信息。

例如,下面的代码尝试将一个整型指针赋值给一个字符型指针,会出现指针类型不匹配的错误。

int *p;
char *q;
q = p;

为了解决这个问题,我们需要进行类型转换。可以使用强制类型转换将指针类型进行转换,例如:

int *p;
char *q;
q = (char *)p;

在上面的例子中,指针p的类型被强制转换为char类型,然后才能赋值给指针q。

另一个常见的例子是函数调用时指针类型不匹配的错误。例如,下面的代码尝试将一个字符型数组传递给一个接收整型数组的函数,也会出现指针类型不匹配的错误。

void printArray(int arr[], int size) 
{int i;for (i = 0; i < size; i++) {printf("%d ", arr[i]);}
}int main() 
{char str[] = "Hello";printArray(str, 5);return 0;
}

为了解决这个问题,我们可以使用强制类型转换将字符型数组转换为整型数组,例如:

void printArray(int arr[], int size) 
{int i;for (i = 0; i < size; i++) {printf("%d ", arr[i]);}
}int main() 
{char str[] = "Hello";int *p = (int *)str;printArray(p, 5);return 0;
}

在上面的例子中,我们先将字符型数组str的地址强制转换为整型指针p,然后再将整型指针p传递给printArray函数。

综上所述,指针类型不匹配的问题通常可以通过强制类型转换或者修改函数参数的类型来解决。但是需要注意的是,修改指针的类型或者进行强制类型转换时,可能会导致类型截断或者数据损失,需要谨慎使用。

6.4 空指针解引用

空指针是指未被初始化或指向NULL值的指针。在C语言中,使用未初始化或指向NULL值的指针解引用可能会导致程序崩溃或产生不可预测的结果。下面让我们一起来看看这个问题的详细解释和解决方案。

  1. 解引用空指针的错误

假设有一个指向int类型变量的指针p,但在使用p之前,没有为它分配内存,并将其初始化,这个指针p被称为空指针。如果在使用空指针p解引用时,也就是访问p所指向的值时,就会产生错误。以下是一个简单的例子:

int main()
{int* p = NULL;*p = 10;return 0;
}

在这个例子中,指针p被初始化为NULL,意味着它不指向任何地址。然而,在解引用指针p之后,我们试图将值10存储到该地址中,这是一个未定义的行为,可能会导致程序崩溃。

  1. 解决方法

为了避免解引用空指针的问题,我们应该始终在使用指针之前,为其分配内存并将其初始化,或者在解引用指针之前,检查指针是否为NULL。以下是几个解决方案:

2.1 初始化指针

在使用指针之前,应始终为其分配内存并将其初始化,以避免产生未定义的行为。下面是一个例子:

int main()
{int a = 10;int* p = &a;*p = 20;return 0;
}

在这个例子中,指针p被初始化为指向变量a的地址,因此在解引用指针p之前,我们可以安全地将值20存储到该地址中。

2.2 检查指针是否为NULL

在解引用指针之前,应始终检查指针是否为NULL,以避免产生未定义的行为。以下是一个例子:

int main()
{int* p = NULL;if (p != NULL){*p = 10;}return 0;
}

在这个例子中,我们检查指针p是否为NULL,只有在指针不为NULL时才进行解引用操作,可以避免解引用空指针的问题。

总之,解引用空指针是一个常见的错误,可能会导致程序崩溃或产生不可预测的结果。为了避免这个问题,我们应始终在使用指针之前,为其分配内存并将其初始化,或者在解引用指针之前,检查指针是否为NULL。

6.5 野指针

野指针是指指向未知或无效内存地址的指针。在C语言中,使用野指针可能会导致程序崩溃或产生不可预测的结果。下面让我们一起来看看这个问题的详细解释和解决方案。

  1. 使用野指针的错误

假设有一个指向int类型变量的指针p,但在使用p之前,已经将该指针p释放或悬挂,也就是说,该指针指向的内存已被释放或已不再指向有效的内存地址。在使用野指针p时,也就是访问p所指向的值时,就会产生错误。以下是一个简单的例子:

int main()
{int* p = (int*)malloc(sizeof(int));free(p);*p = 10;return 0;
}

在这个例子中,指针p被释放,但在解引用指针p之后,我们试图将值10存储到该地址中,这是一个未定义的行为,可能会导致程序崩溃。

  1. 解决方法

为了避免使用野指针,我们应始终在使用指针之前,为其分配内存并将其初始化,或者在释放指针之后,将其设置为NULL。以下是几个解决方案:

2.1 分配并初始化指针

在使用指针之前,应始终为其分配内存并将其初始化,以避免指针成为野指针。以下是一个例子:

int main()
{int* p = (int*)malloc(sizeof(int));*p = 10;free(p);return 0;
}

在这个例子中,我们分配了一个int类型变量的内存,并将指针p初始化为该内存的地址。然后我们使用指针p存储值10,并在使用后释放指针p。

2.2 将指针设置为NULL

在释放指针之后,应将其设置为NULL,以避免成为野指针。以下是一个例子:

int main()
{int* p = (int*)malloc(sizeof(int));*p = 10;free(p);p = NULL;return 0;
}

在这个例子中,我们分配了一个int类型变量的内存,并将指针p初始化为该内存的地址。然后我们使用指针p存储值10,并在使用后释放指针p。最后,我们将指针p设置为NULL,以避免它成为野指针。

总之,使用野指针是一个常见的问题,可能会导致程序崩溃或产生不可预测的结果。为了避免这个问题,我们应始终在使用指针之前,为其分配内存并将其初始化,或者在释放指针之后,将其设置为NULL。

七、总结

7.1 C语言指针的优缺点

指针是C语言中的一个重要概念,它提供了直接访问内存地址的能力。指针有它的优点和缺点,下面让我们一起来看看。

  1. 指针的优点

1.1 直接访问内存

指针可以直接访问内存,使得程序员可以更加灵活地处理内存中的数据,提高程序的效率。

1.2 函数调用

指针可以作为函数参数,将变量的地址传递给函数,使得函数可以直接修改该变量的值。这种方式可以避免在函数调用时进行大量的数据拷贝,提高程序的效率。

1.3 动态内存分配

指针可以使用动态内存分配函数(如malloc、calloc、realloc)动态地分配内存,从而使得程序可以根据需要动态地管理内存,提高程序的灵活性。

  1. 指针的缺点

2.1 内存泄漏

指针在动态内存分配时需要手动释放,如果程序员忘记释放指针所指向的内存,则会导致内存泄漏,降低程序的效率。

2.2 悬挂指针

指针在动态内存分配之后,如果没有指向有效的内存地址,就会成为悬挂指针,这会导致程序出现未定义的行为,从而产生不可预测的结果。

2.3 指针越界

指针访问内存时需要确保不越界,否则会导致程序出现未定义的行为,从而产生不可预测的结果。

2.4 复杂性

指针的使用需要对内存有深入的了解,对于初学者来说难以理解,容易造成程序错误。

总之,指针在C语言中是一种强大的工具,它提供了直接访问内存的能力,可以提高程序的效率和灵活性。但是,指针也有其缺点,容易造成内存泄漏、悬挂指针等问题,需要程序员小心使用。

7.2 C语言指针的应用前景

C语言的指针是一种强大的概念,它提供了对内存中数据的直接访问能力,是C语言中最重要的特性之一。指针的应用前景非常广泛,下面让我们来看看一些主要的应用领域。

  1. 内存管理

指针在C语言中有很多与内存管理相关的应用,如动态内存分配、内存复制等。使用指针可以直接访问和操作内存中的数据,可以更加灵活地管理和利用内存,提高程序的效率和性能。

  1. 数据结构

指针在数据结构中是一个重要的概念,如链表、树等数据结构都使用指针来实现。指针可以将多个数据元素连接在一起,形成一个复杂的数据结构,实现灵活的数据存储和操作。

  1. 文件处理

指针在文件处理中也有很多应用,如文件的读写操作、记录指针的移动等。使用指针可以直接访问文件中的数据,提高文件读写的速度和效率。

  1. 系统编程

指针在系统编程中也有很多应用,如操作系统的进程管理、线程管理、内存管理等。指针可以直接访问系统中的数据结构和内存,实现系统级的操作和管理。

  1. 网络编程

指针在网络编程中也有很多应用,如套接字的创建、发送和接收数据等。使用指针可以直接访问数据缓冲区,实现高效的网络通信。

总之,指针在C语言中的应用前景非常广泛,涵盖了各个领域。指针可以提高程序的效率和性能,实现更为灵活的数据存储和操作,是C语言中最重要的特性之一。

相关文章:

指针:程序员的望远镜

指针&#xff1a;程序员的望远镜一、什么是指针1.1 指针的定义1.2 指针和普通变量的区别1.3 指针的作用1.4 指针的优点和缺点二、指针的基本操作2.1 取地址运算符"&"2.2 指针的声明与定义2.3 指针的初始化2.4 指针的解引用2.5 指针的赋值2.6 指针的运算2.7 指针的…...

【python实现学生选课系统】

一、要求&#xff1a; 选课系统 管理员&#xff1a; 创建老师&#xff1a;姓名、性别、年龄、资产 创建课程&#xff1a;课程名称、上课时间、课时费、关联老师 使用pickle保存在文件 学生&#xff1a; 学生&#xff1a;用户名、密码、性别、年龄、选课列表[]、上课记录{课程…...

备受青睐的4D毫米波成像雷达,何以助力高阶自动驾驶落地?

近日&#xff0c;海外媒体曝出特斯拉已向欧洲监管机构提交车辆变更申请&#xff0c;并猜测特斯拉最新的自动驾驶硬件HW4.0或将很快量产上车。据爆料&#xff0c;HW4.0最大的变化是马斯克放弃的毫米波雷达又加了回来&#xff0c;根据国外知名博主Greentheonly的拆解分析&#xf…...

3.20算法题(一) LeetCode 合并两个有序数组

题目链接&#xff1a;算法面试题汇总 - LeetBook - 力扣&#xff08;LeetCode&#xff09;全球极客挚爱的技术成长平台 题目描述&#xff1a;给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元…...

QT | 编写一个简单的上位机

QT | 编写一个简单的上位机 时间&#xff1a;2023-03-19 参考&#xff1a; 1.易懂 | 手把手教你编写你的第一个上位机 2.QT中修改窗口的标题和图标 3.图标下载 1.打开QT Creator 2.新建工程 Qt Creator 可以创建多种项目&#xff0c;在最左侧的列表框中单击“Application”&am…...

DirectX12(D3D12)基础教程(二十一)—— PBR:IBL 的数学原理(2/5)

目录3、IBL 数学原理3.1、基于微平面理论的 “Cook-Torrance” 模型回顾3.2、 ksk_sks​ 项与菲涅尔项等价消除3.3、拆分“漫反射项”和“镜面反射项”3、IBL 数学原理 接下来&#xff0c;就让我们正式进入整个 IBL 的数学原理的旅程。请注意&#xff0c;前方高能&#xff01; …...

嵌入式学习笔记——SysTick(系统滴答)

系统滴答前言SysTick概述SysTick是个啥SysTick结构框图1. 时钟选择2.计数器部分3.中断部分工作一个计数周期&#xff08;从重装载值减到0&#xff09;的最大延时时间工作流程SysTick寄存器1.控制和状态寄存器SysTick->CTRL2.重装载值寄存器SysTick->LOAD3.当前值寄存器Sy…...

Linux实操之服务管理

文章目录一、服务(service)管理介绍:service管理指令查看服务名服务的运行级别(runlevel):CentOS7后运行级别说明chkconfig指令介绍一、服务(service)管理介绍: 服务(service)本质就是进程&#xff0c;但是是运行在后台的&#xff0c;通常都会监听某个端口&#xff0c;等待其它…...

基于Java+SpringBoot+vue的毕业生信息招聘平台设计和实现【源码+论文+演示视频+包运行成功】

博主介绍&#xff1a;专注于Java技术领域和毕业项目实战 &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3fb; 不然下次找不到哟 Java项目精品实战案例&#xff08;200套&#xff09; 目录 一、效果演示 二、…...

智能生活垃圾检测与分类系统(UI界面+YOLOv5+训练数据集)

摘要&#xff1a;智能生活垃圾检测与分类系统用于日常生活垃圾的智能监测与分类&#xff0c;通过图片、视频和摄像头识别生活垃圾&#xff0c;对常见的可降解、纸板、玻璃、金属、纸质和塑料等类别垃圾进行检测和计数&#xff0c;以协助垃圾环保分类处理。本文详细介绍基于YOLO…...

建立农村污水处理设施已经成为了当务之急!

在现代社会中&#xff0c;随着城市化进程的加速和人口的增长&#xff0c;选择农村污水处理设备进行污水处理已经成为了一个非常重要的问题。虽然城市中的污水处理设施得到了很好的发展&#xff0c;但是农村地区的污水处理还存在很多问题。 在农村地区&#xff0c;由于缺乏污水…...

【Matlab算法】粒子群算法求解一维线性函数问题(附MATLAB代码)

MATLAB求解一维线性函数问题前言正文函数实现可视化处理可视化结果前言 一维线性函数&#xff0c;也称为一次函数&#xff0c;是指只有一个自变量xxx的函数&#xff0c;且函数表达式可以写成yaxbyaxbyaxb的形式&#xff0c;其中aaa和bbb是常数。具体来说&#xff0c;aaa称为斜…...

【JavaEE】Thread 类及常用方法

一、Thread 类Thread 类我们可以理解为是 java 用于管理线程的一个类&#xff0c;里面封装了操作系统提供的线程管理这一方面的 API &#xff08;Thread 是优化后的结果&#xff09;, Java 代码创建的每一个线程&#xff0c;可以理解为为 Thread 实例化的对象&#xff0c;Threa…...

C语言数据结构初阶(7)----队列

CSDN的uu们&#xff0c;大家好。这里是C语言数据结构的第七讲。 目标&#xff1a;前路坎坷&#xff0c;披荆斩棘&#xff0c;扶摇直上。 博客主页&#xff1a;姬如祎队列的基础知识队列&#xff08;queue&#xff09;是只允许在一端进行插入操作&#xff0c;而在另一端进行删除…...

代码随想录二刷 day01 | 704. 二分查找 27. 移除元素 977. 有序数组的平方

代码随想录二刷day01704. 二分查找27. 移除元素977. 有序数组的平方704. 二分查找 题目链接 做这种题最好现在纸上写一写&#xff0c;如果在大脑中想&#xff0c;可能一会就晕了。 二刷的时候发现了一个新的知识点 即&#xff1a; >>的作用 二分法第二种写法&#xff1a…...

Linux 终端、进程组、会话、守护进程

文章目录一、终端概念终端概念控制终端二、进程组概念进程组概述进程组相关 API会话会话概念会话相关 API创建会话注意事项守护进程守护进程介绍守护进程模型守护进程参考代码守护进程相关 API参考文章一、终端概念 终端概念 1、终端&#xff08;Terminal&#xff09; 终端是…...

你是否有潜质成为谷歌开发者专家?加入 GDE 成长计划,释放潜力!

谷歌开发者专家 (Google Developer Experts&#xff0c;GDE)&#xff0c;又称谷歌开发者专家项目&#xff0c;是由一群经验丰富的技术专家、具有社交影响力的开发者和思想领袖组成的全球性社区。通过在各项活动演讲以及各个平台上发布优质内容来积极助力开发者、企业和技术社区…...

安全防御之防火墙篇(二)

目录 1.防火墙如何处理双通道协议&#xff1f; 2.防火墙如何处理NAT&#xff1f; 3.防火墙支持哪些NAT技术&#xff0c;主要应用的场景是什么&#xff1f; 4.当内网PC通过公网域名解析访问内网服务器的时候&#xff0c;会存在什么问题&#xff0c;如何解决&#xff1f;请详细…...

设计必备,5个png免抠素材网站,建议收藏

做设计、PPT都需要用到大量的免抠素材&#xff0c;职场中熟练使用Photoshop的人毕竟是少数&#xff0c;也很少有人愿意花费时间去精细抠图。那这5个免抠素材网站一定要收藏好&#xff0c;可以有效帮你节省时间&#xff0c;提高工作效率。 1、菜鸟图库 https://www.sucai999.co…...

shell 脚本expect

expect 是什么 expect - programmed dialogue with interactive programs&#xff08;与互动程序进行程序对话&#xff09; 定义脚本执行的 shell #!/usr/bin/expect -f 定义的是执行 expect 可执行文件的链接路径&#xff08;或真实路径&#xff09;&#xff0c;功能类似于bas…...

第十九天 Maven总结

目录 Maven 1. 前言 2. 概述 2.1 介绍 2.2 安装 3. IDEA集成Maven 3.1 集成Maven环境 3.2 创建Maven项目 3.3 Maven坐标详解 3.4 导入maven项目 4. 依赖管理 4.1 依赖配置 4.2 依赖传递 4.3 依赖范围 4.4 生命周期 4.5 插件 Maven 1. 前言 1). 什么是Maven? …...

ESP8266-NodeMCU开发板-------开发板介绍(1)

目录 认识ESP8266-NodeMCU开发板​编辑 GPIO编号与NodeMCU开发板引脚名的区别&#xff1a; ESP8266 GPIO编号与NodeMCU开发板引脚名的对应关系 可用引脚 电压电流限制 特殊引脚情况说明 上拉电阻/下拉电阻 模拟输入 通讯 认识ESP8266-NodeMCU开发板 初识NodeMCU开发板 (第1章-第…...

【测试开发篇3】软件测试的常用概念

目录 一、软件测试的生命周期(5个步骤) ①需求分析(两个角度) 用户角度&#xff1a; 开发人员的角度&#xff1a; ②测试计划 ③测试设计、测试开发 ④执行测试 ⑤测试评估 二、软件测试贯穿项目的整个生命周期的体现 需求分析阶段 计划阶段 设计阶段 编码阶段 …...

javaEE初阶 — JavaScript WebAPI

文章目录什么是 DOMDOM 树获取元素1. querySelector2. querySelectorAll事件1. 事件三要素2. 代码案例获取 / 修改元素内容1. innerHTML获取 / 修改元素属性获取 / 修改表单元素属性获取 / 修改样式属性1. 修改内联样式&#xff08;修改 style 属性的值&#xff09;2. 修改元素…...

UE实现地面动态交互效果

文章目录 1.实现目标2.实现过程2.1 SphereMask2.2 材质实现2.3 位置更新3.参考资料1.实现目标 基于SphereMask材质节点实现人物在地面一定范围内的颜色高亮效果。 2.实现过程 实现原理是首先通过,SphereMask材质节点更具计算输出Mask值,其中在球体半径内的输入1,在外部的则…...

如何用自己的数据训练YOLOv5

如何训练YOLOv5 1. Clone the YOLOv5 repository and install dependencies: git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txt2. 整理数据&#xff0c;使其适配YOLO训练 Step1&#xff1a;Organize your dataset in the fo…...

【基础算法】数组相关题目

系列综述&#xff1a; &#x1f49e;目的&#xff1a;本系列是个人整理为了秋招算法的&#xff0c;整理期间苛求每个知识点&#xff0c;平衡理解简易度与深入程度。 &#x1f970;来源&#xff1a;材料主要源于代码随想录进行的&#xff0c;每个算法代码参考leetcode高赞回答和…...

MatBox—基于PyQt快速入门matplotlib的教程库

MatBox—基于PyQt快速入门matplotlib的教程库 __ __ _ _ _ _ _ _ _______ _ _ _ | \/ | | | | | | | | |(_)| | |__ __| | | (_) | || \ / | __ _ |…...

go channel使用

go语言中有一句名言&#xff1a; 不要通过共享内存来通信&#xff0c;而应该通过通信来共享内存。 channel实现了协程间的互相通信。 目录 一、channel声明 二、向channel发送数据 三、从channel读取数据 1. i, ok : <-c 2. for i : range c&#xff08;常用&#xff09…...

5. QtDesignStudio中的3D场景

1. 说明&#xff1a; 三维渲染开发是Design Studio的重要功能&#xff0c;且操作方便&#xff0c;设计效率非常高&#xff0c;主要用到的控件是 View3D ,可以在3D窗口中用鼠标对模型直接进行旋转/移动/缩放等操作&#xff0c;也可以为模型设置各种动画&#xff0c;执行一系列的…...