15 C语言常用函数

news2024/9/27 21:09:39

C语言常用函数

本次笔记参考哔站尚硅谷宋红康老师的C语言教程
C语言是一种广泛应用的编程语言,它提供了一系列的标准库函数,使得程序员能够更高效地编写程序。函数是C语言编程中的基础,通过它们,程序员可以构建出功能丰富的应用程序。掌握这些函数的使用方法对于C语言程序员来说是非常重要。

1、字符串相关函数

1.1 字符串的表示方式

C 语言没有单独的字符串类型,字符串被当作字符数组,即 char 类型的数组。表示方式如下:

方式1:

char str[] = "hello";

方式2:

char *str = "hello";

1.2 两种方式的区别

字符指针和字符数组,这两种声明字符串变量的写法基本是等价的,但是也有区别。

**区别1:**指针指向的字符串,在 C 语言内部被当作常量,不能修改字符串本身。

char* str = "hello!";
str[0] = 'z'; // 报错

如果使用数组声明字符串变量,就没有这个问题,可以修改数组的任意成员。

char str[] = "hello";
str[0] = 'z'; // 不报错

为什么字符串声明为指针时不能修改,声明为数组时就可以修改?

因为系统会将字符串的字面量保存在内存的常量区,这个区域是不允许用户修改的。声明为指针时,指针变量存储的值是一个指向常量区的内存地址,因此用户不能通过这个地址去修改常量区。但是,声明为数组时,编译器会给数组单独分配一段内存,字符串字面量会被编译器解释成字符数组,逐个字符写入这段新分配的内存之中,而这段新内存是允许修改的。

测试代码:

int main() {

    char str1[] = "hello"; //新开辟的空间,保存数组中的数据
    char str2[] = "hello"; //新开辟的空间,保存数组中的数据
    printf("%p\n",str1); //000000f4a93ff81a
    printf("%p\n",str2); //000000f4a93ff814

    char * str3 = "hello"; 
    char * str4 = "hello"; //与前一个str3的数据是共享的,存在于常量区
    printf("%p\n",str3); //00007ff6842ca004
    printf("%p\n",str4); //00007ff6842ca004
    
    return 0;
}

**区别2:**指针变量可以指向其它字符串。

char* s = "hello";
s = "world";

但是,字符数组变量不能指向另一个字符串。

char s[] = "hello";
s = "world"; // 报错

字符数组的数组名,总是指向初始化时的字符串地址,不能修改。所以,声明字符数组后,不能直接用字符串赋值。

char s[10];
s = "abc"; // 错误

为什么数组变量不能赋值为另一个数组?

因为数组变量所在的地址无法改变,或者说,编译器一旦为数组变量分配地址后,这个地址就绑定这个数组变量了,这种绑定关系是不变的,即不能用赋值运算符为它重新赋值。

想要重新赋值,必须使用 C 语言原生提供的 strcpy() 函数,通过字符串拷贝完成赋值。这样做以后,数组变量的地址还是不变的,即 strcpy() 只是在原地址写入新的字符串,而不是让数组变量指向新的地址。

char s[10];
strcpy(s, "abc");

【武汉科技大学2019研】下面各语句中,能正确进行字符串操作的语句是(  )。
A.char a[10]={‘A’,‘B’,‘C’,‘D’,‘\0’};
B.char a[10]; a="ABCDE";
C.char *p; *p="ABCDE";
D.char *s; scanf(“%s”, s);

【答案】A

【解析】B项中,字符数组的数组名指向数组的首元素地址,初始化后不可再被更改;CD两项中的字符指针在定义时均没有进行初始化,对其赋值是非法的,答案选A。

【北京航空航天大学2018研】对于以下C程序,其正确的是(  )。

#include<stdio.h>
int main(){  
char str1[]="Hello";  
char str2[]="Hello";  
if(str1==str2)    
  printf("Equal\n");  
else    
  printf("Unequal\n");  
return 0;
} 

A.Unequal
B.Equal
C.该程序无法通过编译
D.该程序运行时出错

【答案】A

【解析】首先该程序符合语法规则,因此不会编译时产生错误,其次字符数组str1和str2都为指针常量,将他们直接用关系运算符进行比较肯定是不相等的,但是它们所指的字符串是相等的,因此最后输出Unequal。

