C语言基础——函数详解

目录

 函数的概述

1 函数的概念

2 函数的意义

 函数的定义和使用

1 函数的定义

2 函数的调用

2.1 在同一文件中函数定义后函数调用

2.2 在同一文件中函数定义前函数调用

2.3 调用其它文件中定义的函数

2.3.1 在函数调用文件中进行声明

2.3.2 在头文件中进行函数的声明

 函数的参数

1 函数传参

1.1 值传参

1.2 地址传参

1.3 全局变量传参

2 数组作为函数参数

3 主函数的参数

3.1 main函数的返回值

3.2 main函数的参数

 函数和指针

1 指针函数

2 函数指针

3 函数指针数组

4 函数指针作为函数的参数

 字符串处理函数

1 字符串

2 字符串处理函数

2.1 字符串长度计算——strlen

2.2 字符串拷贝——strcpy/strncpy

2.3 字符串的连接——strcat/strncat

2.4 字符串比较——strcmp/strncmp

2.5 字符串查找——strstr

3 字符串数据和数据类型数据转换处理函数

3.1 数字字符串数据转换为整型数据

3.2 基本数据的格式输出到字符数组空间

3.3 字符数组数据格式化到数据变量

4 字符串切割函数

知识点5 递归函数

 内存管理

1 内存管理的方式

1.1 静态内存管理

1.2 动态内存管理

2 动态内存管理实现

2.1 动态内存开辟和释放

2.2 野指针和内存泄漏

3 内存分别

 程序的编译过程


 函数的概述

1 函数的概念

所谓的函数,指的是具有特定功能的有序指令语句的集合,可以提供使用者调用以完成功能语句的执行。此时的功能语句块由设计者实现模块化编程设计,使用者通过函数的调用实现代码重用的效果。

函数的实质:是实现代码的模块化编程和代码重用的设计思想。

2 函数的意义

1) 函数的定义可以实现功能语句的模块化编程设计和代码重用;

2) 对于共用性的功能型语句通过函数的调用,简化程序设计的过程;

3) 简化项目的设计的框架或者逻辑;

4) 通过函数的模块化设计,以实现项目的分工协作。

 函数的定义和使用

1 函数的定义

存储类型 数据类型 函数名(形参列表)/* 称为函数头,花括号包含的部分称为函数体 */

{

函数体中的功能性有序指令集合;

return语句 ;

}

注意:

1. 存储类型:修饰整个函数的属性,只能使用关键字static

        1) 没有使用static关键字修饰,此时函数的作用域(或者链接属性)为整个程序域,也就是可以在当前程序中的任意位置访问;

        2) 有使用static关键字修饰,此时函数的作用域为文件域,也就是只能在当前定义函数的文件中访问;

2. 数据类型:修饰函数返回结果数据值的数据类型

        1) 如果函数不需要返回数据值,此时使用void数据类型表示;此时的return语句可以省略,也可以不省略但是只能使用(return ;)表示,不能跟返回结果;

        2) 如果函数需要返回数据值,使用返回的数据值的数据类型表示

                i. 返回实际数据值;

                ii. 返回值函数调用的状态;

                iii 返回值同时是数据和状态;

3. 函数名:提供给使用者访问有序执行指令集合的接口名称。

4. 形参列表:表示函数数据的输入和输出的参数

        1) 无参数,函数功能语句的执行不需要数据的输入和输出,此时使用void表示。

        2) 有参数:

                可以有一个或者多个形参构成

                每一个形参的组成:数据类型 形参变量名,需要注意形参变量为局部变量,在函数模块内有效;

                如果是多个形参,形参之间使用逗号分隔;

5. 函数体的部分:使用花括号{}所包含的有序指令语句的集合。

/* 定义无参无返回值的函数 */
void test1(void)
{return;        /* 此处的return语句可以省略;同时如果不省略的时候,不能接返回数据值 */
}/* 定义有参有返回值的函数 */
int add(int a, int b)    /* 形参变量作用域为函数域 */
{return a+b;
}

2 函数的调用

所谓函数的调用,指的是使用者根据函数名访问函数,在调用的同时会根据函数形参列表传递实际参数的过程。

2.1 在同一文件中函数定义后函数调用

函数定义和函数调用在同一个文件中,并且函数调用在函数定义之后,此时可以直接函数调用。

#include <stdio.h>
/* 函数的定义,在定义的同时也完成函数的声明过程 */
int add(int a, int b)
{return a+b;
}int main()
{int x = 3;int y = 4;int z;z = add(x, y);     /* 在同一个文件中,函数定义后实现函数的调用 */printf("z = %d\n", z); 
}

2.2 在同一文件中函数定义前函数调用

函数定义和函数调用在同一个文件中,函数的调用在函数定义之前,需要对函数进行声明之后在调用。

函数的声明:

1. 函数声明的目的

是告诉编译器,函数定义在其它地方已经完成,在当前文件中可以直接调用;

2. 函数声明的语法:

extern 数据类型 函数名(形参列表);

        1) extern 是对函数进行外部声明引用,可以省略;

        2) 形参列表中的形参变量名可以省略;

int sub(int a, int b);        /* 函数声明,可以等价于以下几种情况 */
#if 0
extern int sub(int a, int b);
extern int sub(int, int);
int sub(int, int);
#endifint main()
{int x = 3;int y = 4;int z;z = sub(x, y);    /* 函数的调用 */printf("z = %d\n", z); 
}
/* 函数定义 */
int sub(int a, int b)
{return a-b;
}

2.3 调用其它文件中定义的函数

函数调用之前,需要对其它文件中所定义的函数进行函数声明

2.3.1 在函数调用文件中进行声明

根据所需要调用的函数,选择函数进行声明

