c基础 函数

函数

1.函数基本用法

1.1定义和三要素

函数是一个完成特定功能的代码模块,其程序代码独立,通常要求有返回值,也可以是空值。

三要素:功能 参数 返回值

参数:参数就是在函数声明时和函数调用时定义的变量。它用于传递信息给函数。

【1】实参是在函数调用中实际传递给函数的参数;而形式参数是函数声明中接受实参的参数。实参是有具体的数据。

【2】形参只是一个声明的形式,可以理解为一个符号名字。

返回值:函数返回值是函数调用后唯一留下的右值。(右值:只能放在运算符右边)

1.2函数的声明和定义

函数的声明: 存储类型 数据表类型 函数名(数据类型 形参1,数据类型 形参2,...) ;

函数定义格式:

存储类型 数据表类型 函数名(数据类型 形参1,数据类型 形参2,...) ;

{

        函数体;

}

其中:

函数名称:一个标识符

数据类型:是整个函数返回值类型,如果没有就用void

形式参数说明逗号分隔多个变量说明形式通常简称为形参

形参:形式参数就是声明函数时指函数名后括号中的变量,因为形式参数只有在函数调用的过程中才实例化(分配内存单元),所以就叫形式参数。

大括弧对 {语句序列 },称为函数体,语句序列是大于等于零个语句构成

注意:在函数体中,表达式语句里使用的变量必须事先已有说明,否则不能使用。

return后面加表达式,语句中表达式的值,要和函数的<数据类型>保持一致。

总结函数数据

没有参数参数列表可以省略也可以写成void

没有返回值就写void,函数内部没有return

有返回值 根据 返回值 数据 类型 定义 函数 数据 类型

定义子函数时可以直接定义在主函数上面,如果定义在主函数下面需要提前声明函数

类型void可以省略或者表达式结果返回(即写成return;)

1.3函数调用

1.没有返回值:直接调用:函数名(实参)

2.有返回值 如果需要接收返回值就要函数定义一个返回值类型相同变量return 返回出去

实参:在调用有参函数时,函数名后面括号中的参数称为“实参”,是我们真实传给函数的参数,实参可以是:常量、变量、表达式、函数等。

无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。

例:

编写一个函数,函数的2个参数,第一个是一个字符,第二个是一个char *,

返回字符串中该字符的个数。

#include <stdio.h>
#include <string.h>
int geshu(char a,char *p){
    int num=0;
    for(int i=0;i<strlen(p);i++){
        if(a==p[i]){
            num++;
        }
    }
    return num;
}
int main(){
    char s[32]="";
    char a;
    char *p=s;
    scanf("%c",&a);
    gets(s);
    int g=geshu(a,p);
    printf("%d",g);

}

1.4 函数传参

 值传递

单向传递实参这个数据传递形参使用函数改变形参函数外的实参不会受影响,因为他们属于不同空间

#include <stdio.h>

void fun(int a, int b)   //a=10  b=30
{
    a++;  //a=11
    b++;  //b=31
    printf("in fun: %d %d\n", a, b);  //11 31
}

int main(int argc, char const *argv[])
{
    int n1 = 10, n2 = 30;
    fun(n1, n2);
    printf("in main: %d %d\n", n1, n2);  //10 30

    return 0;
}

地址传递

双向传递函数通过传递地址修改地址所指空间内容实参随之变化

#include <stdio.h>

void fun(int *a, int *b)  //a=&n1 b=&n2
{
    (*a)++;           //*a =*&n1 = n1       
    (*b)++;           //*b = *&n2 = n2
    printf("in fun: %d %d\n", *a, *b);   //11 31
}

int main(int argc, char const *argv[])
{
    int n1 = 10, n2 = 30;
    fun(&n1, &n2);  //n1=11  n2=31
    printf("in main: %d %d\n", n1, n2);   //11 31

    return 0;
}

数组传递

和地址传递一样,参数中存在地址,也就是指针。

#include <stdio.h>

void fun(int *p, int n) //p=a
{
    p[2] = 100;                 //a[2] =100
    printf("fun: ");
    for (int i = 0; i < n; i++)
        printf("%d ", p[i]); //或者用*(p+i)
    printf("\n");
}

int main(int argc, char const *argv[])
{
    int a[5] = {1, 2, 3, 4, 5};
    fun(a, 5);

    printf("main: ");
    for (int i = 0; i < 5; i++)
        printf("%d ", a[i]); //1 2 100 4 5
    printf("\n");

    return 0;
}

1.5 函数和栈区(stack)

 栈区的概念

栈用来存储函数内部的变量(包括main()函数)。它是一个FILO(First In Last Out,先进后出)的结构。每当一个函数声明一个新的变量它将被压入栈中。当一个函数运行结束后,这个函数所有在栈中相关的变量都将被删除,而且它们所占用的内存将会被释放。这就产生了函数内部的局部变量。栈区是一段非常特殊的内存区,它由CPU自动管理,所以你不必手动申请和释放内存。