1.2 字符串常用函数

这里的字符串处理函数,都属于库函数。库函数并非C语言本身的组成部分,而是C语言编译系统为方便用户使用而提供的公共函数。不同的编译系统提供的函数数量和函数名、函数功能都不尽相同,使用时要小心,必要时查一下库函数手册

在使用字符串处理函数时,应当在程序文件的开头用#include <string.h>把 string.h 文件包含到本文件中。

strlen()

作用:返回字符串的字节长度,不包括末尾的空字符 ‘\0’ 。

函数原型:

// string.h
//参数是字符串变量,返回的是 size_t 类型的无符号整数,一般当做int类型处理。
size_t strlen(const char* s);

举例:

#include <stdio.h>
#include <string.h>    //需要加载此头文件

int main() {
    char str[10] = "China";
    printf("%d\n", strlen(str));     //5

    printf("%d\n", strlen("China")); //5
    
    //区别于sizeof(),是两个不同的概念
    printf("%d\n", sizeof(str));        //10
}
strcpy()

strcpy(字符数组1, 字符数组2) :字符串的复制,不能使用赋值运算符,直接将字符数组2的字符串复制到字符数组1中。

函数原型:

// string.h
strcpy(char dest[], const char source[])

使用此函数前,如何复制字符串呢?

一方面,下面两种字符串的复制写法,都是错的。因为数组的变量名是一个固定的地址,不能修改,使其指向另一个地址。

char str1[10];
char str2[10];
str1 = "abc"; // 报错
str2 = str1;  // 报错

另一方面,如果是字符指针,赋值运算符( = )只是将一个指针的地址复制给另一个指针,而不是复制字符串。

char* str1;
char* str2;

str1 = "abc";
str2 = str1;

此时,可以使用 strcpy(字符数组1,字符数组2) 函数,用于将一个字符串的内容复制到另一个字符串(前提:字符数组1的长度不小于字符数组2的长度,否则会溢出)。

举例:

#include <stdio.h>
#include <string.h>

int main() {
    char str1[10], str2[] = "China";
    strcpy(str1, str2);
    
    printf("%s\n",str1);

    //或
//    strcpy(str1, "China"); //参数2,也可以是一个字符串常量
//    printf("%s\n",str1);
    
    //进一步
    str1[0] = 'A';
    printf("%s\n",str1); //Ahina
    printf("%s\n",str2); //China
}

思考:如下程序的输出结果

int main() {

    char str1[10] = "abcde1234", str2[] = "China";
    strcpy(str1, str2);

    printf("%s\n",str1); //

    for(int i = 0;i < 10;i++){
        printf("%c",str1[i]); //
    }
    printf("\n");

    return 0;
}
China
China 2345

复制时将字符串2和其后的′\0′一起复制到字符数组1中,取代字符数组1中前面的字符,未被取代的字符保持原有内容。

strncpy()

作用:将字符串2中前面n个字符复制到字符数组1中去。

strncpy(str1, str2, n);

将str2中最前面n个字符复制到str1中,取代str1中原有的最前面n个字符。但复制的字符个数n不应多于str1中原有的字符(不包括′\0′)。

int main() {

    char s1[40] = "1234567890";
    char s2[12] = "helloworld";
    strncpy(s1, s2, 5);
//    s1[5] = '\0';  //测试这行代码添加的必要性
    printf("%s\n", s1); // 

    return 0;
}
strcat()

strcat(字符数组1, 字符数组2):把两个字符数组中的字符串连接起来,把字符串2接到字符串1的后面,结果放在字符数组1中,函数调用后得到一个函数值——字符数组1的地址。

函数原型:

char* strcat(char* s1, const char* s2);

说明:

1、字符数组1必须足够大,以便容纳连接后的新字符串。

2、连接前两个字符串的后面都有′\0′,连接时将字符串1后面的′\0′取消,只在新串最后保留′\0′。

#include <stdio.h>
#include <string.h>

int main() {
    char str1[30] = {"People′s Republic of "};
    char str2[] = {"China"};
    printf("%s\n", strcat(str1, str2)); //People′s Republic of China
    printf("%s\n", str1);  //People′s Republic of China

}
strncat()