#include <stdio.h>
/* 定义无参无返回值函数 */
void test1(void)
{printf("test1\n");
}int add(int a, int b)
{return a+b;
}int sub(int a, int b)
{return a-b;
}

2. 函数调用的源文件:app.c

#include <stdio.h>
/* 函数声明:对需要调用的函数在函数调用之前进行函数声明 */
void test1(void);
extern int add(int a, int b); int main()
{test1();printf("%d\n", add(5,2));
}

注意:程序的编译需要对函数定义和函数调用的源文件进行编译

gcc app.c func.c/* 编译成功默认生成的可执行程序文件为a.out */

2.3.2 在头文件中进行函数的声明

所定义的函数很多,且函数调用频率很高,设计者使用头文件进行函数的声明;对于使用者只需要包含头文件后直接进行函数的调用。

1. 函数定义的源文件:func.c

#include <stdio.h>void test1(void)
{printf("test1\n");
}int add(int a, int b)
{return a+b;
}int sub(int a, int b)
{return a-b;
}

2. 函数声明的头文件:func.h

#ifndef _FUNC_H_
#define _FUNC_H_
/* 函数声明 */
void test1(void);
extern int add(int a, int b); 
extern int sub(int a, int b); #endif

头文件中注意:

         1) 防止头文件被重复展开标识符

#ifndef _FUNC_H_
#define _FUNC_H_/* 数据类型的定义,或者函数和变量的外部声明应用*/
#endif

        2) 在头文件中不能做数据类型变量的定义或者函数的定义,否则可能出现重复定义。

3. 函数调用的源文件:app.c

#include <stdio.h>    /* 头文件的包含:尖括号<>所包含的头文件是在系统环境变量路径中所能找到的头文件 */#include "func.h"     /* 头文件的包含:双引号""所包含的头文件,在系统环境变量路径下不能找到,一般为自定义的头文件 */int main()
{test1();printf("%d\n", add(5,2));
}

注意:程序的编译需要对函数定义和函数调用的源文件进行编译,不需要对函数声明的头文件进行编译

gcc app.c func.c/* 编译成功默认生成的可执行程序文件为a.out */

 函数的参数

1 函数传参

在函数定义的时候,可以给所定义的函数设置形式参数,在函数调用的时候,需要将相同数据类型的实际参数传递给形式参数,需要完成函数传参的问题。在C语言中函数的传参注意包含三种形式,分别为值传参、地址传参和全局变量传参。

1.1 值传参

所谓的值传参,指的是形参为数据类型变量,在函数调用的时候,将相同数据类型实参值传递给形参的过程。其实质将实参值赋值给形参变量值。

特定:

1) 值传参只能实现数据值的输入,不能实现数据值的输出;

2) 对于形参变量值的修改,不会影响实参数据值。

3) 对于函数的返回值,也是采用值传参实现。

4) 形参变量存储空间和实参变量存储空间之间无任何关联。

#include <stdio.h>void setVal(int val)
{printf("val = %d\n", val);val = 100;    /* 对形参变量val值的修改不会影响实参变量a的数据值 */printf("val -> %d\n", val);
}int main()
{int a = 1;setVal(a);    /* 函数的调用,将实参变量a的值赋值给形参变量val, 此时val和a存储空间无任务管理 */printf("a = %d\n", a); 
}

1.2 地址传参

所谓地址传参,指的是在形参为指针变量的时候,将实参数据的地址赋值给形参变量;此时形参指针变量便指向的空间为实参数据空间。

特定:

        1) 地址传参,可以实现数据的输入和输出。

        2) 通过形参地址的解引用访问的是实参,通过形参修改实参数据值;

        3) 形参为指针变量所指向空间为实参空间。

#include <stdio.h>
void Swap(int *p, int *q) 
{int temp;temp = *p;*p = *q;*q = temp;
}int main()
{int x = 3;  int y = 5;printf("%d: x = %d, y = %d\n", __LINE__, x, y);    /* x = 3, y = 5 */Swap(&x, &y);printf("%d: x = %d, y = %d\n", __LINE__, x, y);     /* x = 5, y = 3 */
}

注意:

地址传参的使用,在需要在函数内部去修改实参数据值的时候使用。

如果需要修改的是数据变量,则使用数据变量相同数据类型的一级指针作为形参;

如果需要修改数据是指针变量,则使用指针变量数据类型的指针作为形参。

1.3 全局变量传参

所谓的全局变量传参,不是真正意义上利用参数实现数据的传递,实质是利用全局变量的属性实现数据值的传递过程。由于全局变量的作用域为整个程序域,所以可以在不同的函数模块内访问到全局变量的数据值。从而实现不同模块之间数据传递的过程。

特定:

        1) 全局变量传参,实现简单。

        2) 如果在函数模块内访问全局变量,则此时的模块依赖于全局变量的存在,模块的兼容性和可移植性会更低。

        3) 在一般情况下,不建议使用。

#include <stdio.h>
int m = 4;
int n = 5;void SWAP()
{int temp;temp = m;m = n;n = temp;
}int main()
{printf("%d: m = %d, n = %d\n", __LINE__, m, n);SWAP();printf("%d: m = %d, n = %d\n", __LINE__, m, n);
}

2 数组作为函数参数

数组的访问不能整体访问,只能通过数组名和元素序号逐一元素访问;

数组名[下标];下标范围:[0, 数组元素个数)

对于数组参数的传递,也不能整体传参;

#include <stdio.h>extern void showArr(int a[10]);int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};showArr(arr);
}void showArr(int a[10])
{int i;for (i = 0; i < sizeof(a)/sizeof(a[0]); i++)printf("a[%d] = %d\n", i, a[i]);
}

注意:

程序的执行结果,不能将整个数组中的所有元素顺序输出,实际输出元素的个数:

        1) 在64位操作系统中输出为:arr[0] 和 arr[1] 的结果。

        2) 在32位操作系统中输出为:arr[0]的结果。

