首页 > 其他分享 >八、常用函数

八、常用函数

时间:2024-03-24 15:55:38浏览次数:24  
标签:常用 函数 int str1 char 内存 字符串 指针

本章专题脉络

image

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

image

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;
}

图示:

image

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访问一个不存在的变量,进而出错。

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

标签:常用,函数,int,str1,char,内存,字符串,指针
From: https://www.cnblogs.com/luzriu/p/18092532

相关文章

  • Vim - vimrc常用配置和插件的使用
    基础常用命令I#跳转到行首并进入编辑A#跳转到行尾并进入编辑j#跳转到下一行.#重复上一次的修改(比如给每一行最后加上";",可以按A将光标移动到行尾,输入;,然后按j跳转到下一行,在按.)C#删除当前光标位置到当前行最后的文本,并进入编辑模式s#删除光标后的一个字......
  • C++共享之道:用extern实现源文件变量与类成员函数的巧妙共享
    概述:在C++中,使用`extern`关键字可实现在源文件之间共享变量与类成员函数。通过声明变量或类在头文件中,再在一个源文件中定义,其他源文件通过`extern`引用,促使模块化、可维护的代码组织。在C++中,extern关键字可用于在源文件之间共享变量。它告诉编译器某个变量的声明在其他源文......
  • Java 学习路线:基础知识、数据类型、条件语句、函数、循环、异常处理、数据结构、面向
    Java基础什么是JavaJava是一种由SunMicrosystems于1995年首次发布的编程语言和计算平台。Java是一种通用的、基于类的、面向对象的编程语言,旨在减少实现依赖性。它是一个应用程序开发的计算平台。Java快速、安全、可靠,因此在笔记本电脑、数据中心、游戏机、科学超级计......
  • gcc编译步骤与常用参数
    1.gcc编译步骤与常用参数1.1.编译步骤源码hello.c只有寥寥几行代码#include<stdio.h>intmain(void){printf("hello\n");}执行-E预处理,得到hello.i,生成了很长的.i文件-S编译helloc.s,这一步是最重要的,得到的反汇编文件,可以看出很多问题:-c汇编得到hello.......
  • 数学建模常用代码
    一维插值步骤步骤:(1)输入已知数据,x,y(2)输入待插自变量的值x1代码:x=1:12;y=[589152529313022252724];x1=1:0.1:12;t=interp1(x,y,x1,'spline');% plot(x1,t,'r:') %作图xlabel('x'),ylabel('y')二维插值步骤步骤:(1)先输入二维数据的x,y坐标值(2)输入Z......
  • 损失函数与优化器:交叉熵损失Adam和学习率调整策略
    非常感谢您的委托,我将尽我所能撰写一篇专业而深入的技术博客文章。作为一位世界级的人工智能专家和计算机领域大师,我将以逻辑清晰、结构紧凑、简单易懂的专业技术语言,为您呈现这篇题为《损失函数与优化器:交叉熵损失、Adam和学习率调整策略》的技术博客。让我们开始吧!1......
  • 工作中常用到的Linux命令
    系统,用户信息操作相关命令查看主机ip地址ifconfig获取用户信息id修改用户密码passwd查看链接用户who创建新用户账号useradd删除用户账号userdel修改用户账号的属性usermod查看系统发行版本cat/proc/version说明适用于所有版本。示例[root@vps ~]# cat /pro......
  • 按功能划分的常用Java库
    日志打印使用slf4j作为日志门面API,常用的日志实现库为log4j和logback。<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.36</version></dependency><!--使用logback作为底层日志实现框架,需要在......
  • zip()函数
    开始学python啦,第一篇章就以zip()内置函数,这个函数是看生成验证码的时候,竟然可以通过zip实现并行遍历,觉得很神奇,这个这种遍历方式,java还真没有。pythonzip是一个非常有用的内建函数,它可以实现并行遍历,也可以将两个或多个列表,转换成一个元组或者字典。具体的通过示例来看下案例......
  • [C#] .NET8增加了Arm架构的多寄存器的查表函数(VectorTableLookup/VectorTableLookupEx
    作者:zyl910发现.NET8增加了Arm架构的多寄存器的查表函数(VectorTableLookup/VectorTableLookupExtension),这给编写SIMD向量化算法带来了方便。一、指令说明在学习Arm的AdvSimd(Neon)指令集时,发现它的Lookup(查表)功能,类似X86的Sse系列指令集中的字节Shuffle(换位。如_mm_shuffle_epi......