作用:将字符串2中前面n个字符连接到字符数组1中去。

strncat(str1, str2, n);

strncat() 总是会在拼接结果的结尾,自动添加空字符′\0′ ,所以第三个参数的最大值,应该是 str1 的变量长度减去 str1 的字符串长度,再减去 1 。

#include <stdio.h>
#include <string.h>

int main() {

    char s1[10] = "Hello";
    char s2[8] = "World";
    strncat(s1, s2, 3);
    printf("%s\n", s1);  //HelloWor
    return 0;
}
strcmp()

函数原型:

int strcmp(const char* s1, const char* s2);

strcmp(字符串1, 字符串2):比较字符串1和字符串2。

字符串比较的规则是:将两个字符串自左至右逐个字符相比(按ASCII码值大小比较),直到出现不同的字符或遇到′\0′为止。

(1) 如全部字符相同,则认为两个字符串相等。返回值为0

(2) 若出现不相同的字符,如果返回值为正数,则字符串1大;反之,返回值为负数,则字符串2大。

#include <stdio.h>
#include <string.h>

int main() {
    char *str1 = "abxy";
    char *str2 = "abmn";
    printf("%d\n",strcmp(str1, str2));  // 1

    int compare1 = strcmp("China", "Korea");
    printf("%d\n",compare1);  // -1

    return 0;
}
strlwr()/strupr()

strlwr(字符串):将字符串中大写字母换成小写字母。

strupr(字符串):将字符串中小写字母换成大写字母。

#include <stdio.h>
#include <string.h>

int main() {

    char str[] = "HelloWorld";
    strlwr(str);
    puts(str); //helloworld

    strupr(str);
    puts(str); //HELLOWORLD
}

1.3 基本数据类型和字符串的转换

在程序开发中,我们经常需要将基本数据类型转成字符串类型(即 char数组 )。或者将字符串类型转成基本数据类型。

基本数据类型 -> 字符串

sprintf()函数可以将其他数据类型转换成字符串类型。此函数声明在stdio.h头文件中。

sprintf()和平时我们常用的printf()函数的功能相似,只是sprintf()函数输出到字符串中,而printf()函数输出到屏幕上。

#include <stdio.h>

int main() {
    char str1[20]; //字符数组,即字符串
    char str2[20];
    char str3[20];
    int a = 111, b = 222;
    char c = 'a';
    double d = 333.444;


    sprintf(str1, "%d %d", a, b);
    sprintf(str2, "%d%c", a, c);
    sprintf(str3, "%.5f", d);
    printf("str1=%s\n", str1); //111 222
    printf("str2=%s\n", str2); //111a
    printf("str3=%s\n", str3); //333.44400
    
    return 0;
}
字符串 -> 基本数据类型

调用头文件 <stdlib.h> 的函数atoi()atof() 即可。

#include <stdio.h>
#include <stdlib.h>

int main() {
    char str1[10] = "123456";
    char str2[4] = "111";
    char str3[10] = "12.67423";
    char str4[2] = "a";

    int i = atoi(str1);
    int j = atof(str1);
    short s = atoi(str2);
    double d = atof(str3);
    char c = str4[0];
    printf("i=%d,j=%d,s=%d,d=%lf,c=%c", i, j, s, d, c);

    return 0;
}

2、日期和时间相关函数

在编程中,程序员会经常使用到日期相关的函数,比如:统计某段代码执行花费的时间等等。头文件是 <time.h>

举例说明:

  • 返回一个值,即格林尼治时间1970年1月1日00:00:00到当前时刻的时长,时长单位是秒。
time_t time(time_t *t)
  • 获取当前时间,返回一个表示当地时间的字符串(当地时间是基于参数timer的)。
char *ctime(const time_t *timer)
  • 计算time1和time2之间相差的秒数(time1-time2)
double difftime(time_t time1, time_t time2)

举例:

#include <stdio.h>
#include <time.h> //该头文件中,声明日期和时间相关的函数

//  运行test函数,看看执行花费时间
void test() {
    int i = 0;
    int sum = 0;
    int j = 0;
    for (i = 0; i < 10000000; i++) {
        sum = 0;
        for (j = 0; j < 100; j++) {
            sum += j;
        }
    }
}

