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