原因:

数组作为形参的时候,不在表示为数组;实质表示的数组元素类型的指针变量,所接受的为所传递数组首元素地址。

在数组传参的时候,需要传递数组首元素地址和数组元素的个数

#include <stdio.h>extern void show_arr(int p[], int nmember);    /* 数组作为形参表示数组元素类型的指针,等价于void show_arr(int *p, int nmember); */int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};                 show_arr(arr, sizeof(arr)/sizeof(arr[0]));
}void showArr(int a[10])
{int i;for (i = 0; i < sizeof(a)/sizeof(a[0]); i++)printf("a[%d] = %d\n", i, a[i]);
}void show_arr(int p[], int nmember)
{int i;for (i = 0; i < nmember; i++)printf("p[%d] = %d\n", i, p[i]);
}

3 主函数的参数

在应用程序设计的时候,C语言程序的入口函数为main函数,其参数和返回值可以有多种表示形式;

3.1 main函数的返回值

1. 主函数无返回值main函数返回值数据类型使用void标识
void main()
{
}
2. main函数有返回值main函数的返回值数据类型可以是任意数据类型,还可以是指针。需要根据需求设计

3.2 main函数的参数

1. main函数无参数情况

此时main函数的参数列表需要使用void表示

int main(void)
{
}

2. main函数有参数,在函数内不能访问

int main()    /* 此时的main函数的参数列表为可变参数列表,可以传递任意数据类型的任意个参数 */
{
}

3. main函数可以通过shell终端传递任意个字符串数据

int main(int argc, char *argv[])
{int i;printf("argc = %d\n", argc);for (i = 0; i < argc; i++)printf("argv[%d] : %s\n", i, argv[i]);return 0;     
}

 函数和指针

1 指针函数

所谓的指针函数,实质是函数;函数返回值数据类型为指针的函数称为函数指针。

一般用于字符串操作函数和内存管理的函数,以便于实现内存的链式存储。

定义语法:

存储类型 数据类型 * 函数名(形参列表) 
{
}

和普通函数的区别:

唯一的区别在于指针函数返回值数据类型为指针,其余没有任何区别。

#include <stdio.h>char * my_strcpy(char *dest, const char *src)
{char *p = dest;const char *q = src;while(*q != '\0') {*p = *q; p++;q++;}return dest;
}int main()
{char *str = "hello";char buf[16];
#if 0my_strcpy(buf, str);printf("buf : %s\n", buf);
#elseprintf("%s\n", my_strcpy(buf, str));    /* 指针函数的返回值作为其它函数调用的参数,以实现数据的链式存储表示 */
#endif
}

2 函数指针

所谓的函数指针,实质是指针,表示为指向函数的指针称为函数指针。

所定义的任意的函数,都需要在内存中存储,而存储空间的起始地址可以定义函数指针存储。

函数指针定义语法:

数据类型 (*函数指针变量名)(形参列表);

函数指针初始值设置:

函数指针在设置值的时候,只能将函数的形参和返回值数据类型均一致的函数地址进行赋值。

#include <stdio.h>
/* 函数定义:* 此时函数名add有两层含义:* 1) 表示函数中有序指令集合名称,也就是所谓的函数名称* 2) 表示函数有序指令集合存储地址,也就是所谓的函数指针;*/ 
int add(int a, int b)
{return a+b;
}int sub(int a, int b){return a-b; }int mul(int a, int b)
{return a*b;
}
int main()
{int x = 5;int y = 3;int z;int (*p)(int, int);    /* 定义函数指针 */z = add(x, y); printf("z = %d\n", z); p = &add;                        /* 设置函数指针变量p的值,值为add函数的地址 */printf("%d\n", (*p)(x, y));      p = sub;                         /* 函数名也是函数地址,将sub函数的地址sub赋值给函数指针变量p */printf("%d\n", p(x, y));          p = mul;                         /* 函数名也是函数地址,将mul函数的地址mul赋值给函数指针变量p */printf("%d\n", p(x, y));         /* 相同语句会根据指针指向不同,实现不同的功能。指向是在程序执行过程中决定 */
}

注意:

函数之间访问和函数指针访问函数的区别:

1. 相同点:都可以实现函数的访问

2. 不同点:

        a) 函数直接访问,在程序编译过程中根据函数名决定所访问的函数;

        b) 函数指针访问,在程序指向过程中根据函数指针所指向的函数决定所访问函数功能;

3 函数指针数组

所谓的函数指针数组,实质是数组,数组中的每一个元素为函数指针。

定义的语法:

        数据类型 (* 函数指针数组名[常量表达式])(形参列表);

        常量表达式:表示数组元素的个数;

        函数指针数组名:表示数组集合变量名

        形参列表:表示数组元素所表示函数指针所指向函数的形参列表;

        数据类型:表示数组元素所表示函数指针所指向函数返回值的数据类型;

#include <stdio.h>int add(int a, int b)
{return a+b;
}int sub(int a, int b)
{return a-b;
}int mul(int a, int b)
{return a*b;
}typedef int (*FUNC_P) (int,int);        /* 返回值为int,参数为(int, int)的函数指针取别名为FUNC_PFUNC_P */int main()
{int x = 5;int y = 3;int i;
#if 0/* 定义函数指针数组: */int (*arr[3])(int, int) = {add, sub, mul};
#elseFUNC_P arr[3] = {add, sub, mul};
#endiffor (i = 0; i < sizeof(arr)/sizeof(arr[0]); i++) {printf("%d\n", arr[i](x, y));}
}

4 函数指针作为函数的参数

        在通常情况,函数调用过程是:使用者调用设计者所设计的函数完成某功能,在实际应用过程中可能存在设计者不确定功能实现的具体方法,需要由设计者实现,但是设计者不具备该功能实现的机制。

        此时设计者在设计函数模块的时候,将函数的参数设置为函数指针,由设计者定义回调函数提供给设计者进行调用。

