函数——————

本文详细介绍了C语言中的函数定义,包括函数头、参数列表、返回类型和函数体,以及实参和形参的关系。讨论了无返回值和有返回值的函数,以及如何处理函数参数。同时,提到了栈内存和堆内存的使用,包括静态数据、全局变量和局部变量的生命周期。此外,还讨论了静态函数的概念和作用。
摘要由CSDN通过智能技术生成

函数的定义

  • 函数头:函数对外的公共接口
  1. 函数名称:命名规则与变量一致,一般取与函数实际功能相符合的、顾名思义的名称。

  2. 参数列表:即黑箱的输入数据列表,一个函数可有一个或多个参数,也可以不需要参数。

  3. 返回类型:即黑箱的输入数据类型,一个函数可不返回数据,但最多只能返回一个数据

  • 函数体:函数功能的内部实现

返回类型 函数名称(参数1,参数2,.......)
{
    函数体
    return 返回值;
}

  • 场景1:没有返回值没有参数的函数实现
#include <stdio.h>

// 植发医院,子函数的函数体实现
// 没有返回值没有参数的函数
void huamei(void)
{
    printf("植发进行中,请勿打扰\n");
}

void basketGround(void)
{
    printf("小明打完篮球准备去植发\n");
    huamei();
}

// 主人的家,主函数
int main(int argc, char const *argv[])
{
    // 进入huamei函数并执行
    // huamei为函数名即是函数的首地址
    // ()表示进入此函数并执行
    huamei();
    basketGround();
    return 0;
}
  • 场景2:没有返回值有参数的函数
#include <stdio.h>

// 子函数
void basketGround(char *tName)
{
    printf("%s正在打篮球\n",tName);
}

// 设计一个接口函数,实现两个数值相加
void Add(int a, int b)
{
    printf("%d+%d=%d\n",a,b,a+b);
}

// 主函数
int main(int argc, char const *argv[])
{
    char Name[] = "jack";
    // Name为实参,参数个数没有限制,用逗号隔开
    basketGround(Name); 
    Add(10,20);
    
    return 0;
}
注意:

        实参与形参的类型和传递个数一致

  • 场景3:有返回值有参数
#include <stdio.h>

// 实现两个数值相加,并将结果返回
float Add(int a, float b)
{
    return a+b;
}

int main(int argc, char const *argv[])
{
    int a = 10;
    float f = 10.5;
    float ret = Add(a,f);
    printf("%.2f\n",ret);
    return 0;
}
    //将实参a给形参a,实参f给形参b
#include<stdio.h>

//函数的定义
void print(void)
{
    printf("hello world\n");
}

//可以输出任意字符串
//有参数,没有返回值
//接受数据的参数称为形参  形参:内存空间
void print_str(const char* str)
{
    printf("%s\n",str);
}


//注意:return的返回值类型一定与函数名左边的返回类型一致,否则报错
int add(int a,int b)
{
    int ret = a + b;
    return ret;
}


void print_aj(const char* aj)
{
    printf("%s\n",aj);

}

void meifa(void)
{
    printf("植发进行中,勿扰\n");
}


void huamei(void)
{
    printf("植发进行中,请勿打扰\n");
}

void basketGround(void)
{
    printf("小明打完篮球准备去植发\n");
    huamei();
}


//主函数,程序的入口地址
int main(int argc, char const *argv[])
{
    //函数调用
    //函数名就是这个函数的入口地址
    printf("%p\n",print);
    //()的作用是进入print地址,执行对应的函数
    print();
    //输出字符串
    //实际传递给函数的参数称为实参
    print_str("jack");//const char* str = "jack"
    //实现两个数之和
    int a = 100,b = 200;
    int ret = add(a,b);
    printf("ret:%d\n",ret);
    printf("ret:%d\n",add(a,b));
    print_aj("g");
    meifa();
    huamei();
    basketGround();
    return 0;
}
作业1:设计加减乘除取余接口函数,并实现将结果返回到主函数,并输出
#include<stdio.h>