int main() {
    printf("程序启动...\n");

    time_t start_t;
    //先得到执行test前的时间
    time(&start_t); //获取当前时间

    test(); //执行test

    time_t end_t;
    //再得到执行test后的时间
    time(&end_t); //获取当前时间

    double diff_t; //存放时间差
    diff_t = difftime(end_t, start_t); //时间差,按秒 ent_t - start_t


    //然后得到两个时间差就是耗用的时间
    printf("%d\n",start_t); //1697026306
    printf("%d\n",end_t); //1697026308
    printf("执行test()函数 耗用了%.2f 秒\n", diff_t); //执行test()函数 耗用了2.00 秒

    //获取时间对应的字符串的表示
    char * startTimeStr = ctime(&start_t);
    printf("%s\n",startTimeStr); //Wed Oct 11 20:11:48 2023

    return 0;
}

3、数学运算相关的函数

math.h头文件定义了各种数学函数。在这个库中所有可用的功能都带有一个 double 类型的参数,且都返回 double 类型的结果。

  • double exp(double x) :返回 e 的 x 次幂的值。
  • double log(double x) :返回 x 的自然对数(基数为 e 的对数)
  • double pow(double x, double y) :返回 x 的 y 次幂。
  • double sqrt(double x) :返回 x 的平方根。
  • double fabs(double x) :返回 x 的绝对值。
#include <stdio.h>
#include <math.h>

int main() {
    double d1 = pow(2.0, 3.0);
    double d2 = sqrt(5.0);

    printf("d1=%.2f\n", d1); //d1=8.00
    printf("d2=%f\n", d2);   //d2=2.236068

    return 0;
}

4、内存管理相关函数

4.1 C程序的内存分配

C程序中,不同数据在内存中分配说明:

  1. 全局变量和静态局部变量——内存中的静态存储区/全局区

  2. 非静态的局部变量——内存中的动态存储区:stack 栈

  3. 临时使用的数据——建立动态内存分配区域,需要时随时开辟,不需要时及时释放——heap 堆

  4. 根据需要向系统申请所需大小的空间,由于未在声明部分定义其为变量或者数组,不能通过变量名或者数组名来引用这些数据,只能通过指针来引用)

4.2 void 指针(无类型指针)

  • 每一块内存都有地址,通过指针变量可以获取指定地址的内存块。

  • 指针变量必须有类型,否则编译器无法知道如何解读内存块保存的二进制数据。但是,向系统请求内存的时候,有时不确定会有什么样的数据写入内存,需要先获得内存块,稍后再确定写入的数据类型。

综上,为了满足这种需求,C 语言提供了一种不定类型的指针,叫做 void 指针。它只有内存块的地址信息没有类型信息,等到使用该块内存的时候,再向编译器补充说明,里面的数据类型是什么。

此外,由于void 指针等同于无类型指针(typeless pointer),可以指向任意类型的数据,但是不能解读数据。void 指针与其他所有类型指针之间是互相转换关系,任一类型的指针都可以转为 void 指针,而 void 指针也可以转为任一类型的指针。

int x = 10;
void *p = &x; // 整数指针转为 void 指针
int *q = p; // void 指针转为整数指针
char a = 'X';
void* p = &a;
printf("%c\n", *p); // 报错

由于不知道 void 指针指向什么类型的值,所以不能用 * 运算符取出它指向的值。

void 指针的重要之处在于,很多内存相关函数的返回值就是 void 指针。

4.3 内存动态分配函数

头文件 <stdlib.h>声明了四个关于内存动态分配的函数。所谓动态分配内存,就是按需分配,申请才能获得。

掌握:malloc()

函数原型:

void *malloc(unsigned int size);  //size的类型为无符号整型

作用:在内存的动态存储区(堆区)中分配一个长度为size连续空间。并将该空间的首地址作为函数值返回,即此函数是一个指针函数。

由于返回的指针的基类型为 void,应通过显式类型转换后才能存入其他基类型的指针变量,否则会有警告。如果分配不成功,返回空指针(NULL)。

举例1:

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

举例2:动态申请数组空间

int *p;
p = (int *)malloc(n * sizeof(int));
for (int i = 0; i < n; i++)
  p[i] = i * 5;