求创建回调函数,并将函数地址提供给设计者,设计者内部通过回调函数地址访问回调函数,以实现具体功能 */

#include <stdio.h>/* 设计者设计运算器函数接口:可以实现两个整型数据的运算功能,但是具体运算实现不确定 */
int test(int(*p)(int, int), int a, int b)
{return p(a, b); 
}/* 使用根据自己需求创建回调函数,并将函数地址提供给设计者,设计者内部通过回调函数地址访问回调函数,以实现具体功能 */
int add(int a, int b)
{return a+b;
}int sub(int a, int b)
{return a-b;
}int main()
{int x = 5;int y = 3;    printf("%d\n", test(add, x, y));    /* 两个整型数据的加法运算 */printf("%d\n", test(sub, x, y));    /* 两个整型数据的减法运算 */
}

应用实例:

#include <stdlib.h>
void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
功能:实现任意数据类型数组元素的排序
参数:参数1:base 表示数组首元素基地址;参数2:nmemb 表示数组元素的个数;参数3:size 表示数组中元素所占空间大小参数4:comper是函数指针,指向的是由使用者根据自定义的需要排序数组元素的数据类型所定义的回调函数,用于数组元素之间大小的比较

 字符串处理函数

1 字符串

所谓的字符串,指的是以字符'\0'结尾的连续多个字符所构成的集合称为字符串;包含字符串常量和字符串变量。

1. 字符串常量

所谓的字符串常量,指的是不能被修改的字符串数据,称为字符串常量;

        a) 直接表示字符串常量

                "char"、"hello"

        b) 指针表示:

                char p = "long";/ 开辟两段存储空间,分别为:

                1) 字符串常量"long"的存储空间,

                2)指针变量p的存储空间(可以修改);p所指向的存储空间的内容为字符串常量(不能修改) */

p[0] = 'a';/* 运行时出现错误,p所指向的是字符串常量数据,不能修改

                在实际应用过程中常定义const指针变量存储字符串常量的地址;

                const char *p = "long" */

字符串常量:存储在内存的常量区。

2. 字符串变量

所谓的字符串变量,指的是可以被修改的字符串数据,称为字符串变量;

        a) 字符数组存储字符串数据:

                一般只会使用到字符数组的部分空间实现字符串变量数据的存储;只要遇到'\0'字符串结束。

                避免字符串访问过程中对于数组空间的越界。

                char buf[] = "long";/* 开辟两段存储空间,分别为:

                1) 字符串常量"long"的存储空间,只有在初始化赋值的时候能够访问,后续不能再次访问到;

                2) 字符数组的存储空间(5字节);里面的内容可以修改*/

b) 动态管理的字符指针指向空间存储字符串数据。

        字符串变量:存储在栈区、堆区、和静态存储区的字符串数据

2 字符串处理函数

2.1 字符串长度计算——strlen

/* 函数原型 */
#include <string.h>
char *strcpy(char *dest, const char *src);
功能:将指针src所指向的字符串数据(包含'\0')拷贝到指针dest所指向的目标存储空间中
参数:参数1:src表示源字符串数据的起始地址;参数2:dest表示目标存储空间起始地址;
返回值:返回的是目标存储空间起始地址,实现数据的链式存储
/* 自定义实现 */  
char *my_strcpy(char *dest, const char *src)
{
#if 0int i;for (i = 0; src[i] != '\0'; i++) {dest[i] = src[i];}dest[i] = src[i];
#elsechar *p = dest;const char *q = src;while(*p++ = *q++);
#endifreturn dest;
}      char *strncpy(char *dest, const char *src, size_t n);
功能:将指针src所指向的字符串数据(包含'\0')的前n个字符拷贝到指针dest所指向的目标存储空间中;
参数:参数1:src表示源字符串数据的起始地址;参数2:dest表示目标存储空间起始地址;参数3:拷贝的字符个数n >= 指针src所指向字符串数据的长度的时候,按照src实际字符个数拷贝(包含'\0'),等价于strcpyn < 指针src所指向字符串数据长度的时候,拷贝前n个字符
返回值:返回的是目标存储空间起始地址,实现数据的链式存储。

在字符数组中,sizeof和strlen的区别:

        1) sizeof是运算符关键字,计算整个数组所占内存空间的大小;

        2) strlen是函数,计算字符串中字符的个数,遇到'\0'结束;

2.2 字符串拷贝——strcpy/strncpy

/* 函数原型 */
#include <string.h>
char *strcpy(char *dest, const char *src);
功能:将指针src所指向的字符串数据(包含'\0')拷贝到指针dest所指向的目标存储空间中
参数:参数1:src表示源字符串数据的起始地址;参数2:dest表示目标存储空间起始地址;
返回值:返回的是目标存储空间起始地址,实现数据的链式存储
/* 自定义实现 */  
char *my_strcpy(char *dest, const char *src)
{
#if 0int i;for (i = 0; src[i] != '\0'; i++) {dest[i] = src[i];}dest[i] = src[i];
#elsechar *p = dest;const char *q = src;while(*p++ = *q++);
#endifreturn dest;
}      char *strncpy(char *dest, const char *src, size_t n);
功能:将指针src所指向的字符串数据(包含'\0')的前n个字符拷贝到指针dest所指向的目标存储空间中;
参数:参数1:src表示源字符串数据的起始地址;参数2:dest表示目标存储空间起始地址;参数3:拷贝的字符个数n >= 指针src所指向字符串数据的长度的时候,按照src实际字符个数拷贝(包含'\0'),等价于strcpyn < 指针src所指向字符串数据长度的时候,拷贝前n个字符
返回值:返回的是目标存储空间起始地址,实现数据的链式存储。