float add(float a,float b)
{
    return a+b;
}

float sub(float a,float b)
{
    return a-b;
}

float mul(float a,float b)
{
    return a*b;
}

float div(float a,float b)
{
    return a/b;
}

int sur(int a,int b)
{
    return a%b;
}


int main(int argc, char const *argv[])
{
    float a= 20,b = 10;
    float ret = add(a,b);
    printf("%.2f\n",add(a,b));
    printf("%.2f\n",ret);
    printf("%.2f\n",sub(a,b));
    printf("%.2f\n",mul(a,b));
    printf("%.2f\n",div(a,b));
    printf("%.2f\n",sur(a,b));
    return 0;
}
练习:封装接口函数实现两个数值交换,主函数将交换后的内容打印出来
#include <stdio.h>
// 等价于 int *a1 = &a; int *b1 = &b;
void swap(int *a1, int *b1)
{
    int temp = *a1; // 解引用,获取a1地址里面的内容
    *a1 = *b1;
    *b1 = temp;
}

int main(int argc, char const *argv[])
{
    int a = 10, b = 20;

    swap(&a,&b);
    printf("%d,%d\n",a,b);
    return 0;
}




//第二种

#include<stdio.h>

//swap:交换两个数值
void swap(int *point_a,int *point_b)//形参
{
    int temp = *point_a;//解引用
    *point_a = *point_b;
    *point_b = temp;
    printf("%d,%d\n",*point_a,*point_b);
}

int main(int argc, char const *argv[])
{
    int a = 10,b = 20;
    swap(&a,&b);//实参
    printf("after(%d,%d)\n",a,b);
    return 0;
}
语法汇总:
  1. 当函数的参数列表void时,表示该函数不需要任何参数

  2. 当函数的返回类型void时,表示该函数不返回任何数据

  3. 关键字return表示退出函数。

                ①若函数头中规定有返回数据类型,则 return 需携带一个类型与之匹配的数据;

                ②若函数头中规定返回类型为 void,则 return 不需携带参数。

总结

函数名前面为返回值的类型,哪怕此函数没有返回值,也要写上void 
     函数的返回类型要顶格
     函数返回类型与函数名空一格
     函数参数要写(),哪怕里面没有任何内容,如果没有内容最好这样写void func(void)
     函数调用者调用子函数的时候,可以不接收返回值

注意

大括号表示函数的工作范围,如果离开此范围,数据就不属于此函数管辖
     函数不能在main(){}里面实现,虽然不会报错,如果不调用,这个子函数是不会被执行的
     所有函数的实现都在主函数外面实现

// 语法没问题,编程规范有问题,没有人这么写
    int main()// 主函数
    {
        void func() // 子函数
        {
            printf("func\n");
        }
        
        func();
    }

    // 正确的写法是将子函数放到主函数外面实现
    void func()
    {
        
    }
    
    int main()
    {
        func();
    }

实参与形参

  • 概念:

    • 函数调用中的参数,被称为实参,即arguments

    • 函数定义中的参数,被称为形参,即parameters

  • 实参与形参的关系:

    • 实参与形参的类型和参数个数必须一一对应

    • 形参的由实参初始化。

    • 形参与实参位于不同的内存区域,彼此独立

// 函数定义中,x、y都属于形参,位于函数 max 的栈内存中
// 它们的值由实参一一对应初始化
int max(int x, int y)
{
    int z;
    z = x>y ? x : y;
    return z;
}

int main(void)
{
    int a = 1;
    int b = 2;
    int m;
        
    // 函数调用中,a、b都属于实参,存储于主函数 main 的栈内存中
    m = max(a, b);    
}

函数调用的流程

        函数调用时,进程的上下文回切换到被调函数,当被调函数执行完毕之后再切换回去。

  

     

 作业1.输入一个数,求此数最近相邻最小质数(只能被1或者自身整除的数称为质数)
#include<stdio.h>
#include<stdbool.h>