得到一个元素类型为int型,长度为n的数组。取元素方式与之前相同,如获取第2个元素:p[1]。

举例3:

struct node *p;
p = (struct node *) malloc(sizeof(struct node));  //(struct node*)为强制类型转换
typedef struct BTNode{
	int data;
	struct BTNode *lchild;
	struct BTNode *rchild;
}BTNode;

//声明二叉树结点方式1
BTNode bt1;
//声明二叉树结点方式2:需熟练掌握
BTNode *bt2;
bt2 = (BTNode*)malloc(sizeof(BTNode));

考研数据结构中所有类型结点的内存分配都可以用函数malloc()来完成,模式固定,容易记忆。

方式2中的BT是指针型变量,还可以指向其它节点。而方式1中的BT则不行。此外,调用结构体成员时,

//针对于方式1:结构体变量取成员,用"."
int x = bt1.data;

//针对于方式2:指向结构体的指针取成员,用"->"
int x = bt2->data;
int x = (*bt2).data;//以前的写法

关于返回值为NULL:

malloc() 分配内存有可能分配失败,这时返回常量 NULL。Null 的值为0,是一个无法读写的内存地址,可以理解成一个不指向任何地方的指针。它在包括 stdlib.h 等多个头文件里面都有定义,所以只要可以使用 malloc() ,就可以使用 NULL 。由于存在分配失败的可能,所以最好在使用 malloc() 之后检查一下,是否分配成功。

int* p = malloc(sizeof(int));
if (p == NULL) { // 内存分配失败
  
}
// 或
if (p != NULL) {
  //...
}

上面示例中,通过判断返回的指针 p 是否为 NULL ,确定 malloc() 是否分配成功。

了解:calloc()

函数原型:

void *calloc(unsigned int n,unsigned int size);

作用:在内存的**动态存储区(堆区)**中分配n个,单位长度为size的连续空间,这个空间一般比较大,总共占用n*size 个字节。并将该空间的首地址作为函数的返回值。如果函数没有成功执行,返回NULL。

calloc()函数适合为一维数组开辟动态存储空间,n为数组元素个数,每个元素长度为size。

举例:

int *p;
p = (int *)calloc(10,sizeof(int)); //开辟空间的同时,其内容初始化为零

//等同于
int* p;
p = (int *)malloc(10 * sizeof(int));
memset(p, 0, sizeof(int) * 10);

上面示例中, calloc() 相当于 malloc() + memset() 。

了解:realloc()

函数原型:

void *realloc(void* p, unsigned int size)

作用:重新分配malloc()或calloc()函数获得的动态空间大小,即调整大小的内存空间。将先前开辟的内存块的指针p指向的动态空间大小改变为size,单位字节。返回值是一个全新的地址(数据也会自动复制过去),也可能返回跟原来一样的地址。分配失败返回NULL。

  • realloc() 优先在原有内存块上进行缩减,尽量不移动数据,所以通常是返回原先的地址。
  • 如果新内存块小于原来的大小,则丢弃超出的部分;如果大于原来的大小,则不对新增的部分进行初始化(程序员可以自动调用 memset() )。

举例1:

int* b;
b = (int *)malloc(sizeof(int) * 10);
b = (int *)realloc(b, sizeof(int) * 2000);

指针 b 原来指向10个成员的整数数组,使用 realloc() 调整为2000个成员的数组。

举例2:动态栈入栈时,判断是否需要扩容

int push(SqStack &S, ElemType e) {
    if (S.top - S.bottom >= S.stacksize) {               //栈满,追加存储空间
        S.bottom = (ElemType *) realloc(S.bottom,(STACKINCREMENT + S.stacksize) * sizeof(ElemType));
        if (!S.bottom) //if(S.bottom == NULL)
            return FALSE;       //空间分配失败
        S.top = S.bottom + S.stacksize;
        S.stacksize += STACKINCREMENT;
    }
    *S.top = e;
    S.top++;     // 栈顶指针加1
    return TRUE;
}
掌握:free()

函数原型:

void free(void *p);

函数无返回值。p是最近一次调用malloc()或calloc()函数时的返回值。