2.3 字符串的连接——strcat/strncat

/* 函数原型 */
#include <string.h>
char *strcat(char *dest, const char *src);
功能:将指针src所指向的字符串数据(包含'\0')拷贝到指针dest所指向字符串数据的末尾('\0'地址位置开始);
参数:参数1:src表示源字符串数据的起始地址;参数2:dest表示目标存储空间起始地址;
返回值:返回的是目标存储空间起始地址,实现数据的链式存储。/* 自定义函数实现*/
char *my_strcat(char *dest, const char *src)
{int i;int j;for (i = 0; dest[i] != '\0'; i++);for (j = 0; src[j] != '\0'; j++)dest[i+j] = src[j];dest[i+j] = src[j];return dest;
}char *strncat(char *dest, const char *src, size_t n);
功能:将指针src所指向的字符串数据(包含'\0')的前n个字符拷贝到指针dest所指向字符串数据的末尾('\0'地址位置开始);
参数:参数1:src表示源字符串数据的起始地址;参数2:dest表示目标存储空间起始地址;参数3:拷贝的字符个数n >= 指针src所指向字符串数据的长度的时候,按照src实际字符个数拷贝(包含'\0'),等价于strcatn < 指针src所指向字符串数据长度的时候,拷贝前n个字符
返回值:返回的是目标存储空间起始地址,实现数据的链式存储。

2.4 字符串比较——strcmp/strncmp

/* 函数原型 */
#include <string.h>
int strcmp(const char *s1, const char *s2);
功能:比较s1和s2所指向字符串数据,对字符串中对应序号的字符ASCII编码值的比较
返回值:s1 > s2 返回1,s1 == s2 返回0,s1 < s2 返回-1
/* 自定义实现 */    
int my_strcmp(const char *s1, const char *s2)
{int i;for (i = 0; s1[i] != '\0' && s2[i] != '\0'; i++) {if (s1[i] > s2[i])return 1;if (s1[i] < s2[i])return -1;}if (s1[i] == '\0' && s2[i] == '\0')return 0;if (s1[i] == '\0')return -1;if (s2[i] == '\0')return 1;
}                int strncmp(const char *s1, const char *s2, size_t n);
功能:比较s1和s2所指向的字符数据中的前n个字符
参数:n >= s1字符串中字符个数 || n >= s2字符串中字符个数,等价于strcmpn < s1 字符串中字符个数 && n < s2字符串中字符个数;只会对前n个字符进行比较;

2.5 字符串查找——strstr

/* 函数原型 */
#include <string.h>
char *strstr(const char *haystack, const char *needle);
功能:在haystack指针指向的父串中查找needle指针指向的子串是否存在
参数:参数1:haystack表示父串的起始地址;参数2:needle表示子串的起始地址
返回值:如果存在,返回子串在父串中第一次出现的起始地址;如果不存在,返回NULL;
/* 自定义实现 */
char *my_strstr(const char *haystack, const char *needle)
{const char *p = haystack;const char *q = needle;const char *r; while(*p != '\0') {if (*p != *q) {p++;continue;}r = p;q = needle;while(*q != '\0') {if (*q == *r) {q++;r++;} else {break;}}if (*q == '\0')return p;}return NULL;
}

3 字符串数据和数据类型数据转换处理函数

3.1 数字字符串数据转换为整型数据

/* 函数原型 */
#include <stdlib.h>int atoi(const char *nptr);            /* int 类型的整型数据 */
long atol(const char *nptr);           /* long 类型的整型数据 */
long long atoll(const char *nptr);     /* long long 类型的整型数据 */
功能:将数字字符串数据转换为整型数据注意:转换只针对于字符'0' ~ '9' 的数字字符,其它字符不能转换会自动结束;
/* 自定义函数 */
int my_atoi(const char *nptr)
{const char *p = nptr;int num = 0;while(*p >= '0' && *p <= '9') {num = 10*num + (*p - '0');p++;}return num;
}

3.2 基本数据的格式输出到字符数组空间

int sprintf(char *str, const char *format, ...);
参数:str:目标存储空间起始地址format:格式化信息,和printf一致;
返回值:返回格式成功的字节数;int snprintf(char *str, size_t size, const char *format, ...);
功能等价于sprintf,比sprintf更安全:传递了存储空间的大小,避免字符数组存储空间的越界访问。

3.3 字符数组数据格式化到数据变量

int sscanf(const char *str, const char *format, ...);
参数:str:表示字符数组存储空间起始地址;format:格式化信息,和scanf一致。接收数据使用数据的存储空间地址表示

4 字符串切割函数

#include <stdio.h>
#include <string.h>int main()
{char buf[] = "asdffj;jsksaj;kfjkawe;kjgkes;jkgesrk;jgjserkj";char *arr_p[8];int i = 0;char *p;p = strtok(buf, ";");while(p != NULL) {arr_p[i] = p;i++;p = strtok(NULL, ";");}while(i--) {printf("%s\n", arr_p[i]);}   
}

知识点5 递归函数

所得递归函数,函数在执行过程中,满足函数自己调用自己或者间接调用自己的时候,此时的函数称为递归函数。

递归函数算法思路满足递推和回归两个阶段:

递归函数的原则:

1) 当算法满足,当前数据运算,和去掉当前数据运算算法一致的时候,此时可以采用递归函数实现

2) 递归具体实现:

        单个函数的递归:函数自己调用自己本身;

        多个函数的递归:函数之间循环调用

        如果两个函数:A->B,B->A

3) 递归函数,必须存在结束条件。需要避免递归调用过程中出现死循环过程。

注意:所有的递归函数,都可以使用循环控制逻辑实现,递归简化代码。