bool is_prnNum(int n)
{
    if(n < 2)
        return false;
    //查找除了1和本身以外的其他数是否能除尽
    for(int i = 2;i < n;i++)
    {
        if(n % i == 0)//不是质数
            return false;
    }
    return true;
}

int main(int argc, char const *argv[])
{
    int n;
    scanf("%d",&n);
//计算n的下一个或上一个是否为质数
    int count = 0;

    while(1)
    {
        //判断是否有质数
        if(is_prnNum(n - count))
        {
            printf("%d\n",n-count);
            break;
        }
        count++;
    }
   
    return 0;
}

作业2.字符串去重--->googggoole--->gole(华为笔试题)

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


int main(int argc, char const *argv[])
{
    char str[100] = {0};
    //从键盘获得字符串
    gets(str);
    int len = strlen(str);
    printf("%d\n",len);

    for(int i = 0;i < len;i++)
    {
        for(int j = i + 1;j < len;j++)
        {
            if(str[i] == str[j])
            {
                for(int k = j;k < len;k++)
                {
                    str[k] = str[k+1];
                }
                j--;
                len--;
            }
        }
        printf("%s\n",str);
    }

    return 0;
}



作业3.    实现MyStrcat()接口
                     MyStrcpy()
                     MyStrncpy()
                     MyStrlen()

///MyString.h

#include <stdbool.h>

/*
* MyStrcpy : 字符串拷贝
*/
bool MyStrcpy(char *dest, const char *src);
bool MyStrncpy(char *dest, const char *src, size_t n);

/*
* MyStrcat : 字符串拼接
*/
char *MyStrcat(char *dest, const char *src);

/*
* MyStrlen : 计算字符串有效长度
*/
int MyStrlen(const char *src);


//MyString.c

#include <stdio.h>
#include <stdbool.h>

// 字符串拷贝
bool MyStrcpy(char *dest, const char *src)
{
    if(dest == NULL || src == NULL)
        return false;

    while(*src) // 只要没到0则继续遍历
    {
        *(dest++) = *(src++);
    }
    *dest = '\0';
    
    return true;
}

bool MyStrncpy(char *dest, const char *src, size_t n)
{
    if(dest == NULL || src == NULL)
        return false;

    int i;
    for(i = 0; i < n && src[i] != '\0'; i++)
    {
        dest[i] = src[i];
    }
    for(;i < n; i++)
    {
        dest[i] = '\0';
    }
    return true;
}

// 字符串合并
char *MyStrcat(char *dest, const char *src)
{
    if(dest == NULL || src == NULL)
        return NULL;

    // 定位dest末尾
    while(*dest)
    {
        ++dest;
    }  
    
    while(*src)
    {
        *dest++ = *src++; 
    }
    *dest = '\0';
    return dest;
}

// 字符串长度
int MyStrlen(const char *src)
{
    if(src == NULL)
        return -1;
    
    int len = 0;
    while(*src++ != '\0')
    {
        len++;
    }
    return len;
}


//main.c

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

int main(int argc, char const *argv[])
{
    char src[5] = "jack";
    char dest[10] = "ken";
    // 字符串拷贝
    //MyStrcpy(dest,src);
    //MyStrncpy(dest, src, 3);
    //printf("dest : %s\n",dest);

    MyStrcat(dest,src);
    printf("dest:%s\n",dest);

    printf("dest len : %d\n",MyStrlen(dest));
    return 0;
}

冒泡函数

 作业4.设计一个函数接口实现冒泡排序输入15432  输出 12345
#include<stdio.h>
#include<stdbool.h>
//bubble :  冒泡接口
//array  :  排序的内容
//lenght :  数组长度
//return :  成功返回true,失败返回false
bool bubble(int *array,int lenght)
{
    if(array == NULL || lenght == 0)
        return false;
    for(int i = 0;i < lenght - 1;i++)
    {
        for(int j = 0;j < lenght - i - 1;j++)
        {
            if(array[j] > array[j+1])
            {
                int temp = array[j];
                array[j] = array[j+1];
                array[j+1] = temp;
            }
        }
    }
    return true;
}

