C语言快速入门
前言
== 用例题读懂C语言 ==
C语言函数分为:
- 库函数
- 自定义函数
那么什么是函数呢?
- 函数可以理解成一个机器;
- 输入某些东西;
- 经过机器加工后;
- 输出某样东西;
一、库函数
库函数是C语言规定好的函数,我们会用就行,库函数被封装在不同的头文件中,使用某个库函数时,引用其头文件即可
(一)库函数分类
函数 | 举例 |
---|---|
IO函数 | getchar() |
字符串操作函数 | strlen() |
字符操作函数 | toupper() |
内存操作函数 | memcpy() |
时间日期函数 | time() |
数学函数 | sqrt() |
其他库函数 | … |
(二)库函数使用指南
可使用一下网站查询:
二、自定义函数
构成循环结构有三种语句:
- while 循环语句
- for 循环语句
- do while 循环语句
(一)函数的定义
|~|一|~|
返回值类型 函数名(入口参数1,入口参数2)
{
函数体;
}
/eg:定义加和函数
int sum(int a,int b)
{
int sum=a+b;
return sum;
}
(二)函数的调用
- 传值调用
- 传指调用
函数名();
/eg:调用加和函数
sum(x,y);
以下文例子中的例题讲解区别:
#include<stdio.h>
void swap1(int x, int y)
{
int z = 0;
z = x;//放空瓶 x放Z里 X空了
x = y;//放空瓶 Y放X里
y = z;//交换x,y 放y里//
}
void swap2(int* pa, int* pb)//传过来的是地址 怎么接收地址呢 函数传递参数定义为指针变量
{
int z = 0;
z = *pa;
*pa = *pb;
*pb = z;
}
int main()
{
int a = 10;
int b = 30;
printf("交换前a= %d b=%d\n", a, b);
swap1(a,b);//传值调用//形参和实参无必然联系,调用时,形参只是实参的临时拷贝
printf("方法1交换后a= %d b=%d\n", a, b);
swap2(&a, &b);//传指调用//形参变量为指针,使得自定义函数可以和函数外部的值取得联系,也就是说函数内部可以操作函数外的变量
printf("方法2交换后a= %d b=%d\n", a, b);
return 0;
}
(三)函数的传入参数
- 实参:调用函数时传递进去的参数
- 形参:函数定义时定义的参数,只在函数内部有效,等同于局部变量
#include<stdio.h>
int sum(int a, int b)//形式参数-形参//只有调用函数时,才会给形式参数分配空间,调用时创建空间,出函数括号空间销毁,等同于局部变量的空间,只在函数内有效
{
int sum = a + b;
return sum;
}
int main()
{
int x = 0;
int y = 0;
int he = 0;
printf("请输入两个数:\n");
scanf("%d %d", &x, &y);
he = sum(x, y);//传入实际的参数-实参//实参可以是传递常量,变量,表达式,函数//得有确定的值
printf("%d ",he);
return 0;
}
(四)自定义函数例子
前置指示:指针
/*
指针类型 能保存两种信息
指针类型是通过值来保存目标数据对象的首地址(指针类型的值就是目标数据对象的首地址)
通过类型本身来标记目标数据对象的空间大小(如:int纪录占用字节数为4个字节、char纪录占用1个)
*/
#include<stdio.h>
int main()
{
int a = 10;
//1.取a的地址&a
//2.存储到一个存储地址的变量中,这个变量叫做指针变量 指针定义了数据类型和大小 存的是整数的地址 int* pa = &a;
//3.怎么通过这个存在Pa中地址找到其中的值呢 *pa 联系起来了 Pa和a
int* pa = &a;
printf("通过地址改值前:a=%d\n", a);
*pa = 90;
printf("通过地址改值后:a=%d\n",a);
return 0;
}
- 用函数实现求两个数最大值
#include<stdio.h>
int get_max(int x, int y)//自定义,得到较大数函数
{
int z = 0;
z = x > y ? x : y;//三目运算,表达式1?表达式2:表达式3;先执行表达式1,执行完毕,表达式1的结果如果为真,那么执行表达式2,并且这个整体的运算式的结果是表达式2的结果,否则执行表达式3,运算式的结果是表达式3的结果
return z;
}
int main()
{
int a = 28;
int b = 10;
int max = get_max(a,b);//函数的调用 返回了大的数
printf("%d", max);
return 0;
}
- 用函数交换两个数的值
//错误代码:函数执行,开辟了a和b的空间,执行到swap函数时,又开辟了x和y的空间,这两个空间的地址是不同的,也就是说只是交换了x和y地址中的值,而主函数中定义的变量的值没有被交换
#include<stdio.h>
//在调用swap时,实参传给形参,其实形参是实参的一份临时拷贝,创建了单独的临时空间,拷贝了实参的值
//所以改变形参,不能改变实参
void swap(int x, int y)
{
int z = 0;
z = x;//放空瓶 x放Z里 X空了
x = y;//放空瓶 Y放X里
y = z;//交换x,y 放y里//
}
int main()
{
int a = 10;
int b = 20;
printf("转换前:a=%d b=%d\n", a, b);
swap(a, b);
printf("转换后:a=%d b=%d\n", a, b);
return 0;
}
//正确代码
#include<stdio.h>
void swap(int* pa, int* pb)//传过来的是地址 怎么接收地址呢 函数传递参数定义为指针变量
{
int z = 0;
z = *pa;
*pa = *pb;
*pb = z;
}
int main()
{
int a = 10;
int b = 30;
printf("交换前a= %d b=%d\n", a, b);
swap(&a,&b);//把值传进去换不行,因为传进去后的值并不在主函数定义的a,b所对应的地址内,无法操作这两个数的交换,所以传递进去a和b的地址,
//设计函数时,如果要联系起来主函数和函数中的值 就传地址
printf("交换后a= %d b=%d\n", a, b);
return 0;
}
什么时候用指针作为参数?
- 想改变 在主函数中调用函数时传进去的参数值时,应将传递的参数变为指针类型
- 用函数打印100-200间素数
- 素数又称质数。所谓素数是指除了 1 和它本身以外,不能被任何整数整除的数,例如17就是素数,因为它不能被 2~16 的任一整数整除。
- 其他自然数b-素数a
- 若整数a除以非零整数b,商为整数,且余数为零,我们就说a能被b整除(或说b能整除a)
a为被除数,
b为除数,即b/a,读作“b整除a”或“a能被b整除”。
/*思路*/
/打印100-200素数
/1.产生100-200数
/2.调用函数判断这些数是否是素数
/3.编写实现功能函数
/4.over
#include<stdio.h>
int is_prime(int n)
{
int i = 0;
for (i = 2; i < n; i++)//遍历出从2到这个数本身-1的数字,依次取余
{
if (n % i == 0)//n除以除了他本身//不满足if跳出if 跳出for 返回1
{
return 0;//被整数不是素数 返回0//
}
//return 1;放在这里行不行 ?不行 都得遍历一遍 确定这些都不能被整除才返回1表示是素数 放这里如果 n =105,只判断了i=2的情况,因为不满足if 直接跳出循环了
}
return 1;//是素数 返回0//没有return 0才能证明 他没有被整数 进而证明 是素数
}
int main()
{
int i = 0;
int cnt = 0;
for (i = 100; i <= 200; i++)
{
if (is_prime(i) == 1)
{
cnt++;
printf("%d是素数\n", i);
}
}
printf("\n 共有素数%d个", cnt);
return 0;
}
- 输入一个数判断其是不是素数
#include<stdio.h>
int is_prime(int n)
{
int i = 0;
for (i = 2; i < n; i++)//遍历出从2到这个数本身-1的数字,依次取余
{
if (n % i == 0)//n除以除了他本身//不满足if跳出if 跳出for 返回1
{
return 0;//被整数不是素数 返回0//
}
//return 1;放在这里行不行 ?不行 都得遍历一遍 确定这些都不能被整除才返回1表示是素数 放这里如果 n =105,只判断了i=2的情况,因为不满足if 直接跳出循环了
}
return 1;//是素数 返回0//没有return 0才能证明 他没有被整数 进而证明 是素数
}
int main()
{
int i = 0;
int cnt = 0;
scanf("%d", &i);
if (is_prime(i) == 1)
{
cnt++;
printf("%d是素数\n", i);
}
else//有无else的区别是 printf("%d不是素数\n", i);会不会一直输出
{
printf("%d不是素数\n", i);
}
return 0;
}
- 输入一个数判断其是不是素数,但要求可以一直输入,直到主动退出程序
/*思路*/
//
//1.选择菜单
//2.选择菜单程序
// --1.写入数据判断
// --0.退出程序
//3.写case1判断函数
// --1.如果返回1 是素数 打印
// --2.如否则判断i=0?等于 执行退出程序
// --3.都不满足 不是素数 打印
//4.over
#include<stdio.h>
int is_prime(int n)
{
int i = 0;
for (i = 2; i < n; i++)//遍历出从2到这个数本身-1的数字,依次取余
{
if (n % i == 0)//n除以除了他本身//不满足if跳出if 跳出for 返回1
{
return 0;//被整数不是素数 返回0//
}
//return 1;放在这里行不行 ?不行 都得遍历一遍 确定这些都不能被整除才返回1表示是素数 放这里如果 n =105,只判断了i=2的情况,因为不满足if 直接跳出循环了
}
return 1;//是素数 返回0//没有return 0才能证明 他没有被整数 进而证明 是素数
}
void menu()
{
printf("***********************\n");
printf("****1.开始输入*********\n");
printf("****0.退出程序*********\n");
printf("***********************\n");
}
int main()
{
int i = 0;
int cnt = 0;
int input = 0;
menu();
scanf("%d", &input);
do
{
switch (input)
{
case 1:
printf("情输入数字:\n");
scanf("%d", &i);
if (i == 0)
{
printf("%d啥也不是\n", i);
printf("程序已退出\n");
}
else if (is_prime(i) == 1)
{
printf("%d是素数\n", i);
}
else
{
printf("%d不是素数\n", i);
}
break;
case 0:
printf("程序已退出\n");
break;
default:
printf("输入错了\n");
break;
}
if (i == 0)
{
input = 0;
}
}while(input);
return 0;
}
- 判断1000-2000中哪些是闰年
/*思路*/
/1.for循环产生1000-2000
/2.将每个数送到函数中判断
/ --1.是闰年返回1
/ --2.不是闰年返回2
/能被4整除但不能被100整除的年份为普通闰年
#include<stdio.h>
int is_leap_year(int n)
{
if ((n % 4 == 0 && n % 100 != 0) || n % 400==0 )
{
return 1;
}
else
{
return 0;
}
}
int main()
{
int i = 0;
for (i = 1000; i <= 2000; i++)
{
if (is_leap_year(i) == 1)
{
printf("%d是闰年\n", i);
}
}
return 0;
}
- 二分法用函数查找有序数组
/*思路*/
//1.定义有序数组
//2.定义被查找的数
//3.调用二分查找函数
//4.自定义函数功能binary_search
// -1.找到了就返回查找数字的下标
// -2.没找到返回-1
//5.函数思路
// -1.定义左下标 右下标 中间下标
// -2.利用中间下标对应的数字 和目标数字进行比较
// -3.如果中间数字大于目标数字--要查找的数字在左边 那么使右下标-1
// -4.如果中间数字小于目标数字--要查找的数字在右边 那么使左下标+1
// -5.否则返回下标数字
// -6.将上述步骤循环 直到左右下标交叉
// -7.如果都查找完了 退出了循环 说明都没找到 那么就返回-1吧
#include<stdio.h>
int binary_search(int a[],int n,int s)//为什么要把数组长度也传递进来呢 见下文:
{
int left = 0;
int right = s - 1;
while(left<=right)
{
int mid = (left + right) / 2;
if (a[mid] > n)
{
right = mid - 1;
}
else if (a[mid] < n)
{
left = mid + 1;
}
else
{
return mid;
}
}
return -1;
}
int main()
{
int arr[] = {
1,2,3,4,5,6,7,8,9,10 };
int k = 7;
int sz = sizeof(arr) / sizeof(arr[0]);
int ret = binary_search(arr, k, sz);//实现这个函数 得告诉他 查哪个数组 查哪个数 这个数组一共有多少元素
//数组arr传参 实际上传递的不是数组本身
//只是传进去了数组的首地址 也就是说 定义函数时参数 本质上是指针int binary_search(int *a,int n,int s)所以如果在函数内求的话sizeof(a)求的指针的大小 4字节 /4 =1
//所以传递数组参数时 不会将整个数组值传进去 只传递的是首地址 所以想求数组的大小 不能在函数里面求 要在外面求出来数组大小 作为参数传进去
if (ret == -1)
{
printf("没找到\n");
}
else
{
printf("找到了 下标是:%d", ret);
}
return 0;
}
- 编写一个函数,每调用一次函数,num值就会加一
#include<stdio.h>
void Add(int *a)//用指针接收地址
{
(*a)++;
}
int main()
{
int num = 0;
Add(&num);//想改变num的值 所以传地址进去
printf("%d\n", num);
Add(&num);//想改变num的值 所以传地址进去
printf("%d\n", num);
Add(&num);//想改变num的值 所以传地址进去
printf("%d\n", num);
Add(&num);//想改变num的值 所以传地址进去
printf("%d\n", num);
return 0;
}
三、函数的嵌套调用和链式访问
(一)函数的嵌套调用
//函数的嵌套调用
#include<stdio.h>
void text_2()
{
printf("挺住 坚持 加油\n");
}
int text_1()
{
text_2();
return 0;
}
int main()
{
text_1();
return 0;
}
(二)函数的链式访问
#include<stdio.h>
#include<string.h>
int main()
{
int ch = strlen("abcd");
printf("%d\n", ch);
//函数的链式访问
printf("%d\n", strlen("abcdef"));
return 0;
}
#include<stdio.h>
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
//4321结果
//printf 返回的是打印在控制台上的个数
//43 两个 2
//2 一个 1
return 0;
}
四、函数的声明和定义
(一)函数的声明
1.告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,无关
紧要。
2.函数的声明一般出现在函数的使用之前。要满足先声明后使用。
3.函数的声明一般要放在头文件中的。
4.便捷声明:自定义函数第一行复制粘贴在前面,加个分号。扫描二维码关注公众号,回复: 17496649 查看本文章
(二)函数的定义
test.h的内容
放置函数的声明
#ifndef __TEST_H__
#define __TEST_H__
//函数的声明
int Add(int x, int y);
#endif //__TEST_H__
test.c的内容
放置函数的实现
#include "test.h"
//函数Add的实现
int Add(int x, int y)
{
return x+y;
}
五、函数的递归
(一)函数的递归的定义
函数自己调用自己就是递归
一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。
递归策略:
只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归的主要思考方式在于:把大事化小
(二)函数的递归的必要条件
- 存在限制条件,当满足这个限制条件的时候,递归便不再继续。
- 每次递归调用之后越来越接近这个限制条件。
- 理解:类比 循环中的判断条件 和 调整部分
(三)函数的递归的例子
挖坑 后期有机会填
(四)关于栈溢出的理解
每一个函数调用 都得在栈区分配一块空间递归调用时,每次调用都重新分配空间,会出现栈溢出的情况
写递归Tips:
- 不能死递归,要有跳出条件,且每次递归都逼近跳出条件
- 递归层次不能太深