C语言指针

指针

指针概念

什么是指针

在C语言中,指针是一种特殊类型的变量,它存储了一个内存地址。这个地址指向存储在计算机内存中的数据的位置。指针允许我们直接访问和操作内存中的数据,而不需要复制数据本身。

内存地址

我们知道指针存储了一个内存地址,那么到底什么是内存地址呢?

想要理解什么是内存地址,我们需要先理解一个变量的存储过程和原理

int a = 5;

我们知道,这句话声明了一个整型变量a然后初始化为5. 那么在计算机中,这个过程是如何实现的呢?我们后续需要变量a的时候,计算机又是怎么重新过去这个变量a的呢?

实际上,这一句话在计算中是完成了两个操作:

  1. int a;

    我们知道,C语言中整型变量的大小是4 bytes,因此,计算机首先要做的,就是从栈中定义一个变量a,然后在内存中开辟一个4 bytes的空间,然后让a指向这个空间. 也就是说,从现在开始,这个4 bytes的空间,就是变量a 的了.

  2. ``a = 5`

​ 计算机已经给变量a开辟了空间,但是只有空间是没用的,我们声明变量的意义是为了存储数据. 而我们知道计算机中所有数据的存储方式都是二进制,因此我们将5转换为二进制,即101,然后将这个数据,放到变量a的空间中.

到此位置,int a = 5;这个语句就在计算机中完成了,那么什么是内存地址呢? 其实就是我们为变量a开辟的空间的一个索引.

计算机的内存可以看作是一系列的存储单元,每个存储单元都有一个唯一的地址。这些地址通常是连续的,从0开始递增。每个存储单元都可以存储一定数量的数据,具体取决于计算机的架构和操作系统。就像我们从地图上面根据经纬度找到任何一个地方一样,计算机可以通过内存地址找到内存中的任何一个数据.

我们可以通过取址操作符(&)更加直观的去看到内存地址:

int main()
{
	int in = 10;
    printf("%X\n",&in);
    printf("%p\n",&in);
    printf("%d\n",&in);
    return 0;
}

在这里插入图片描述

在printf中,%X表示大写的十六进制输出,%p表示十六进制的内存地址输出,%d表示整数输出.

虽然%x和%p都是表示十六进制,但是他们之间还是有区别的,%p输出是一个内存地址,这个内存地址的大小取决于计算机本身,如果内存地址本身比较小,那么输出的高位会根据需要补0,而%x则不会.

可以看到,我们通过取址操作符,得到了整数in的内存地址,为0x000000000061FE1C

指针的声明和初始化

声明

在C语言中,要声明一个指针变量,你需要指定指针所指向的数据类型。通常,指针声明的语法如下:

datatype *pointer_name;

其中,datatype是指针所指向的数据类型,pointer_name是指针变量的名称。例如,要声明一个指向整数的指针,你可以这样写:

int *ptr;

这个声明告诉编译器ptr是一个指向整数的指针变量

指针类型变量的意义

和其他变量一样,指针变量在声明的时候也要规定类型,那么指针变量的类型有什么意义呢?和其他变量一样,控制着指针变量的大小吗?

和查看其他变量大小的方法类似,我们也可以通过程序进行查看:

int main()
{
	printf("%d\n",sizeof(int*));
    printf("%d\n",sizeof(char*));
    printf("%d\n",sizeof(double*));
    printf("%d\n",sizeof(float*));
    printf("%d\n",sizeof(long*));
    printf("%d\n",sizeof(short*));
    printf("%d\n",sizeof(long long*));
    printf("%d\n",sizeof(unsigned int*));
    printf("%d\n",sizeof(unsigned long*));
    printf("%d\n",sizeof(unsigned short*));
    printf("%d\n",sizeof(unsigned long long*));
    printf("%d\n",sizeof(void*));
    return 0;
}
//输出为:
//8
//8
//8
//8
//8
//8
//8
//8
//8
//8
//8
//8

可以看到,这些所有指针变量的大小和指针变量的类型并没有关系,都是8 bytes. 那么这些类型有什么作用呢?

  1. 指针解引用

    #include <stdio.h>
    int main()
    {
    	int n = 0x11223344;
    	int* p1 = &n;
    	*p1 = 0;
        printf("%x\n",n);
    	return 0;
    }
    //输出为:
    // 0
    
    #include <stdio.h>
    int main()
    {
    	int n = 0x11223344;
    	char* p2 = (char *) & n;
    	*p2 = 0;
        printf("%x\n",n);
    	return 0;
    }
    //输出为:
    // 11223300
    

    可以看到,上面两段代码中,只有指针变量的类型不同. 但是第一段代码中,n的四个字节全部都被变成了0,而第二段代码中,只有第一个字节的数据变为了0;

    结论:指针类型决定了对指针解引用的时候有多大权限(一次可以操作几个字节)。

    比如:char的指针解引用只能访问一个字节,而int的指针解引用就可以访问4个字节。

  2. 指针运算

    #include <stdio.h>
    int main()
    {
    	int n = 10;
    	char* pc = (char*)&n;
    	int* pi = &n;
    	printf("&n   = %p\n", &n);
    	printf("pc   = %p\n", pc);
    	printf("pc+1 = %p\n", pc+1);
    	printf("pi   = %p\n", pi);
    	printf("pi+1 = %p\n", pi+1);
    }
    //输出为:
    //&n   = 000000000061FE0C
    //pc   = 000000000061FE0C
    //pc+1 = 000000000061FE0D
    //pi   = 000000000061FE0C
    //pi+1 = 000000000061FE10
    

    可以看到,char*类型的指针,+1的时候只会增加一个一个字节. 而int*类型的指针,+1的时候则会增加4个字节

    结论:指针类型决定了指针向前或者向后走一步有多大(距离)。

  3. void 指针*

    void* 类型,无具体类型的指针(泛型指针),void* 类型的指针大部分使用在函数参数的部分,用来接收不同类型数据的地址。

    但是void* 类型的指针不能直接进行指针的±整数和解引用运算。

初始化

和所有的变量类似,我们在声明之后,需要给指针变量赋初始值,通常情况下有以下这几种方式:

  1. 某个已存在变量的地址

​ 我们可以使用取址运算符(&)获取变量的内存地址,再将其赋值给指针变量

int x = 10;
int *ptr = &x; // ptr存储了x的内存地址
  1. 空指针

NULL是一个特殊的指针值,表示空指针,不指向任何有效的内存地址。这在动态分配内存时很有用。

int *ptr = NULL; // 将指针 ptr 初始化为空指针
  1. 动态分配的内存地址

​ 你可以使用 malloc()calloc() 函数动态分配内存,并将指针初始化为分配内存的地址。这在需要动态管理内存时非常有用。

int *ptr = (int *)malloc(sizeof(int)); // 分配一个整数大小的内存空间并将其地址赋给指针 ptr
  1. 指针常量

​ 也可以直接将一个指针常量赋值给指针变量

int arr[] = {1, 2, 3};
int *ptr = arr; // arr是一个指向数组首元素的指针常量

指针的解引用

上面说到,指针的目的其实就是允许我们能够直接访问和操作内存中的数据,而不需要复制数据本身. 我们已经知道如何声明和初始化指针了,那么如何直接访问和操作内存中的数据呢?这就涉及到指针的另一个操作,解引用.

指针的解引用是指通过指针访问其所指向的内存地址中存储的值。在C语言中,我们可以使用解引用操作符 * 来实现这一操作。

int x = 10;
int *ptr = &x; // 将指针 ptr 初始化为变量 x 的地址

int value = *ptr; // 使用解引用操作符 * 访问 ptr 指向的地址中的值,并将其赋给变量 value

在这个示例中,*ptr 表示我们要访问指针 ptr 所指向的内存地址中存储的值。这个值将会被赋给变量 value

但是解引用操作符并不只能访问内存地址中的数据,正如上面所说的,还可以对数据进行操作

修改所指向的值
int main()
{
	int a = 5;
    int* p = &a;
    *p = 10;
    printf("%d\n", a);
    return 0;
}
//输出结果为:10

可以看到,我们虽然没有使用a = 10这样子的语句去改变a的值,但是由于我们通过指针直接操作内存地址上的数据,因此直接将a的数据更改了.

需要注意的是,在解引用一个指针之前,必须确保该指针指向了一个有效的内存地址。如果解引用了一个空指针(值为NULL)或者一个非法的内存地址,会导致程序崩溃或产生未定义的行为。

在了解了指针的概念后,我们现在开始研究一下,指针的作用有哪些?

指针运算(一)

首先观察下面这段代码并且思考:输出是什么?解释一下这段代码的含义。

int x = 5;
int *p = &x;
int **q = &p;
printf("%d %d %d\n", x, *p, **q);

学习了指针概念后,这些问题就比较简单了

输出为:5 5 5

而这段代码的含义为:

  • p是一个指向整型变量x的指针
  • q是一个指向p的指针(指向指针的指针)
  • x的值是5
  • *p解引用得到x的值,即5
  • **q先解引用得到p的值,再解引用得到p所指向的x的值,也是5

这些只是指针最简单的一些运算,等学习了后面的知识后,还会介绍指针的一些其他的运算.

指针和数组

在研究指针和数组之前,先看下面这段代码,并思考:输出是什么?为什么?

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

输出的结果为:1 4

不管你是否知道结果,我现在都来告诉你为什么.

在C语言中,数组名本身其实就是一个指针常量,它指向数组的第一个元素的内存地址. 因此,在上面的代码中我们能够直接通过数组名给指针变量赋值, 并且通过使用指针来访问和操作数组元素.

因此,实际上,我们还可以通过解引用操作符对数组名进行操作.

int main(void){
    int arr[] = {1, 2, 3, 4, 5};
    int *ptr = arr;
    printf("%d\n",*arr);
    printf("%p\n",arr);
}

//输出为:
// 1
// 000000000061FE00

可以看到,通过解引用,输出的是数组的第一个元素,并且arr也可以通过%p直接输出地址.

因此,我们将数组名直接赋值给指针,就是将数组第一个元素的内存地址存储到了指针中,然后指针和整数之间的加减法在之前也是有所介绍,这个和指针变量的类型有关,加一就是内存地址往后增加一个变量类型的大小. 而数组和指针的类型都是整数,并且数组内的元素都是连续的,所以当进行ptr+3的时候,就是从数组的第一个元素的内存地址再跳三个内存地址,也就是到了第四个元素的内存地址中,此时再通过解引用操作符,得到的就是第四个元素的值了.

因此之前代码的输出才是1 4.

和其他变量一样,既然我们能够通过指针访问数组的元素,那么也就能够通过指针修改数组的元素.

int main(void){
    int arr[] = {1, 2, 3, 4, 5};
    int *ptr = arr;
    *(ptr+2) = 10;
    printf("%d\n",arr[2]);
}
//输出为:
// 10
// arr此时为 [1,2,10,4,5] 

需要注意的是,虽然数组名是一个指针常量,但是我们不能对它进行修改操作,如arr++arr = ptr这样的语句是非法的。数组名只是一个常量,我们只能对它进行读操作,而不能写操作。

指针和字符串

虽然字符串本质上就是一个字符数组。但是由于字符串在C语言中的重要性,我将它单独列出来进行分析.

先观察下面这段代码并思考:输出是什么?为什么?

char str[] = "Hello";
char *ptr = str;
printf("%c\n", *(ptr + 4));

有了上面指针和数组的分析,这题就比较简单了,输出为:o

但是不要忘记了char str[ ] = "hello" 等效于char str[] = {'h','e','l','l','o','\0'}他的最后一位为\0.

指针和函数

想要理解指针和函数之间的关系,需要彻底了解C语言中函数传参的方式.

在了解这些之前,我们先观察下面的代码并且思考:哪个函数能够实现将两个数的值交换的目的,为什么?其他的函数为什么不能?

void swap1(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

void swap2(int a , int b){
    
	int temp = a;
    a = b;
    b = temp;
}
void swap3(int *a , int *b){
    
	int *temp = a;
    a = b;
    b = temp;
}

带着对于这个问题的疑问,我们来介绍C语言中函数的传参方式.

C语言中有哪些传参方式呢?

总的来说分为两种,值传递和地址传递. 我分别进行介绍

值传递

在值传递中,函数的参数是通过将其值复制给函数参数的局部变量来传递的。这意味着在函数内部对参数的修改不会影响到函数外部的变量。值传递是C语言中最常见的传参方式。

void add(int a ){
    a = a+1;
}

int main()
{
    int a = 10;
    add(a);
    printf("a = %d\n", a);
}
//输出为: 10

可见,虽然我们将a传入add函数中,并且通过a = a + 1希望将a的值加一,但是由于C语言中是值传递,我们所传递的只是变量a的值,并不是变量a本身. 因此在函数中的操作并不会改变函数外的值.

我们通过一种更加直观的方式来展示:

void add(int a ){
    printf("%p\n",&a);
    a = a+1;
}

int main()
{
    int a = 10;
    printf("%p\n",&a);
    add(a);
    printf("a = %d\n", a);
}
// 输出为:
// 000000000061FE1C
// 000000000061FDF0
// a = 10

我们将函数外和函数内的a的地址分别打印出来,可以看到,两个地址并不相同. 这是正常且必须的,因为这就是值传递的过程:

在我们调用函数的时候,程序为函数开辟一个空间来存储一些局部变量. 而我们传递给函数的参数的值也会在调用的时候赋值给这个空间中的一个参数的副本. 因此,不管我们在函数中如何改变这个参数值,都无法改变函数外面的变量的值. 并且当函数执行完毕后,程序为函数开辟的存储局部变量的空间也会被释放,参数的副本也会随之不见. 如果想要其中的一些值被保留下来,需要使用关键字static,这里就不多做赘述,可以查看我的另一篇博客: C语言static关键字

原理:

值传递的原理是基于C语言中的栈(stack)实现的。在函数调用时,实参的值被压入栈中,并且复制到函数的形参中。这样,每个函数调用都有自己的一份参数值的副本,它们在栈中被分配内存空间。当函数返回时,栈中的内存空间被释放,形参的值也随之被销毁。

地址传递(指针传递)

C语言中的地址传递(也称为指针传递)是指将变量的内存地址传递给函数,而不是变量的值本身。这使得函数能够直接访问并修改变量的值,而不需要复制整个变量。

void swap(int *x, int *y) {
    int temp = *x;
    *x = *y;
    *y = temp;
}

int main() {
    int a = 5, b = 10;
    printf("Before swap: a = %d, b = %d\n", a, b); // 输出5, 10
    swap(&a, &b); // 传递a和b的地址
    printf("After swap: a = %d, b = %d\n", a, b); // 输出10, 5
    return 0;
}
// 输出:
// Before swap: a = 5, b = 10
//After swap: a = 10, b = 5

可以看到,通过地址传递方式调用的函数成功的交换了两个数的值.

那么这个过程中是什么样的呢?我们来具体分析:

在上面的示例中,我们定义了一个swap函数,它接受两个整型指针xy作为参数。在main函数中,我们声明了两个整型变量ab,并分别赋值为510。然后我们调用swap函数,传递ab的内存地址作为实参。

当函数调用发生时,发生了以下过程:

  1. 程序为形参xy在栈内存中分配了两个临时空间,用于存储传递过来的实参&a&b的值,也就是ab的内存地址。
  2. swap函数内部,我们使用解引用运算符*访问了xy所指向的内存地址上存储的值,实现了ab值的交换。
  3. swap函数执行完毕后,临时空间被释放,但ab的值已经被成功交换。

那么是不是只要使用地址传递的方式传递参数就能够实现在函数内改变函数外部变量的效果呢?

void swap3(int *a , int *b){
    
	int *temp = a;
    a = b;
    b = temp;
}

int main() {
    int a = 5, b = 10;
    printf("Before swap: a = %d, b = %d\n", a, b); // 输出5, 10
    swap3(&a, &b); // 传递a和b的地址
    printf("After swap: a = %d, b = %d\n", a, b); // 输出10, 5
    return 0;
}
//输出为:
//Before swap: a = 5, b = 10
//After swap: a = 5, b = 10

可以看到,虽然我们定义函数的时候,也是通过地址传递方式传递的参数,但是并没有成功的交换a和b的值,这是为什么呢?

我们参照着上面函数调用时候发生的过程对这段代码进行分析:

  1. 程序为形参ab在栈内存中分配了两个临时空间,用于存储传递过来的实参&a&b的值,也就是ab的内存地址。
  2. swap函数内部,我们将&a的值传递给temp,然后再将&b传递给a,最后将temp的值传递给b

所以总的来说,我们函数中实现的,只是将实参ab的地址在函数内部进行了交换,并且在函数执行完成后临时空间中用来存储ab地址的空间被释放. 因此并没有成功的改变变量ab的值.

指针运算(二)

对指针有关的知识进行了更加深入的了解后,现在可以再来看看指针的其他运算.

指针和整数的加减

指针和整数的加减法大多数情况下,都是有关数组方面的计算

我们知道数组在内存中是连续存放的,只要知道第一个元素的地址,就可以找到后面所有元素的地址.

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
	int* p = arr[0];
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i)); // *(p+i)相当于arr[i]
	}
	return 0;
}
// 输出为: 1 2 3 4 5 6 7 8 9 0

指针p+i,表示指针向后移动i个单位(每个单位为指针p的类型所占的字节数),指向原先指向的元素后的第i个元素。若p指向arr[0],则p+i指向arr[i];

指针减指针

int main(void){
    char name[]  = "hello world";
    char* p = name;
    while(*p != '\0'){
        p++;
    }
    printf("%d",p-name);
}
//输出为:11

指针减指针的运算其实和上面指针的整数加减法有关,所得到的其实是一个整数.

这个整数代表的,就是两个指针之间,存在的元素个数.

指针的关系运算(比大小)

由于指针所存储的是内存地址,因此其实比较的也就是内存地址的大小.

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
	int* p = &arr[0];
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	while (p < arr + sz) //指针的大小比较
	{
		printf("%d ", *p);
		p++;
	}
	return 0;
}

其实上面三个指针运算最终可以统一为指针和整数的加减法. 彻底明白了指针和整数的加减法,后面两个其实就是相关概念的延申.

指针运算的笔试题

表达式含义
*p++ 或 *(p++)首先计算表达式的值为*p,然后p自增1
(*p)++首先计算表达式的值为*p,然后*p自增1
*++p 或 *(++p)首先p自增1,然后计算*p
++*p 或 ++(*p)首先*p自增1,然后计算表达式的值为(*p)

理解这四个其实很简单,就是++,*和()的位置不同.

const修饰指针

首先const是用来修饰变量,保证变量的值不能够被改变.

那么const修饰指针变量的时候会有什么情况出现呢?

  1. const放在*左边

    修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变

  2. const放在*右边

    修饰的是指针本身,保证了指针变量的内容不能被修改

int main(){
    int a = 10;
    int b = 20;
    const int* p  = &a; //  不能通过*p 更改a的值,但是可以直接更改a的值
    int*const q = &a;  // 不能更改q的值,但是可以通过*q更改a的值
    *p = 20;
    p = &b;
    q = &b;
    *q = 30;
}
//报错:
//hello.c: In function 'main':
//hello.c:7:8: error: assignment of read-only location '*p'
//     *p = 20;
//        ^
//hello.c:9:7: error: assignment of read-only variable 'q'
//     q = &b;
//       ^

动态内存分配

在介绍什么是动态内存分配之前,我们先看下面这段代码,并且思考:这段代码的作用是什么?解释一下mallocfree的用途。

int *arr = malloc(5 * sizeof(int));
if (arr == NULL) {
    printf("Memory allocation failed\n");
    return 1;
}
for (int i = 0; i < 5; i++) {
    arr[i] = i * 2;
}
free(arr);

带着对这个问题的疑问,我们继续介绍什么是动态内存分配

在C语言中,内存可以分为两种类型:静态内存和动态内存。

静态内存是指在程序编译时就已经分配好的内存空间,包括全局变量、静态变量和在函数内部定义的局部变量等。这些变量的内存空间在程序运行期间是固定不变的。

动态内存则是指在程序运行期间动态分配的内存空间。动态内存分配允许程序在运行时根据需要请求和释放内存空间,从而可以有效利用内存资源,提高程序的灵活性和可扩展性。

那我们既然已经有了静态内存,为什么还需要动态内存呢?这两者之间的区别是什么?

为什么需要动态内存

假设现在,我们需要一个数组用来存储10个整型的数据,那我们可以这样声明一个整型数据:

int arr[10];

但是如果我们需要一个数组用来存储整型的数据,但是并不知道数据的个数呢?我们应该如何声明?

如果只有静态内存,那么我们这个时候就需要大概猜测数据的规模,然后在猜测的规模上选择一个比他还大的数用来声明数组,比如说:

int arr[1000];

但是这样子声明的数组还是有缺点的,首先就是我们无法保证数据的个数一定小于1000,如果数据个数大于1000,那么这个数据就没办法存储所有的数据. 其次就是,由于我们在未知数据个数的情况下只能尽可能的选择大的数来声明数据,这样子就可能造成栈溢出(stack overflow). 还有就是如果数据很少的时候,那么这个数组的大部分空间都是空的,十分浪费内存. 因此,为了解决这些问题,C语言出现了动态内存.

动态内存和静态内存的区别

动态内存的空间是在堆(heap)中开辟的,而静态内存的空间是在栈(stack)中开辟的.

这是它与通过定义数组分配出来的空间最本质的区别.

在C语言中,动态内存分配主要通过以下四个函数实现:

  1. malloc函数
    malloc函数用于动态分配一块连续的内存空间。它的原型是void *malloc(size_t size);size参数指定需要分配的内存大小(以字节为单位)。如果分配成功,函数返回一个指向分配内存的指针;如果分配失败,函数返回NULL

    int *ptr = (int *)malloc(sizeof(int) * 10); // 分配10个整型变量的空间
    
  2. calloc函数
    calloc函数也用于动态分配内存,但它会自动将分配的内存空间初始化为0。它的原型是void *calloc(size_t nmemb, size_t size);nmemb参数指定需要分配的元素个数,size参数指定每个元素的大小。

    int *ptr = (int *)calloc(10, sizeof(int)); // 分配10个整型变量的空间,并初始化为0
    
  3. realloc函数
    realloc函数用于重新分配内存块的大小。它的原型是void *realloc(void *ptr, size_t size);ptr参数是需要重新分配的内存块的指针,size参数是新的内存块的大小。如果分配成功,函数返回一个指向新分配内存的指针;如果分配失败,原有内存保持不变,并返回NULL

    ptr = realloc(ptr, sizeof(int) * 20); // 将之前分配的内存块扩大为20个整型变量的空间
    
  4. free函数
    free函数用于释放之前使用malloccallocrealloc函数动态分配的内存空间。它的原型是void free(void *ptr);ptr参数是指向需要释放的内存块的指针。释放内存后,该内存块将返还给系统,供其他程序使用。

    free(ptr); // 释放之前分配的内存块
    

动态内存分配的优点是可以有效利用内存资源,避免内存浪费,并且可以根据需要动态调整内存大小。但同时也存在一些风险,如果使用不当,可能会导致内存泄漏、越界访问等问题。因此,在使用动态内存分配时,需要格外小心谨慎。

一般来说,动态内存分配的步骤如下:

  1. 使用malloccalloc函数动态分配所需的内存空间。
  2. 使用分配的内存进行相关操作。
  3. 操作完成后,使用free函数释放分配的内存空间。

正确使用动态内存分配可以提高程序的灵活性和可扩展性,是C语言编程中一个非常重要的概念和技术。掌握动态内存分配的原理和使用方法对于编写高质量、高效的C语言程序至关重要。

在C语言中,声明数组的时候必须是一个常数,因为C文件在编译的时候就需要确定为数组创建的空间,所以没有办法在运行的时候声明.

int main(){
    int a;
    scanf("%d",&a);
    int arr[a]; 
}

这种声明是错误的,因为在编译的时候,a并不是一个常量,因此程序不知道要给arr分配多少的空间. 这也就是为什么需要动态分配内存的原因.
oc(ptr, sizeof(int) * 20); // 将之前分配的内存块扩大为20个整型变量的空间


4. `free`函数
`free`函数用于释放之前使用`malloc`、`calloc`或`realloc`函数动态分配的内存空间。它的原型是`void free(void *ptr);`。`ptr`参数是指向需要释放的内存块的指针。释放内存后,该内存块将返还给系统,供其他程序使用。

```c
free(ptr); // 释放之前分配的内存块

动态内存分配的优点是可以有效利用内存资源,避免内存浪费,并且可以根据需要动态调整内存大小。但同时也存在一些风险,如果使用不当,可能会导致内存泄漏、越界访问等问题。因此,在使用动态内存分配时,需要格外小心谨慎。

一般来说,动态内存分配的步骤如下:

  1. 使用malloccalloc函数动态分配所需的内存空间。
  2. 使用分配的内存进行相关操作。
  3. 操作完成后,使用free函数释放分配的内存空间。

正确使用动态内存分配可以提高程序的灵活性和可扩展性,是C语言编程中一个非常重要的概念和技术。掌握动态内存分配的原理和使用方法对于编写高质量、高效的C语言程序至关重要。

在C语言中,声明数组的时候必须是一个常数,因为C文件在编译的时候就需要确定为数组创建的空间,所以没有办法在运行的时候声明.

int main(){
    int a;
    scanf("%d",&a);
    int arr[a]; 
}

这种声明是错误的,因为在编译的时候,a并不是一个常量,因此程序不知道要给arr分配多少的空间. 这也就是为什么需要动态分配内存的原因.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值