int main(int argc, char const *argv[])
{
    int array[5] = {1,5,4,3,2};
    int lenght = sizeof(array) / sizeof(array[0]);
    //冒泡接口
    bool ret = bubble(array,lenght);
    if(ret = false)
    {
        printf("bubble failed:");
        return -1;
    }
   
    for(int i = 0;i < lenght;i++)
    {
        printf("%d\t",array[i]);
    }
    return 0;
}
练习;给a b c d e f去空格
#include<stdio.h>

int main(int argc, char const *argv[])
{
    char buf[100] = {0};
    int len = 0;
    //scanf遇到空格结束,不会拿空格
    while ((buf[len++] = getchar()) != '\n');
    for(int i = 0;i < len;i++)
    {
        //找空格
        if(buf[i] == ' ')
        {
            for(int j = i;j < len;j++)
            { 
                buf [j] = buf[j+1];
            }
        }
    }
     printf("%s",buf);
    return 0;
}

函数接口

#include<stdio.h>

char print_str(char *buf)
{
    int len = 0;
    while ((buf[len++] = getchar()) != '\n');
    for(int i = 0;i < len;i++)
    {
        if(buf[i] == ' ')
        {
            for(int j = i;j<len;j++)
            {
                buf[j] = buf[j+1];
            }
        }
    }
}

int main(int argc, char const *argv[])
{
    char buf[100] = {0};
    print_str(buf);
    printf("%s",buf);
    return 0;   
}

函数模块化

  1. 一个工程应该是由多个c文件以及头文件组成

  2. c文件用于存放自己封装的函数接口或者动静态库

  3. 头文件用于存放函数的声明以及各种结构体,宏,枚举的定义

冒泡函数的封装流程

  1. 创建工程目录用于存放项目

  2. 在此工程目录下创建bubble.c main.c bubble.h

    1. bubble.c 是存放冒泡算法

    2. main.c 主函数实现

    3. bubble.h是冒泡算法的函数声明

   3.在此工程创建bubble.c添加以下内容

#include <stdbool.h>
#include <stdio.h>

bool bubble(int *array, int lenght)
{
    if(array == NULL || lenght <= 0)
    {
        return false;
    }

    for(int i = 0; i < lenght-1; i++)
    {
        for(int j = 0; j < lenght-i-1; j++)
        {
            if(array[j] > array[j+1])
            {
                int temp = array[j];
                array[j] = array[j+1];
                array[j+1] = temp;
            }
        }
    }
    return true;
}

4.创建main.c添加以下内容

#include <stdio.h>
#include <stdbool.h>

#include "bubble.h"

int main(int argc, char const *argv[])
{
    int array[] = {5,4,3,2,1};
    int len = sizeof(array) / sizeof(array[0]);
    bubble(array, len);

    for(int i = 0; i < len; i++)
        printf("%d\t",array[i]);

    printf("\n");
    return 0;
}

5.创建bubble.h添加以下内容

bool bubble(int *array, int lenght);

6.编译成程序

gcc main.c bubble.c -o main.exe
执行程序
./main.exe
gcc 编译器编译原理

1.将c文件进行预编译,作用是将头文件里面的内容进行展开,就是将头文件的内容拷贝到main函数上面
    gcc main.c -o main.i -E

2.生成汇编程序,作用是将c文件编译成更加底层的编程语言汇编程序
    gcc main.i -o main.s -S

3.将汇编程序生成未链接的可执行程序
    gcc main.s -o main.o -c

4.将未连接的可执行程序生成已链接的可执行程序
    gcc main.o -o main.exe

typedef 与宏定义区别——考点

    1. 宏主要用于定义常量及书写复杂的内容;typedef主要用于定义类型别名。

    2. 宏替换发生在编译阶段之前,属于文本插入替换;typedef是编译的一部分。

    3. 宏不检查类型;typedef会检查数据类型。

    4. 宏不是语句在在最后加分号typedef是语句,要加分号标识结束。

    5. 注意对指针的操作,typedef char * p_char和#define p_char char * 区别巨大。