#include <stdio.h>int func(int x)
{int i;int num = 1;for (i = 1; i <= x; i++)num *= i;return num;
}int test(int x)
{if (x == 0)return 1;return x * test(x-1);
}int main()
{int i = 0;for (i = 0; i < 10; i ++) {printf("%d! = %d(%d)\n", i, func(i), test(i));}
}

练习:棋盘上放麦粒的问题

1) 现有棋盘,一共有64格,格子编号为0-63;

2) 在格子中放麦粒:

        编号为0的格子放1粒,后续格子麦粒数是前一个格子麦粒的2被;

        格子的编号:0 1 2 3

        格子麦粒数:1 2 4 8

        编号为n的格子的麦粒数 = 2 * 编号为n-1的格子麦粒数

问:编号为i的格子麦粒数据,以及编号为i的格子的前面所有格子麦粒数。

 内存管理

在Linux系统中,程序的运行会创建进程,进程是资源管理的最小单位。

1) 在32位操作系统中

        程序运行的进程自动构造4G的虚拟内存空间,其地址编号为:0x0 ~ 0xffff ffff。程序中数据(程序执行指令、常量、变量)都会在虚拟内存中存储,所有的虚拟内存是被完全访问

2) 在64位操作系统中,虚拟内存的最大数是140737488224256,换算成16进制是0x7FFFFFFE0000。虚拟内存空间未被完全访问,其中未被访问的空间未保留。

1 内存管理的方式

根据数据存储空间在虚拟内存中的开辟方式分为:静态内存管理和动态内存管理

1.1 静态内存管理

所谓的静态内存管理,指的是数据存储空间会随着 程序运行过程中自动开辟和释放

在程序编译的时候,决定数据存储空间的大小和生命周期,在程序执行的时候按照预先所设定进行空间内存管理。

主要包含全局变量和局部变量存储空间

1. 全局变量:

        生命周期为程序执行开始到程序执行结束;

        存储位置为静态存储区

        作用域(链接属性):默认为全局域,使用static关键修饰作用域为当前文件域。

2. 局部变量:

        a) 默认的auto修饰的局部变量

                生命周期为程序语句执行到模块结束;

                存储位置为栈区

                作用域为模块域

        b) static修饰的局部变量

                生命周期为程序语句执行到程序结束

                存储位置为静态存储区

                作用域为模块域

1.2 动态内存管理

所谓的动态内存管理,指的是由使用者根据数据存储空间的大小和生命周期管理内存;

生命周期:空间的使用者手动开辟到空间的使用者手动释放

存储位置:堆区;

作用域:是由空间起始地址的存储变量的属性表示。

2 动态内存管理实现

在标准C库中,提供了动态内存管理函数:malloc、calloc、realloc、free

2.1 动态内存开辟和释放

#include <stdlib.h>
void *malloc(size_t size);
功能:malloc动态开辟指定size字节大小的连续内存空间实际开辟内存空间的大小略大于size字节,多出部分用于存储空间的相关信息。
参数:size > 0,按照指定size字节数开辟存储空间,成功返回空间的起始地址,开辟成功的空间数据未初始化;失败返回NULLsize == 0,可能返回NULL;也可能返回开辟成功内存空间的起始地址,所开辟成功内存空间不能进行读写访问,否则导致内存的越界访问。
返回值:返回值为void类型指针,可以用于存储任意数据类型的地址,在动态内存管理过程中可以根据使用者的实际需求进行指针类型的转换,以实现动态内存存储任意数据类型的数据            
void free(void *ptr);
功能:释放指针变量ptr所指向的动态开辟内存空间;
注意:1) 动态内存空间的释放,只需要传递空间的起始地址;2) 在动态内存释放之后,需要将指针变量设置为NULL,表示指针变量没有指向。否则出现野指针,此时访问出现未知异常。3) 对于动态开辟的内存空间,在不使用的时候,需要及时动态释放,否则一直占用内存,导致程序执行效率降低。void *calloc(size_t nmemb, size_t size);
功能:calloc按照数据元素个数nmemb和元素空间字节数size开辟连续的nmemb*size字节内存空间等价于:malloc(nmemb*size);void *realloc(void *ptr, size_t size);
功能:对ptr指针指向的已开辟的原空间进行重新开辟指定大小的size字节大小空间,用于原空间的减小和放大处理
参数:    参数1:ptr表示原开辟成功内存空间的起始地址;参数2:size表示新开辟内存空间的字节数size <= 原开辟成功内存空间大小,表示内存空间截短。保留原内存空间的前size字节空间,返回值为原ptr地址值;size > 原开辟成功内存空间大小:size <= 原开辟内存空间大小 + 向后未使用连续内存空间大小直接在原空间后追加开辟空间,并返回原空间地址ptr的值;size > 原开辟内存空间大小 + 向后未使用连续内存空间大小重新找到足够连续未使用内存空间开辟,并将原内存空间数据拷贝到新开辟内存空间,在释放原内存空间;

#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main()
{int i;char *p; char *str;p = (char *)malloc(4);if (p == NULL) {perror("malloc");return -1; }int *q = (int *)p;*q = 0x12345678;for (i = 0; i < 4; i++)printf("%x\n", p[i]);    free(p);p = NULL;q = NULL;p = calloc(16, 1);if (p == NULL) {perror("calloc");return -1;}strcpy(p, "abcdef");printf("%p:%s\n", p, p);str = realloc(p, 8);printf("%p:%s\n", p, p);free(p);free(str);p = realloc(p, 2048);printf("%p:%s\n", p, p);free(p);p = NULL;
}

2.2 野指针和内存泄漏

