当前位置: 首页 > news >正文

C语言基础10——函数

1. 函数

1.1 定义

一个完成特定功能的代码模块

1.2 三要素

功能、参数、返回值

1.3 定义格式

存储类型 数据类型 函数名(参数列表) // 形参

{

        函数体; // 函数功能

        return; // 函数返回值

}

(1)没有参数:参数列表可以省略,也可以使用void
(2)没有返回值:函数内部没有return语句,数据类型为 void
(3)有返回值:要根据返回值的数据类型定义函数的数据类型,并带有return语句
(4)定义子函数时直接定义在主函数上面,如果定义在主函数下面需要提前函数声明

1.4 函数声明

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

函数可以在主函数上当进行定义,也可以在主函数下面进行定义,若在主函数下方进行定义,需要在主函数上方进行声明。

#include <stdio.h>
#include <string.h>void fun1();             //声明函数,但是并不对函数的内部进行编写int main()               //主函数 
{fun1();              //调用函数return 0;
}void fun1()              //编写函数内容
{printf("hello\n");
}

1.5 函数调用

(1)没有返回值:直接调用:函数名(实参);
(2)有返回值:如果需要接受返回值,就要定义一个与返回值类型相同的变量去接受,如果不需要返回值,就可以直接调用函数

#include <stdio.h>
#include <string.h>void fun(void)
{printf("is a function\n");
}//求两数之和
void add1(int n, int m)//形参
{printf("%d\n", n + m);
}//求两数之和
int add2(int n, int m)
{return n + m;
}int sub(int n, int m);int main()
{int a = 4, b = 6;//调用fun();add1(2, 3);int num = add2(a, b);printf("num = %d\n", num);int ret = sub(1, 2);printf("ret = %d\n", ret);return 0;
}int sub(int n, int m)
{printf("我在主函数下面\n");int num = n + m;return num;
}

练习1:编写一个函数,函数的2个参数,第一个是一个字符,第二个是一个char *,返回字符串中该字符的个数。(参考代码见本文最后)

1.6 函数传参

1.6.1 值传递

单向传递,将实参传递给形参之后,改变形参实参不受影响

#include <stdio.h>
#include <string.h>int fun (int n, int m)
{n++;m++;return n + m;
}int main()
{int a = 3; int b = 4;int ret = fun(a, b);printf("%d %d %d\n", a, b, ret);    //3,4,9return 0;
}

1.6.2 地址传递

双向传递,函数数中修改形参,实参会随之变化 (通过地址间接修改内容)

#include <stdio.h>
#include <string.h>int fun (int *n, int *m)                //定义指针变量,因为要传入的是地址
{*n = *n + 2;*m = *m + 3;return *n + *m;
}int main()
{int a = 3; int b = 4;               //要传地址int ret = fun(&a, &b);printf("%d %d %d\n", a, b, ret);    //5,7,12return 0;
}

1.6.3 数组传递

和地址传递一样,虽然参数当中存在数组的定义,但是会认为是一个指针

#include <stdio.h>
#include <string.h>void fun(char s[32])
{printf("%d\n", sizeof(s));    //8
}int main()
{fun("hello");return 0;
}

1.6.4 补充

char *p = "hello";

// p 在栈区开辟8字节空间存放字符串常量"hello"的首地址

char buf[32]="hello";

//buf:在栈区开辟32字节空间,存放"hello"字符串

2. 动态内存开辟(开辟堆区空间)

为什么存在动态内存开辟

<1>在技术方面,普通的空间申请,都是在全局或者栈区,全局一般不太建议大量使用,而栈空间有限,那么如果一个应用需要大量的内存空间的时候,需要通过申请堆空间来支持基本业务。

<2>在应用方面,程序员很难一次预估好自己总共需要花费多大的空间。想想之前我们定义的所有数组,因为其语法约束,我们必须得明确"指出"空间大小.但是如果用动态内存申请(malloc)因为malloc是函数,而函数就可以传参,也就意味着,我们可以通过具体的情况,对需要的内存大小进行动态计算,进而在传参申请,提供了很大的灵活性

2.1 开辟空间