函数多文件封装

1.先打开终端输入
        gcc main_test.c MyString.c -o a.exe
    2.执行程序
        ./a.exe

 注意:

编译带头文件的工程程序,千万不要和.h头文件一起编译,头文件是不需要编译的,只需要将头文件和需要编译的程序放在同一个文件夹即可

主函数命令行参数

#include <stdio.h>

int main(int argc, char const *argv[])
{
    // char const *buf[] = {"jack","rose"};
    // printf("%s\n",buf[1]);
    
    // argc : 参数的个数,默认为1
    printf("argc : %d\n",argc);
    // argv[0]表示程序a.exe自身
    //printf("argv[0] : %s\n",argv[0]);
    // argv[1]表示传递的第一个参数,注意传递的参数要用空格隔开
    //printf("argv[1] : %s\n",argv[1]);

    // 输出外部参数 :"jack" "rose" "ken" "123"

    // 判断是否有输入4个外部参数
    if(argc != 5)
    {
        printf("请输入对应的参数个数:\n");
        return -1;
    }

    printf("argv[1] : %s\n",argv[1]);
    printf("argv[2] : %s\n",argv[2]);
    printf("argv[3] : %s\n",argv[3]);
    // 注意 所有外部输入的参数都是以字符串方式传输
    printf("argv[4] : %s\n",argv[4]);

    return 0;
}

#include<stdio.h>

//argc是外部参数个数
//argv[0]为程序本身   argv[0] : D:\HF2306\lesson\c语言第四章\code\a.exe
int main(int argc, char const *argv[])
{
    printf("argc =  %d\n",argc);
    for(int i = 1;i < argc;i++)
    {
        printf("argv[%d] : %s\n",i,argv[i]);
    }
    return 0;
}

C进程内存布局

栈内存

  • 什么东西存储在栈内存中?

    • 环境变量

    • 命令行参数

    • 局部变量(包含形参)

  • 栈内存有什么特点?

    • 空间有限,尤其在嵌入式环境下,尤其不可以用来存储尺寸太大的变量,在Linux栈内存大小为8M

    • 每当一个函数被调用,栈就会向下增长一段,用以存储该函数的局部变量

    • 每当一个函数退出,栈就会向上缩减一段,将该函数的局部变量所占内存归还给系统。

    • 栈空间申请的变量随着函数结束,空间自动释放

  • 注意:

    • 栈内存的分配和释放都是由系统规定的,我们无法干预。

void func(int a, int *p) // 在函数 func 的栈内存中分配
{
    double f1, f2;        // 在函数 func 的栈内存中分配
    ...           // 退出函数 func 时,系统的栈向上缩减,释放内存
}

int main(void)
{
    int m  = 100;  // 在函数 main 的栈内存中分配
    func(m, &m);  // 调用func时,系统的栈内存向下增长
}

数据段与代码段

  • 数据段细分成如下几个区域:

    • .bss 段:存放未初始化的静态数据,它们将被系统自动初始化为0

    • .data段:存放已初始化的静态数据

    • .rodata段:存放常量数据

  • 代码段细分成如下几个区域:

    • .text段:存放用户代码

    • .init段:存放系统初始化代码

int a;       // 未初始化的全局变量,放置在.bss 中
int b = 100; // 已初始化的全局变量,放置在.data 中

int main(void)
{
    static int c;       // 未初始化的静态局部变量,放置在.bss 中
    static int d = 200; // 已初始化的静态局部变量,放置在.data 中
    
    // 以上代码中的常量100、200防止在.rodata 中
}

 注意:

数据段和代码段内存的分配和释放,都是由系统规定的,我们无法干预,所以尽量不要使用数据段,除非没办法。