内存由系统自动申请,在变量生命周期结束时由系统释放,也就是说,在程序运行的时候,系统有多个任务,就是检测变量是否该释放了,简单来说,就是cpu要抽时间去执行这部分功能。所以,如果这种变量比较多,不加节制的定义的话,那CPU的额外的工作量就会加大,综合下来,程序的运行效率就会低下。

 栈区(stack)的总结

 栈由CPU管理,无法修改

 变量自动地分配和释放

 栈并非没有限制,大部分栈都有一个上边界

 栈随着变量地产生和销毁生长和收缩

 栈区变量只有在函数创建它们

 2.开辟堆区(heap)空间

2.1 堆区概念

申请的空间种分为五个区域栈区(堆栈区),堆区,全局区,常量区,代码区,我们之前讲的这些定义变量、数组都是在内存的栈区存储。

堆区的特点:由我们程序员随时申请,由我们自己随时释放。

2.2 栈区的特点

堆区的特点:由我们程序员随时申请,由我们自己随时释放。

堆和栈的主要区别有:

 栈由系统自动分配,而堆是人为申请开辟;

 栈获得的空间较小,而堆获得的空间较大;

 栈由系统自动分配,速度较快,而堆一般速度比较慢;

 栈是连续的空间,而堆是不连续的空间是随机分配

 3.malloc 函数

3.1 定义

用man手册查看:

#include <stdlib.h>

void *malloc(size_t size);

功能:在堆区开辟大小为size的空间

参数:size:开辟空间的大小(单位:字节)

返回值:

成功:返回开辟空间的首地址

失败:NULL;

malloc()要和free()搭配使用

size_t大小:printf("%d\n",sizeof(size_t)); //4字节

3.2 用法

malloc内的参数是需要动态分配的字节数,而不是可以存储的元素个数!

当动态分配内存时,存储的是字符型数据,每个元素1字节,所以字节数刚好等于需要存储的元素个数(字符数+1);

如果存储的是整型或浮点型数据,字节数等于需要存储的元素个数 * 一个元素的字节数

代码格式

数据类型 *指针名 = (数据类型 *)malloc(n*sizeof(数据类型));

3.3 free()函数定义

#include <stdlib.h>

void free(void *ptr);

功能:释放之前调用 calloc、malloc 或 realloc 所分配的内存空间。

参数:ptr:堆区空间首地址

返回值:无

可以释放以后赋值空指针

free(p); //p成为野指针

p=NULL; //防止使用野指针

注意:

手动开辟堆区空间,要注意内存泄漏

当指针指向开辟堆区空间后,又对指针重新赋值,则没有指针指向开辟的堆区空间,就会造成内存泄漏。

使用完堆区空间后及时释放空间

3.4函数中开辟堆区的地址

以下代码

报段错,原因: 函数调用结束相当于销毁申请空间不会保留函数内部变量所以函数调用结束后p没了开始通过传参main函数q也就是NULL传递p也就是p指向NULL 函数内改变p指向指向堆区但是不会影响函数q因为qp两个空间所以函数调用结束后q改变还是指向NULL打印空指针段错误

解决方法1 通过返回

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

char *fun()
{
    char *p = (char *)malloc(32);
    strcpy(p, "hello");
        
    return p;
}

int main(int argc, char const *argv[])
{
    char *q = fun(); //q接收了函数的返回值也就是p保存的堆区地址
    printf("%s\n", q);
    free(q);

    return 0;
}

解决方法2 通过传参, 传递二级指针#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void fun(char **p)   //p=&q
{
    *p = (char *)malloc(32);   //*p = *&q =q

    strcpy(*p, "hello");
}

int main(int argc, char const *argv[])
{
    char *q = NULL;
    fun(&q);  //&q类型是char **
    printf("%s\n", q); 
    free(q);
    return 0;
}
解释: 通过传递二级指针&q也就是q地址传递fun函数p接收也就是p=&q也就是p指向q通过*p可以访问q*p重新赋值相当于q重新赋值所以函数*p(也就是q)指向堆区函数调用结束函数p销毁但是q已经指向堆区

4.string函数族

4.1strcpy

#include <string.h>

char *strcpy(char *dest, const char *src);

功能:把整个字符串复制,包括\0。

参数: char *dest :目标字符串首地址

const char *src:源字符串首地址

返回值:目标字符串首地址

4.2strcat

#include <string.h>

char *strcat(char *dest, const char *src);

功能:用于字符串拼接

参数:

char *dest:目标字符串首地址

const char *src: 源字符串首地址

返回值:目标字符串首地址

 4.3strlen

#include <string.h>

size_t strlen(const char *s);

功能:计算字符串长度

参数:s:字符串的首地址

返回值:返回字符串实际长度,不包括‘\0’在内。

 4.4strcmp

#include <string.h>

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

功能:用于字符串比较

参数:s1 s2用于比较多字符串

返回值:

从字符串首个字符开始比较字符ASCII的大小,如果相等继续向后判断。

正数: s1>s2

0 : s1== s2

负数: s1<s2

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值