作用:释放指针变量p所指向的内存空间,使这部分内存能重新被其它变量使用。否则这个内存块会一直占用到程序运行结束。

举例:

int *p;
p=(int *)malloc(sizeof(int));
    
//...各种操作...
    
free(p); //千万不要忘了使用free()释放内存!

注意:

1、指针 p 必须是经过动态分配函数 malloc 成功后返回的首地址。

2、分配的内存块一旦释放,就不应该再次操作已经释放的地址,也不应该再次使用 free() 对该地址释放第二次。

3、如果忘记调用free()函数,同时p所在的函数调用结束后p指针已经消失了,导致无法访问未回收的内存块,构成内存泄漏。

4.4 举例

举例:动态创建数组,输入5个学生的成绩,另外一个函数检测成绩低于60 分的,输出不合格的成绩。

#include <stdio.h>
#include <stdlib.h>

#define N 5

void check(int *ptr) {
    printf("\n不及格的成绩有: ");
    for (int i = 0; i < 5; i++) {
        if (ptr[i] < 60) {
            printf(" %d ", ptr[i]);
        }
    }
}

int main() {
    int *p;
    //动态创建数组
    p = (int *) malloc(N * sizeof(int));

    printf("请输入%d个成绩:\n",N);
    for (int i = 0; i < N; i++) {
        scanf("%d", p + i);
    }
    //检查不及格的学生
    check(p);
    
    free(p); //销毁 堆区 p 指向的空间

    return 0;
}

图示:

4.5 动态分配内存的基本原则

1)**避免分配大量的小内存块。**分配堆上的内存有一些系统开销,所以分配许多小的内存块比分配几个大内存块的系统开销大

2)**仅在需要时分配内存。**只要使用完堆上的内存块,就需要及时释放它,否则可能出现内存泄漏。

这里需要遵守原则:谁分配,谁释放。

3)**总是确保释放以分配的内存。**在编写分配内存的代码时,就要确定在代码的什么地方释放内存。

4.6 常见的内存错误及其对策

1)内存分配未成功,却使用了它

新手常犯这种错误,因为他们没有意识到内存分配会不成功。

常用解决办法是,在使用内存之前检查指针是否为NULL。比如,如果指针p是函数的参数,那么在函数的入口处应该用if(p==NULL)if(p!=NULL)进行防错处理。

2)内存分配虽然成功,但是尚未初始化就引用它

犯这种错误主要有两个起因:一是没有初始化的观念;二是误以为内存的缺省初值全为零,导致引用初值错误。

int * p = NULL;
p = (int*)malloc(sizeof(int));
if (p == NULL){/*...*/}
/*初始化为0*/
memset(p, 0, sizeof(int));

题外话,无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦。

3)内存分配成功并且已经初始化,但操作时提示内存越界

在使用数组时经常发生下标“+1”或者“-1”的操作,特别是在for循环语句中,循环次数很容易搞错,导致数组操作越界。

数组访问越界在运行时,它的表现是不定的,有时什么事也没有,程序一直运行(当然,某些错误结果已造成);有时,则是程序一下子崩溃。

4)忘记了释放内存,造成内存泄漏

含有这种错误的函数每被调用一次就丢失一块内存。刚开始时系统的内存充足,你看不到错误。终有一次程序突然死掉,系统出现提示:内存耗尽。

动态内存的申请与释放必须配对,程序中malloc()free()的使用次数一定要相同,否则肯定有错误。

5)未正确的释放内存,造成内存泄漏

程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存。此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。

#include <stdio.h>
#include <stdlib.h>

void getMemory(int *p) {
    p = (int *) malloc(sizeof(int));  // 在这里修改的是局部指针 p,不会影响 main 函数中的原始指针 ptr
    //....
}

int main() {
    int *ptr = NULL;
    getMemory(ptr);  // 将 ptr 的值传递给 getMemory,但是在函数内部修改的是 p,而不是 ptr
    printf("ptr = %d\n", *ptr);  // 这里的 *ptr 是未定义行为,因为 ptr 没有指向有效的内存
    free(ptr);  // 这里试图释放未分配的内存,会导致问题
}