局部变量与栈内存

  • 局部变量概念:凡是被一对花括号包含的变量,称为局部变量

  • 局部变量特点:

    • 某一函数内部的局部变量,存储在该函数特定的栈内存中

    • 局部变量只能在该函数内可见,在该函数外部不可见

    • 当该函数退出后,局部变量所占用的内存立即被系统回收,因此局部变量也称为临时变量

    • 函数的形参虽然不被花括号包含,但依然属于该函数的局部变量

  • 栈内存特点:

    • 每当一个函数被调用时,系统将自动分配一段栈内存给该函数,用于存放其局部变量

    • 每当一个函数有退出时,系统将自动回收其栈内存

    • 系统为函数分配栈内存时,遵循从上(高地址)往下(低地址)分配原则

int max(int x, int y)// 变量x和y存储在max()函数的栈中
{
    int z;//变量z存储在max()函数的栈中
    z = x > y ? x : y;
    return z; // 函数退出后,栈中的x、y和z被系统回收
}
int main(void)
{
    int a = 1;// 变量a存储在main()函数的栈中
    int b = 2;// 变量b存储在main()函数的栈中
    int m;// 变量m存储在main()函数的栈中,未赋值所以其值为随机值
    m = max(a,b);
}

技术要点:

  • 栈内存相对而言是比较小的,不适合用来分配尺寸太大的变量

  • return之后不可再访问函数的局部变量,因此返回一个局部变量的地址通常是错误的。

const关键字----只读

const 修饰全局变量:存储在常量区
const 修饰局部变量:存储在栈区
具体验证代码如下:

#include <stdio.h>

       int 	mq0; 	//未初始化变量 
       int 	mq1	= 1;//全局变量 
static int  mq2 = 2;//静态变量 
static int  mq3=  3; 
const  int 	mq4 = 4;//只读全局变量 
const  int 	mq5 = 5;

int main(void)
{
    
	const int mq6 = 6;//只读局部变量 
	const int mq7 = 7;
		  int mq8 = 8;//局部变量 
		  int mq9 = 9;

	printf("0x%p: %d\n", &mq0 , mq0);
	printf("0x%p: %d\n", &mq1 , mq1);
	printf("0x%p: %d\n", &mq2 , mq2);
	printf("0x%p: %d\n", &mq3 , mq3);
	printf("0x%p: %d\n", &mq4 , mq4);
	printf("0x%p: %d\n", &mq5 , mq5);
	printf("0x%p: %s\n",  "A", "A");
	printf("0x%p: %s\n",  "B", "B");
	
	printf("0x%p: %d\n", &mq6 , mq6);
	printf("0x%p: %d\n", &mq7 , mq7);
	printf("0x%p: %d\n", &mq8 , mq8);
	printf("0x%p: %d\n", &mq9 , mq9);

    return 0;
}

静态数据

c语言中,静态数据有两种:

  • 全局变量:定义在函数外的变量

  • 静态局部变量:定义在函数内部,且被static修饰的变量

int a; // 全局变量,退出整个程序之前不会释放
void f(void)
{
    static int b; // 静态局部变量,退出整个程序之前不会释放
    printf("%d\n", b);
    b++;
}

int main(void)
{
    f();
    f(); // 重复调用函数 f(),会使静态局部变量 b 的值不断增大
}
为什么需要静态数据
  1. 全局变量在默认的情况下,对所有文件可见,为某些需要在各个不同文件和函数之间访问的数据提供操作上的方便。

  2. static修饰的全局变量,只能在本文件使用,如果未被static修饰的全局变量,所有的文件都能使用,会出现命名污染。

  3. 当我们希望一个函数退出后依然能保留局部变量的值,以便于下一次调用时还能用,静态局部变量可以帮助实现这样的功能。

注意1:
  • 若定义时未初始化,则系统会将所有的静态数据自动初始化为0

#include<stdio.h>

void func()
{
    //static修饰的变量只初始化一遍
    //生命周期为程序周期,局部函数结束不释放空间
    int b[100];
    static int a[100];
    for(int i = 0;i < 100;i++)
    {
        printf("%d\n",a[i]);
    }
}

int main(int argc, char const *argv[])
{
    func();
    return 0;
}
  • 静态数据初始化语句,只会执行一遍
#include<stdio.h>