在调用malloc函数之前,需要先调入头文件

#include <stdlib.h>

        void *malloc(size_t size);

功能:在堆区开辟空间

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

返回值:

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

        失败:NULL

2.2 释放空间

#include <stdlib.h>

        void free(void *ptr);

功能:释放堆区空间

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

返回值:无

int *p = malloc(size);
free(p);
// 对p进行释放的时候需要对p赋值一个NULL,
避免成为野指针
p = NULL;

【例】

#include <stdio.h>
#include <string.h>
#include <stdlib.h>int main()
{int n;scanf("%d", &n);int *p = (int *)malloc(n * sizeof(int));    //开辟n个int类型大小的字节//  p = &n;    不要中途改变p保存的地址//容错判断if(NULL == p){printf("开辟失败\n");return -1;}printf("开辟成功\n");for(int i = 0 ; i < n ; i ++){*(p+i) = i;}for(int i = 0 ; i < n ; i ++){printf("%d\n", p[i]);}//对空间使用完成之后要释放空间free(p);p = NULL;return 0;
}

2.3 注意

1. 手动开辟堆区空间,要注意内存泄漏
        
当指针指向开辟堆区空间后,又对指针重新赋值,则没有指针指向开辟堆区空间,就会造成内存泄漏
2. 使用完堆区空间后,及时释放空间 free

2.4 常见的动态内存错误

<1>对空指针的应用操作

<2>对动态开辟空间的越界访问

<3>对非动态开辟内存使用free释放

<4>使用free释放动态内存的一部分

<5>对同一动态内存多次释放

<6>动态开辟内存忘记释放(内存泄露)

思考1:如下代码输出结果。(参考见本文最下方)

void fun(char *p)

{

        p =char *malloc(32);

        strcpy(p, "hello");

}

main()

{

        char *m = NULL;

        fun(m);

        printf("%s\n", m);

}

3. string函数族

3.1 strcpy

#include <string.h>

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

功能:实现字符串的复制        (注意:赋值包括'\0')

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

        src:源字符串首地址

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

#include <stdio.h>
#include <string.h>
#include <stdlib.h>int main()
{char s[32] = "world nihao";char a[32] = "hello";strcpy(s, a);printf("%s\n", s);printf("%d\n", s[5]);printf("%c\n", s[6]);return 0;
} 
char *strncpy(char *dest, const char *src, size_t n);

功能:实现字符串的复制

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

        src:源字符串首地址

        n:要复制的字符个数

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

#include <stdio.h>
#include <string.h>
#include <stdlib.h>int main()
{char s[32] = "world nihao";char a[32] = "hello";strncpy(s, a, 2);printf("%s\n", s);return 0;
} 

3.2 strlen

#include <string.h>

        size_t strlen(const char *s);

功能:计算字符串的实际长度

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

返回值:实际长度

3.3 stract

#include <string.h>

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

功能:字符串拼接

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

        src:源字符串首地址

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

char *strncat(char *dest, const char *src, size_t n); 将src的前n个字符拼接到dest中

#include <stdio.h>
#include <string.h>
#include <stdlib.h>int main()
{char s[32] = "hello";char a[32] = "world";strcat(s, a);printf("%s\n", s);return 0;
} 

3.4 strcmp

#include <string.h>

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

功能:用于字符串比较

参数:s1, s2用于比较的字符串首地址

返回值:

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

s1         >         s2         正数         1

s1         ==       s2         0

s1         <         s2         负数         -1

int strncmp(const char *s1, const char *s2, size_t n); // 比较前n个字符的大小

#include <stdio.h>
#include <string.h>int main()
{char s1[] = "hello";char s2[] = "hello world";char s3[] = "nihao";char *s4[] = "shijie";char *s5[] = "hello";int ret = strcmp(s1, s2);         //  负数  s1 < s2int ret1 = strcmp(s1, s3);        //  负数  s1 < s3int ret2 = strcmp(s2, s4);        //  负数  s2 < s4int ret3 = strcmp(s3, s2);        //  正数  s3 > s2int ret4 = strcmp(s1, s5);        //  0     s1 == s5if(!strcmp(s1, s5))              //  !false == true{printf("相等\n");}return 0;
}