在本例中,getMemory()中的p申请了新的内存,只是把 p所指的内存地址改变了,但是ptr丝毫未变。getMemory()中的p也始终没有进行内存的释放。事实上,因为没有用free释放内存,每执行一次getMemory()就会泄漏一块内存。

6)释放了内存却继续使用它

函数的return语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。

long *p;

void addr() {
    long k;
    k = 0;
    p = &k;
}
void port() {
    long i, j;
    j = 0;
    for (i = 0; i < 10; i++) {
        (*p)--;
        j++;
    }
}


int main() {
    addr();
    port();
}


由于addr函数中的变量k在函数返回后就已经不存在了,但是在全局变量p中却保存了它的地址。在下一个函数port中,试图通过全局指针p访问一个不存在的变量,进而出错。

在计算机系统,特别是嵌入式系统中,内存资源是非常有限的。尤其对于移动端开发者来说,硬件资源的限制使得其在程序设计中首要考虑的问题就是如何有效地管理内存资源

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1643864.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

牛客NC382 切割木头【中等 二分超找 Java/Go/C++】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/707d98cee255448c838c76918a702be0 核心 二分查找Java代码 import java.util.*;public class Solution {/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返回方法规定的值即可…

泰克示波器电流探头如何抓浪涌电流波形?

泰克示波器是一种常见的电子测量仪器&#xff0c;广泛应用于电子工程、通信工程、医疗设备等领域。它的主要功能是实时显示电信号的波形&#xff0c;从而帮助工程师和技术人员分析和调试电路。而在一些特定的应用场景中&#xff0c;例如电源、电机、电器设备等&#xff0c;我们…

神经网络之防止过拟合

今天我们来看一下神经网络中防止模型过拟合的方法 在机器学习和深度学习中&#xff0c;过拟合是指模型在训练数据上表现得非常好&#xff0c;但在新的、未见过的数据上表现不佳的现象。这是因为模型过于复杂&#xff0c;以至于它学习了训练数据中的噪声和细节&#xff0c;而不…

基于EBAZ4205矿板的图像处理:12图像二值化(阈值可调)

基于EBAZ4205矿板的图像处理&#xff1a;12图像二值化(阈值可调) 先看效果 板卡拿回寝室了&#xff0c;明天晚上再补充实际运行效果 我的项目是可以通过按键调整二值化的阈值的&#xff0c;key1为阈值加1&#xff0c;key2为阈值减1&#xff0c;key3为阈值加10&#xff0c;ke…

vue初始化项目

打开终端输入vue create project-name 选择Manually select features回车&#xff0c;继续选择如下&#xff1a; 如果要使用pina就可以不选vuex&#xff0c;回车&#xff0c;选择如下&#xff1a; 按下图选即可

SX1301 1MHz、2A升压电流模式PWM转换器芯片IC

一般说明 S1301是直流-直流之间的a电流。安装Wμ电路内置0.25Ω功率MOSFET&#xff0c;使此稳压器高功率效率。内部补偿网络还可最大限度地减少多达6个外部元件数量。误差放大器的同相输入端接0.6V精密基准电压&#xff0c;内部具有软启动功能&#xff0c;可以减小浪涌电…

Hikyuu-SYS-趋势双均线交易策略实现

本篇中&#xff0c;我们将通过技术分析流派中经典的“趋势双均线策略”&#xff0c;向大家展现如何 Hikyuu 来测试自己的想法&#xff0c;并最终将它转化为策略&#xff01; 准备工作 下面的代码在 Jupyter Lab 中执行&#xff0c;和直接使用 .py 文件执行的区别主要在于 matp…

C语言--带环链表问题

继续学习 一、判断链表是否带环 141. 环形链表 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a;用快慢指针&#xff0c;快指针走两步&#xff0c;慢指针走一步&#xff0c;当慢指针走一半快指针进到环里 当慢指针进环&#xff0c;快指针已经在环中转了一会儿了 | |…

【论文笔记】Training language models to follow instructions with human feedback B部分

Training language models to follow instructions with human feedback B 部分 回顾一下第一代 GPT-1 &#xff1a; 设计思路是 “海量无标记文本进行无监督预训练少量有标签文本有监督微调” 范式&#xff1b;模型架构是基于 Transformer 的叠加解码器&#xff08;掩码自注意…

C语言二叉树代码实现

声明&#xff1a;著作权归作者所有。商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处。 采用二叉链表作为存储结构&#xff0c;完成二叉树的建立&#xff0c;先序、中序和后序遍历的操作&#xff0c;求所有叶子及结点总数的操作等。 #include <stdio.h> #i…

ctfshow 框架复现

文章目录 web 466web 467web 468web469web 470web 471web 472web 473web 474web 475web 476 web 466 Laravel5.4版本 &#xff0c;提交数据需要base64编码 代码审计学习—Laravel5.4 - 先知社区 (aliyun.com) 用第二条链子 反序列化格式 /admin/序列化串base64<?php na…

牛客NC383 主持人调度(一)【简单 排序 Java/Go/C++】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/e160b104354649b69600803184094adb 思路 直接看代码&#xff0c;不难Java代码 import java.util.*;public class Solution {/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返…

用python画一个正八边形

1 问题 使用turtle库的turtle.fd()函数和turtle.seth()函数绘制一个边长100的正八边形。 2 方法 1、利用for循环解决如何画出图形中相同的八条边的问题。 2、再利用turtle.fd()函数和turtle.seth()函数画出完整的图形。 代码清单 1 import turtleturtle.pensize(2)d0for i in r…

第77天:WAF 攻防-权限控制代码免杀异或运算变量覆盖混淆加密传参

目录 案例一&#xff1a; 代码-脚本后门免杀变异-覆盖&传参 传参 变量覆盖 案例二&#xff1a;代码-脚本后门免杀变异-异或&加密 加密 异或 案例三&#xff1a;免杀脚本生成代码 php免杀脚本 案例一&#xff1a; 代码-脚本后门免杀变异-覆盖&传参 传参 正…

【C++程序员的自我修炼】string 库中常见用法(二)

制芰荷以为衣兮 集芙蓉以为裳 不吾知其亦已兮 苟余情其信芳 目录 字符串的头部插入insert <1>头部插入一个字符串&#xff1a; <2>头部插入一个字符&#xff1a; <3>迭代器的插入 总结&#xff1a; 字符串的头部删除 erase <1>头部插入删除一个字符&a…

Spring入门及注解开发

1 引言 自定义注解可以用来为代码添加元数据信息,简化配置,提高代码的可读性和可维护性。通过自定义注解,可以实现自定义的业务逻辑、约束条件、配置参数等功能。在Spring中,自定义注解常用于标记组件、配置依赖注入、AOP切面等。 自定义注解可以添加元数据信息,低代码框…

使用sherpa-onnx给文字添加标点符号

上篇文章https://blog.csdn.net/AWNUXCVBN/article/details/138372795 识别出的文字都是没有标点符号的&#xff0c;虽然可以添加整句断行&#xff0c;但总觉得怪怪的…… 于是看了一下&#xff0c;ncnn没有添加标点符号的功能&#xff0c;翻了一下onnx有示例&#xff0c;但没有…

【测试报告】网页聊天室

⭐ 作者&#xff1a;Jwenen &#x1f331; 作者主页&#xff1a;Jwenen的个人主页 &#x1f496; 持续更文&#xff0c;关注博主少走弯路&#xff0c;谢谢大家支持 &#x1f496; 测试报告 1. 编写目的2. 项目介绍3. 功能测试用例4. 自动化测试源码 1. 编写目的 本报告为网页聊…

【华为】AC直连二层组网隧道转发实验配置

【华为】AC直连二层组网隧道转发实验配置 实验需求拓扑配置AC数据规划表 AC的配置顺序AC1基本配置(二层通信)AP上线VAP组关联--WLAN业务流量 LSW1AR1STA获取AP的业务流量 配置文档 实验需求 AC组网方式&#xff1a;直连二层组网。 业务数据转发方式&#xff1a;隧道转发。 DHC…

Odoo14修改登录界面,实现炫酷粒子效果

目录 原登录界面 最终效果 实现步骤 插件下载 原登录界面 最终效果 实现步骤 1 odoo创建插件web_login 2 在static目录下编写css和js文件 login.css代码 html, body {position:fixed;top:0px;left:0px;height:100%;width:100%;/*Fallback if gradeints dont work */b…