void func()
{
    //static修饰的变量只初始化一遍
    //生命周期为程序周期,局部函数结束不释放空间
    int b = 0;
    static int a = 0;
  
    printf("%d\n",++b,++a);
    
}

int main(int argc, char const *argv[])
{
    func();
    return 0;
}
  • 静态数据从程序开始运行时便已存在,直到程序退出时才释放。
#include<stdio.h>

void func()
{
    //static修饰的变量只初始化一遍
    //生命周期为程序周期,局部函数结束不释放空间
    int a = 10;
    printf("%d\n",++a);
    
}
int *func2()
{
    /* int a = 10;
    return &a;//段错误 */

    static int a = 10;
    return &a;
}

int main(int argc, char const *argv[])
{
    int *ret = func2();
    printf("%d\n",*ret);
    return 0;
}
注意2:
  • static修饰局部变量:使之由栈内存临时数据,变为静态数据

#include<stdio.h>

void func()
{
    //static修饰的变量只初始化一遍
    //生命周期为程序周期,局部函数结束不释放空间
    int a = 10;
    printf("%d\n",++a);
    
}
int *func2()
{
    /* int a = 10;
    return &a;//段错误 */

    static int a = 10;
    return &a;
}

int main(int argc, char const *argv[])
{
    int *ret = func2();
    printf("%d\n",*ret);
    return 0;
}
  • static修饰全局变量:使之由个文件可见的静态数据,变成为本文件可见的静态数据
func.c
#include"func.h"
//static 的作用是限制某个变量或某个函数只能在本文件生效

static int a = 20;
void func()
{
    printf("a:%d\n",a);
    return 0;
}


main.c
#include"func.h"

static int a = 20;
int main(int argc, char const *argv[])
{
    func();
    func();
    func();
    return 0;
}


func.h
#ifndef _FUNC_H
#define _FUNC_H
#include <stdio.h>

void func();


#endif
  • static修饰的函数:使之由各文件可见的函数,变成为本文件可见的静态函数。
func.h
#ifndef _FUNC_H
#define _FUNC_H
#include <stdio.h>
//函数声明
static void func();


#endif

func.c
#include"func.h"
//static 的作用是限制某个变量或某个函数只能在本文件生效

static int a = 20;
static void func()
{
    printf("a:%d\n",a);
   
}

main.c
#include"func.h"

static int a = 10;

static void func()
{

}
int main(int argc, char const *argv[])
{
    func();
    printf("a:%d\n",a);
    return 0;
}

堆内存(重点,一定要掌握)

  • 堆内存基本特征:
    • 相比栈内存,堆的总大小仅受限于物理内存,在物理内存允许的范围内,系统对堆内存的申请不做限制。

    • 相比栈内存,堆内存从下往上增长。

    • 堆内存是匿名的,只能由指针来访问。

    • 自定义分配的堆内存,除非开发者主动释放,否则永不释放,直到程序退出。

相关API:
  • 申请堆内存:malloc() / calloc()

  • 清零堆内存:bzero()

  • 释放堆内存:free()

 

int *p = malloc(sizeof(int)); // 申请1块大小为 sizeof(int) 的堆内存
bzero(p, sizeof(int));        // 将刚申请的堆内存清零

*p = 100; // 将整型数据 100 放入堆内存中
free(p);  // 释放堆内存

// 申请3块连续的大小为 sizeof(double) 的堆内存
double *k = calloc(3, sizeof(double));

k[0] = 0.618;
k[1] = 2.718;
k[2] = 3.142;
free(k);  // 释放堆内存
注意:
  • malloc()申请的堆内存,默认情况下是随机值,一般需要用 bzero() 戳中memset()来清零。

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