1. 野指针

        所谓的野指针,指的是指针指向的内存空间未初始化

        指针变量在定义的时候,未设置初始值而使用默认值;此时指向的空间未做开辟;

        指针变量在指向动态管理内存的时候,有将指针指向的内存空间做释放,此时指向的空间未做开辟;

        野指针的访问,可能会导致未知异常,需要谨慎:

        在指针变量定义的时候有明确指向空间和指向空间释放的时候,将指针变量值设置NULL。在访问指针的时候,对指针进行判断。

2. 内存泄漏

        所谓的内存泄漏,所开辟的内存空间,一直占用未被使用,同时也不能对该内存空间进行读写访问。也不能再次进行内存资源的分配管理。就会导致内存泄漏。

        内存泄漏的根本原因:已动态开辟的内存空间没有指针指向,无法实现内存的访问。之前指向该内存空间的指针变量被修改。

避免内存泄漏:

        1) 合理选择内存空间的开辟和释放;

        2) 在对内存空间指针访问的时候,不要随意修改指针的指向;如果要修改,在指向改变之前对原指向空间进行释放或者使用新的指针变量存储原空间地址。

        3) 实时检测内存资源情况,如果有检测到内存泄漏,需要立即做内存释放。

3 内存分别

在32位操作系统,其内存分布管理

#include <stdio.h>
#include <stdlib.h>extern void func();void test(void)
{}int a;
static int c;
int d;int main()
{static int b;static int e;char *p = malloc(1);    /* .text:代码段 */printf("test: %p\n", test);printf("main: %p\n", main);printf("func: %p\n", func);/* .rodata:常量数据 */printf("%p\n", "ancd");/* .data:已初始化静态数据变量 */printf("&d:%p\n", &d);printf("&e:%p\n", &e);/* .bss:未初始化静态数据变量 */printf("&a:%p\n", &a);printf("&b:%p\n", &b);printf("&c:%p\n", &c);/* 堆区 */printf("p = %p\n", p);/* stack:栈区 */printf("&p = %p\n", &p);}void func()
{}

 程序的编译过程

在Linux系统中,提供gcc编译器,实现面向过程的C语言程序的编译,生成可执行程序。

1. 整体编译:

        gcc 源文件

        源文件:可以是一个或者多个.c文件,

        源文件.c文件的个数是由程序中的所有.c文件构成;

        在编译成功生成可执行程序,默认情况下,可执行程序位a.out;可以是-o选项指定所生成的可执行程序

        例如:gcc hello.c -o hello编译源文件hell.c,成功生成可执行程序hello

1. 分布编译:

        按照C语言程序的具体编译过程,实现整个程序的编译,具体的步骤:

        a) 预处理阶段:

        所谓的程序的预处理,就是实现预处理指令的执行,具体包含

                1) #include 预处理指令实现头文件的展开;

                2) #define 标识符(常量和宏函数)的替换;

                3) # if 条件预处理指令实现语句选择编译;

预处理实现:

        gcc -E hello.c -o hello.i /* -E 预处理选项,对源文件hello.c进行预处理生成hello.i文件 */

b) 编译阶段:

        所谓的编译阶段,实现语句的语法检测,将高级语言(C/C++)翻译为汇编代码

        gcc -S hello.i -o hello.s/* -S 编译选项,将预处理后的C代码编译位汇编代码 */

c) 汇编阶段

        汇编阶段,是将汇编指令翻译为机器指令(二进制代码指令)

        gcc -c hello.s -o hello.o/* -c汇编选项,将汇编代码翻译为二进制机器代码 */

d) 链接阶段

        链接阶段,是实现程序的链接,具体的链接包含:

                1) 将程序中的所有.o文件的.text段、.data段、.bss段等分别进行链接为整体;

                2) 链接所依赖的库程序,

                        静态库,直接将库代码链接到可执行程序中;

                        动态库,链接所依赖库的信息

                3) 链接启动代码

                链接main函数启动前的初始化代码

                gcc hello.o -o hello

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

图片工具箱:一键批量加水印,守护创意,提升效率!

前言 你是否曾在处理海量图片时&#xff0c;被繁琐的步骤和漫长的等待时间折磨得苦不堪言&#xff1f;是否梦想过拥有一款神器&#xff0c;能让你的图片处理工作变得轻松愉快&#xff0c;从此告别加班的烦恼&#xff0c;迎接升职加薪的曙光&#xff1f;那么&#xff0c;让我向…

有限差分学习笔记

有限差分介绍 ​ 在数学中&#xff0c;有限差分法&#xff08;finite-difference methods&#xff0c;简称FDM&#xff09;&#xff0c;是一种微分方程数值方法&#xff0c;是通过有限差分来近似导数&#xff0c;从而寻求微分方程的近似解。 由泰勒展开式的推导 显式方…

Web应用加密数据传输方案

目录 概述 最初的方案 改进后的方案 秘钥的过期时间 概述 介于公司最近发布了一个面向C端用户的Web系统&#xff0c;为防止前端调用后端的API接口时&#xff0c;数据传输的内容轻易的被黑客获取&#xff0c;而设计的一个前后端数据加密传输方案 最初的方案 在最开始&#xf…

2 种方式申请免费 SSL 证书,阿里云 Certbot

如何使用免费的 SSL 证书&#xff0c;有时在项目中需要使用免费的 SSL 证书&#xff0c;Aliyun 提供免费证书&#xff0c;三个月有效期&#xff0c;可以直接在aliyun 申请&#xff0c;搜索 SSL 证书&#xff0c;选择测试证书。 Aliyun 证书需要每三月来来换一次&#xff0c;页…

<数据集>车内视角行人识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;6470张 标注数量(xml文件个数)&#xff1a;6470 标注数量(txt文件个数)&#xff1a;6470 标注类别数&#xff1a;1 标注类别名称&#xff1a;[pedestrian] 序号类别名称图片数框数1pedestrian647029587 使用标注…

奔驰S迈巴赫S480升级动态按摩座椅效果怎么样