------------------------------------------------------这是一条分割线------------------------------------------------------

天才预设的上一篇跳转路径:C语言基础9——指针与数组-CSDN博客

天才预设的下一篇跳转路径:未完待续~

天才预设的合集传送路径:C基础_Gu_shiwww的博客-CSDN博客

参考答案

练习1:

编写一个函数,函数的2个参数,第一个是一个字符,第二个是一个char *,返回字符串中该字符的个数。(参考代码见本文最后)

#include <stdio.h>
#include <string.h>int charlen(char ch, char *s)
{int n = 0;while(*s != '\0'){if(*s == ch)n++;s++;}return n;
}int main()
{char str[32] = "hello";char ch = 'l';printf("请输入字符:");scanf("%[^\n]", str);        //字符数组的名字便是首元素地址printf("请输入要查找的字符:");getchar();scanf("%c", &ch);int charnum = charlen(ch, str);printf("查找的字符个数为:%d\n", charnum);return 0;
}

思考1:

如下代码输出结果。(参考见本文最下方)

void fun(char *p)

{

        p =char *malloc(32);

        strcpy(p, "hello");

}

main()

{

        char *m = NULL;

        fun(m);

        printf("%s\n", m);

}

代码出现段错误

因为fun函数内部开辟的空间的指针p是局部变量,会在函数调用结束之后便会被释放,属于局部开辟。而指针m拿到的又是NULL,输出的时候会打印NULL或者段错误

可以通过两种方式解决

思考1~1 通过传参

#include <stdio.h>void fun(char **p)              // p = &m
{*p = (char *)malloc(32);    //*p = mstrcpy(*p, "hello");
}int main()
{char *m = NULL;fun(&m);printf("%s\n", m);free(m);m = NULL;return 0;
}

思考1~2 通过返回值

#include <stdio.h>char *fun()
{char *p = (char *)malloc(32);strcpy(p, "hello");return 0;
}int main()
{char *m = fun();        //m == p == 开辟的空间地址——>helloprintf("%s\n", m);free(m);m = NULL;return 0;
}

http://www.dtcms.com/a/299327.html

相关文章:

  • Qt 与 WebService 交互开发
  • Linux学习 韦东山IMX6ULL(一)搭建环境并编译程序
  • SQL Developer Data Modeler:一款免费跨平台的数据库建模工具
  • 强化学习(第三课第三周)
  • Rust赋能土木工程数字化
  • AUTOSAR进阶图解==>AUTOSAR_SRS_TimeService
  • Raft 协议 Paxos协议 和zk协议的特点和异同
  • 【Pandas】pandas Index objects Index.shape
  • 【温度传感器】热电偶、热敏电阻、热电阻、热成像仪原理及精度解析
  • ASP.NET Core MVC中taghelper的ModelExpression详解
  • .NET Core中的配置系统
  • 记录和分享抓取的数字货币和大A时序数据
  • 去中心化时代的通信革命:briefing与cpolar技术融合带来的安全范式革新
  • AI代理性能提升实战:LangChain+LangGraph内存管理与上下文优化完整指南
  • 如何创建或查看具有 repo 权限的 GitHub 个人访问令牌(PAT)
  • kafka的消费者负载均衡机制
  • 《人性的弱点》重构【01】
  • Java:采用mybatis+pagehealper优雅的实现分页功能
  • Flutter 提取图像主色调 ColorScheme.fromImageProvider
  • Go 的时间包:理解单调时间与挂钟时间
  • SWC 深入全面讲解
  • 集成学习的相关理论阐述
  • RocketMQ学习系列之——特殊消息类型
  • 塞舌尔公司良好信誉证明Certificate of Good Standing证书的用途
  • 大众化餐饮:把日常过成诗
  • 基于POD和DMD方法的压气机叶片瞬态流场分析与神经网络预测
  • 幸福网咖订座点餐小程序的设计与实现
  • 启动式service
  • Java同步锁性能优化:15个高效实践与深度解析
  • ARM SMMUv3控制器初始化及设备树分析(七)