int main(int argc, char const *argv[])
{
    // 手动申请堆空间
    int *ptr = malloc(5*sizeof(int));
    if(ptr == NULL)
    {
        // 将错误信息输出
        perror("malloc fialed:");
        return -1;
    }
    
    // 清空堆
    //bzero(ptr,5*sizeof(int));
    memset(ptr,0,5*sizeof(int));

    ptr[0] = 10;
    ptr[1] = 22;
    ptr[2] = 33;
    ptr[3] = 44;
    ptr[4] = 55;

    printf("%d\n",ptr[1]);

    int buf[5] = {100,200,300,400,500};
    //ptr[0] = buf[0];
    // strcpy拷贝的内容一定是字符串,非字符串不能使用
    //strcpy(ptr,buf);

    // 内存拷贝memcpy
    memcpy(ptr,buf,5*sizeof(int));
    printf("%d\n",ptr[2]);

    // 手动释放空间
    free(ptr);//将ptr与堆空间断开
    ptr = NULL; // 防止ptr成为野指针

    printf("%d\n",ptr[2]);

    return 0;
}
#include<stdio.h>
#include<stdlib.h>

int main(int argc, char const *argv[])
{
    //申请堆空间,可以存放100个char类型的数据
    char *str = malloc(sizeof(char)*100);
   // str = "jack";   //str指向字符串常量,写法异常
    //将字符串进行拷贝
    memcpy(str,"jack",5);
    str[2] = 'g';
    printf("%s\n",str);


    //申请堆空间用于存放整数
    //static int a[10];
    int a[10] = {1,2,3,4,5};
    int *int_p = malloc(sizeof(int)*10);
    //手动清空malloc
    memset(int_p,0,sizeof(int)*10);
    // 或 
    //int *int_p = NULL;
     
    int_p[0] = 10;//*int_p = 10;
    int_p[1] = 20;//*(int_p+1) = 20;
    int_p[2] = 30;
    printf("%d\n",int_p[2]);
    //内存拷贝
    memcpy(int_p,a,sizeof(a)/sizeof(a[0])*sizeof(int));
    printf("%d\n",int_p[1]);

 
    //释放堆空间,释放空间后无法通过int_p访问堆空间
    //注意free不是吧int_p指向的空间删除,只是断开联系而已
    free(int_p);
    int_p = NULL;
    //段错误
   //memcpy(int_p,a,sizeof(a)/sizeof(a[0]));
   //printf("%d\n",int_p[1]);

    return 0;
}

calloc()申请的堆内存,默认情况下是已经清零了的,不需要再清零。

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

int main(int argc, char const *argv[])
{
    // 申请堆空间
    char *p = calloc(5,sizeof(char));
    if(p == NULL)
    {
        perror("calloc failed:");
        return -1;
    }
    // 错误,不能直接赋值,会改变p所指向的地址
    //p = "jack";
    //printf("%s\n",p);

    // 清空堆空间
    memset(p,0,5);
    
    strcpy(p,"rose");
    printf("%s\n",p);

    // 清空堆空间
    memset(p,0,5);

    memcpy(p, "ken",3);
    printf("%s\n",p);

    // 释放空间
    free(p);
    p = NULL;

    return 0;
}
realloc()申请的堆内存,在原来内存基础上可进行扩容。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    // 申请堆空间
    char *p = calloc(1,sizeof(char));
    if(p == NULL)
    {
        printf("calloc failed:");
        return -1;
    }

    char buf[] = "afjoiajfajflajlfjalflafjslfjsljfls";

    // 扩容,如果不扩容会溢出
    char *ptr = realloc(p,100);
    strcpy(ptr,buf);
    printf("%s\n",ptr);


    return 0;
}

free()只能释放堆内存,并且只能释放整块堆内存,不能释放别的区段的内存或者释放一部分堆内存。

释放内存的含义:
  • 释放内存意味着将内存的使用权归还给系统。

  • 释放内存并不会改变指针的指向。

  • 释放内存并不会对内存做任何修改,更不会将内存清零。

静态函数

静态函数:只能在定义的文件内可见的函数,称为静态函数。

// 在函数头前面增加关键字static,使之成为静态函数
static void f(void)
{
    // 函数体
}

要点:

  • 静态函数主要是为了缩小函数的可见范围,减少与其他文件中重命名函数冲突的概率。

  • 静态函数一般被定义在头文件中,然后被各个源文件包含。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值