在迈巴赫 S480 的尊崇之旅中&#xff0c;舒适从未有尽头。现在&#xff0c;为您呈现前排动态按摩座椅的升级&#xff0c;将舒适体验提升至全新境界。 迈巴赫 S480 已然是舒适的代名词&#xff0c;但前排动态按摩座椅的升级&#xff0c;将为您带来前所未有的放松与享受。 当您…

网络UDP报文详细解析

目录 一、简介二、详细介绍三、其他相关链接1、TCP报文段的详细图总结2、TCP三次握手和四次挥手详解3、socket通信原理及相关函数详细总结4、网络包IP首部详细解析 一、简介 本文主要介绍UDP报文格式。 二、详细介绍 UDP是一种无连接、不可靠的用户数据报协议&#xff0c;其…

【注解】反序列化时匹配多个 JSON 属性名 @JsonAlias 详解

JsonAlias 注解是 Jackson 提供的一个功能强大的注解&#xff0c;允许一个字段在反序列化时匹配多个 JSON 属性名。它适用于在处理多种输入数据格式时&#xff0c;或当 JSON 数据的键名可能变化时。 一、JsonAlias 的作用 多种别名&#xff1a;JsonAlias 允许你为一个字段定义…

MySQL在Windows和Ubuntu上的安装与远程连接配置

MySQL是一个广泛使用的开源关系数据库管理系统&#xff0c;适用于各种操作系统。本文将详细介绍如何在Windows和Ubuntu系统上安装MySQL&#xff0c;并配置远程连接。 1. 在Windows上安装MySQL 1.1 下载MySQL安装包 首先&#xff0c;访问MySQL官方网站&#xff08;https://de…

UEFI 01记: 开发环境 在 ubuntu22 中搭建 edk2 开发环境并运行简单示例

https://uefi.org 1&#xff0c;预备环境 $ sudo apt install uuid-dev $ sudo apt install nasm $ sudo apt install bison flex $ sudo apt install build-essential $ sudo apt-get install x11proto-xext-dev $ sudo apt-get install libx11-dev $ sudo apt-get install l…

[数据集][目标检测]管道漏水泄漏破损检测数据集VOC+YOLO格式2614张4类

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;2614 标注数量(xml文件个数)&#xff1a;2614 标注数量(txt文件个数)&#xff1a;2614 标注…

[windows][apache]Apache代理安装

下载apache服务软件和VC_redist安装包 https://www.apachelounge.com/download/ https://www.microsoft.com/zh-CN/download/details.aspx?id48145 解压文件&#xff0c;修改httpd.conf文件 37行出修改文件的解压目录 60行修改监听端口 安装服务 进入apache的目录&#xf…

美团mtgsig 1.2算法分析

声明 本文以教学为基准、本文提供的可操作性不得用于任何商业用途和违法违规场景。 本人对任何原因在使用本人中提供的代码和策略时可能对用户自己或他人造成的任何形式的损失和伤害不承担责任。 如有侵权,请联系我进行删除。 这里只是我分析的分析过程,以及一些重要点的记录…

帮助我们从曲线图中获取数据的软件分享——GetData Graph Digitizer

在科技论文写作和数据分析过程中&#xff0c;我们常常需要将自己的数据与前人的研究成果进行对比。然而&#xff0c;有时我们只能从别人的论文中获得一张包含坐标轴的曲线图&#xff0c;而无法直接获取原始数据。在这种情况下&#xff0c;GetData Graph Digitizer 软件就显得尤…

人眼检测(单张图像)

生产资料私有化&#xff0c;是阻碍社会发展(包括学习)的一大阻力。希望更多学习资料公出供学习。 目录 实验原理 示例代码 运行结果 注意事项&#xff1a; 在OpenCV中使用C对中可以读取图像文件&#xff0c;检测图像中的人脸和眼睛&#xff0c;并在检测到的眼睛位置绘制矩…

Telnet详解与应用——从原理到实战模拟

1. 引言 在现代网络管理中&#xff0c;远程访问和控制设备的能力至关重要。Telnet是一种经典的远程访问协议&#xff0c;尽管在安全性方面逐渐被SSH等更现代化的协议取代&#xff0c;但其在早期网络管理中的广泛使用使其成为网络工程师的基本技能之一。本文将深入探讨Telnet的…

Leuze ROD4-20 ROD4-38系列激光扫描仪软件与操作手测

Leuze ROD4-20 ROD4-38系列激光扫描仪软件与操作手测

移动式气象站:科技赋能,监测天气

在自然灾害频发、气候变化日益显著的今天&#xff0c;准确、及时地获取气象信息对于农业生产、城市规划、交通运输以及灾害预警等领域至关重要。传统固定气象站虽能提供稳定的观测数据&#xff0c;但在偏远地区、灾害现场或快速变化的环境中&#xff0c;其局限性逐渐显现。为此…

数据仓库系列7:什么是概念模型、逻辑模型和物理模型,它们有什么区别?

你是否曾经困惑于数据仓库中的各种模型?概念模型、逻辑模型、物理模型 - 它们听起来很相似,但实际上各有千秋。 目录 引言:为什么模型如此重要?1. 概念模型:勾勒数据的蓝图什么是概念模型?概念模型的特点概念模型的例子概念模型的作用如何创建概念模型 2. 逻辑模型:细化你的…

十大护眼落地灯品牌哪个牌子好?落地灯品牌排行前十名

十大护眼落地灯品牌哪个牌子好&#xff1f;随着快经济时代的到来&#xff0c;人们在学业以及事业上的压力也日益增加&#xff0c;不少朋友反应在日常工作、学习是经常出现眼部疲劳的状况&#xff0c;甚至会时不时出现眼睛干涩、流泪&#xff0c;对学习、工作状态造成了极大的困…