1.1 预备知识
-
#include <stdio.h>叫做“头文件
就是一条预处理命令, 它的作用是通知C语言编译系统在对C程序进行正式编译之前需做一些预处理工作。 -
文件就是“01流”,两种文件(二进制文件,纯文本文件)
-
一些文件操作函数:
-
fgetc/fputc ;fgets/fputs ;fprintf/fscanf(不推荐用,效率低)fread/fwrite ;rewind倒带函数
-
fseek随机存取; ftell告诉读取的位置 ;remove删除文件,不可恢复
-
解释:直接一条一条执行语句,编译:整个代码转换为计算机可懂语言。语言没有解释和编译的区别,只是习惯性的C语言编译执行,Python语言解释执行。
-
输入是以行为单位进行的,行的结束标志就是按下了回车键,在按下回车键之前,程序不会读到任何东西。
2.1变量
2.1.1 如何定义一个变量
- 定义:变量是一个保存数据的地方,方便参与后续运算
- 形式:类型名称 变量名称
- 定义多个变量:用,分隔就行
int price, amount;
- 命名:变量的名字是一种“标识符”,可以由 字母、数字、下划线组成,数字不能出现在首位,变量名中不能出现c语言的关键字(保留字)。
2.1.2 变量赋值与初始化
int price = 0
- “ = ”表示赋值,把=右边的值交给左边,第一次赋值叫初始化,有运算符的式子叫表达式。
- 如果不给变量做“初始化”赋值的话,变量的值就是所在内存地址有的数。
- 可以这样赋值:int price = 1,amount = 100;
- int price,amount = 100,age;
- 首次定义变量必须说明类型,并且定义之后该变量类型不能更改!
- C99可以在任何位置给变量赋值,ANSI C只能在代码开头赋值。
- scanf函数:scanf("%d",&price);用来读取终端输入的数字(如果读不到数字就默认为0),f指的是格式化,输出格式化的东西;&是取地址的意思,%d表示取的格式是整数,%f取的格式为浮点数。
2.1.3 关于scanf
——以下为引用他人的笔记片段(5条消息) C语言scanf函数用法完全攻略_jacklin_001的博客-优快云博客_scanf语句
- 功能:将从键盘输入的字符转化为“输入控制符”所规定格式的数据,然后存入以输入参数的值为地址的变量中。
- 字符串:字符串是一个或多个字符的序列(包括:数字、字母、空格等),双引号不是字符串的一部分。双引号仅告诉编译器它括起来的是字符串。
-
首先要清楚的是:我们从键盘输入的全部都是字符。比如从键盘输入 123,它表示的并不是数字 123,而是字符 '1'、字符 '2' 和字符 '3'。这是为什么呢?操作系统内核就是这样运作的。操作系统在接收键盘数据时都将它当成字符来接收的。这时就需要用“输入控制符”将它转化一下。
%d
的含义就是要将从键盘输入的这些合法的字符转化成一个十进制数字。经过 %d 转化完之后,字符 123 就是数字 123 了。在 printf 中,所有的“非输出控制符”都要原样输出。同样,在 scanf 中,所有的“非输入控制符”都要原样输入。所以没有必要写scanf("i = %d",&i);因为这样在终端还要原样输入i = ,少一个都不行。
所以 scanf 中
%d
后面也没有必要加\n
,因为在 scanf 中\n
不起换行的作用。它不但什么作用都没有,你还要原样将它输入一遍。所以在 scanf 的使用中一定要记住:双引号内永远都不要加“非输入控制符”。除了“输入控制符”之外,什么都不要加,否则就是自找麻烦。而且对于用户而言,肯定是输入越简单越好。
-
scanf函数中没有精度控制,如:scanf("%5.2f",&a);是非法的。不能企图用此语句输入小数为2位的实数。
- 一次给多个变量赋值:
# include <stdio.h>
int main(void)
{
int i, j;
scanf("%d%d", &i, &j);
printf("i = %d, j = %d\n", i, j);
return 0;
}
- 从键盘输入时,给多个变量赋的值之间一定要用空格、回车或者Tab 键隔开,用以区分是给不同变量赋的值。且空格、回车或 Tab 键的数量不限,只要有就行。一般都使用一个空格。
- 注意,scanf 中双引号内多个“输入控制符”之间千万不要加逗号!可以用空格。
-
注意,在 printf 中,“输出控制符”的类型可以与数据的类型不一致,但是在 scanf 中,对于从键盘输入的数据的类型、scanf 中“输入控制符”的类型、变量所定义的类型,这三个类型一定要一致,否则就是错的。虽然编译的时候不会报错,但从程序功能的角度讲就是错的,则无法实现我们需要的功能。比如:在 scanf 中,从键盘输入的一切数据,不管是数字、字母,还是空格、回车、Tab 等字符,都会被当作数据存入缓冲区。存储的顺序是先输入的排前面,后输入的依次往后排。按回车键的时候 scanf 开始进入缓冲区取数据,从前往后依次取。
但 scanf 中 %d 只识别“十进制整数”。对 %d 而言,空格、回车、Tab 键都是区分数据与数据的分隔符。当 scanf 进入缓冲区中取数据的时候,如果 %d 遇到空格、回车、Tab 键,那么它并不取用,而是跳过继续往后取后面的数据,直到取到“十进制整数”为止。对于被跳过和取出的数据,系统会将它从缓冲区中释放掉。未被跳过或取出的数据,系统会将它一直放在缓冲区中,直到下一个 scanf 来获取。
但是如果 %d 遇到字母,那么它不会跳过也不会取用,而是直接从缓冲区跳出。所以上面这个程序,虽然 scanf 进入缓冲区了,但用户输入的是字母 a,所以它什么都没取到就出来了,而变量 i 没有值,即未初始化,所以输出就是 –858993460。
但如果将 %d 换成 %c,那么任何数据都会被当作一个字符,不管是数字还是空格、回车、Tab 键它都会取回。
2.1.4 常量与变量
- 写法:const int 名称 = 数字 注意常量名称一般大写以示区分
- 举例:const int AMOUNT = 100;
- 用下划线分隔单词:const int STUDENT_OF_AMOUNT;
- 优点:便于读码人理解常量代表的是什么,后续只需书写AMOUNT不用写100了。也便于修改
- const:是一个修饰符,加在int前面,表示变量的值一旦初始化就不可修改。修改则程序报错
2.2数据类型
2.3表达式
2.3.1表达式
- 表达式:是一系列运算符和算子的组合,用来计算一个值。
- 算子:参与运算的值,可以是常数、变量、一个方法的返回值。
- 举例:计算时间差:(重点是换算)
int hours1, minute1;
int hours2, minute2;
scanf("%d %d",&hours1,&minute1);
scanf("%d %d",&hours2,&minute2);
int t1 = hours1 * 60 + minute1;
int t2 = hours2 * 60 + minute2;
int t = t2 - t1;
printf("时间差是%d小时%d分钟",t/60,t%60);
2.3.2 运算符
——三种运算符:算数运算符、关系运算符、逻辑运算符。
--算数运算符
- 当“+”取作加号时,1+2,此时,1和2是它起作用的操作数,共两个数,所以是双目运算符。
- 当“+”取作正号时,+3,此时3时它起作用的操作数,共一个数,所以是单目运算符。
- 类型转换:
当运算符左右两个操作数类型不同时,编译器会将它们共同转换位某种数据类型,通常情况下,会向精度较大的那个类型转化。
也可以强制转化,就是在数字前面加类型,比如(int)2.1,即将浮点数直接取整,类型一定加()
--关系运算符
- 关系表达式会得到一个值,这个值是逻辑值,真或假;用0表示假。除了0都可表示真,c语言中的输出结果是 真为1,假为0
- 高优先级:< <= > >=
- 低优先级:== !=(不等于)
--逻辑运算符
- 高优先级:逻辑非: !(a为真,!a为假)
- 中优先级:逻辑与: && (a && b 全真为真)
- 低优先级:逻辑或: || (a || b 有真即真)
2.3.3 交换两个变量
用中间变量法:
int a = 3;
int b = 4;
int t = a;
b = a;
a = t;
2.3.4 复合赋值和递增递减
- 复合赋值运算符:+= -= *= /= %= (注意两个运算符之间不要有空格)
- 举例:t += 5的意思是 t = t+5; t*=sum + 12的意思是 t=t*(sum+12)
- 递增递减运算符:++ -- (两者有前缀形式和后缀形式)
3.1判断
3.1.1 if语句
结构:if (条件成立){
做花括号里的
}
3.1.2 关系运算
- 括号里是关系运算(优先级:其他算数运算符>关系运算符>赋值运算符)
- 连续的关系运算是从左到右的,比如:6>5>4 6>5 的结果是1 1>4的结果是0
3.1.3 if流程图
3.1.4 if else语句
if () {
......;
......;
} else {
......;
......;
}
- if else 没有花括号的话就只能执行一句话。
- 进行多次判断 把 else 变成 else if
3.2 循环
3.2.1 举例:判断数字位数
int x;
int n = 0;
scanf("%d",&x);
n++;
x /= 10;
while ( x > 0 ) {
n++;
x /= 10;
}
printf("%d\n",n);
return 0;
3.2.2 while循环
- 当条件满足时不断重复循环体内的语句。
3.2.3 do-while循环
- 无论如何,循环都会至少执行一遍,再来判断条件。
- 数数字位数的例子可以用 do-while 优化为:
int x;
int n = 0;
scanf("%d",&x);
do
{
x /= 10;
n ++;
} while (x > 0); //一定注意分号
printf("%d\n",n);
return 0;
3.2.4 for循环
举例:用while计算n的阶乘:
int fact = 1;
int n;
scanf("%d",&n);
int i = 1;
while (i <= n) {
fact *= i;
i ++;
}
printf("%d! = %d\n",n,fact);
用for计算n的阶乘:
int n;
scanf("%d",&n);
int fact = 1;
for( int i = 1; i <= n; i ++ ) { //i是循环内变量,在for里定义即可
fact *= 1;
}
printf("%d! = %d\n",n,fact);
第一个:初始条件; 第二个:循环继续条件; 第三个:循环每一轮要做的事。
for可以理解成“对于”
for循环尽量从0开始!(对数组有益)
3.2.4 循环总结
- 如果有固定次数,用for
- 如果必须执行一次,用do while
- 其他情况用while
4.1 逻辑类型和运算
4.1.1 逻辑类型
- bool 类型(C99才有)虽然有这种写法,但输出值没有True或False (输入可),只有0和1
- #include <stdbool.h> 要写头文件后才能用bool true false
include <stdio.h>
include <stdbool.h>
int main() {
bool b = 6 > 5;
bool t = true;
t = 2;
printf("%d\n",b); //取值的时候还是用%d
return 0;
}
4.1.2 逻辑运算
- 逻辑运算是自左向右进行的,如果左边已能定结果,程序不再做右边的计算。
- 优先级: !> && > ||
- 对于&&,左边有false就不做右边了;
- 对于||,左边有true就不做左边了;
4.1.3 条件运算和逗号运算
——条件运算符:指?和:
- 示例:条件?条件满足时的值:条件不满足时的值;
- 优先级:条件运算符高于赋值运算符,但比其他人和都低
- 注意:尽量不写嵌套的条件表达式,不好理解
——逗号运算
- 逗号用来连接两个表达式,逗号左边的先运算,右边的留下来作逗号运算结果
- 优先级:是所有运算符中最低的
- 运用:在for循环中:for(i=0, j=10; i<j; i++, j--);
4.2 级联和嵌套的判断
4.2.1 嵌套的if-else
- else总是和最近的if匹配,但是如果加了大括号那就根据{}位置来判断。
- 注意:else与if的匹配只靠格式对齐来调整是没用的!
if ( code == READY ) {
if ( count < 20 )
printf("一切正常\n");
} else //虽然和上一个if对齐,但是是和外层的if匹配
printf("继续等待\n");
- 当没有花括号时:
if ( gameover == 0 )
if ( player2move == 2 )
printf("your turn\n");
else //匹配内层if
printf("my turn\n");
else //匹配外层if
printf("game over\n");
- 所以养成良好的编程习惯,()之后敲一个{}
4.2.2 级联的if-else
表示一个分段函数:(不是包含关系,是一层一层连下来的)
if ( x < 0 ) {
f = -1;
} else if ( x == 0 ) {
f = 0;
} else {
f = 2 * x;
}
4.3 多路分支
4.3.1 switch-case
switch ( type ) {
case 1: //注意是冒号
printf("你好"); //含义是:当type这个变量==1的时候执行
break; //break后面也得有分号 break直接离开{}
case 2:
printf("早上好");
break;
case 3:
printf("晚上好");
break;
case 4:
printf("再见");
break;
default: //注意也是冒号 default在上述四个都没有时执行
printf("啊,什么啊?");
}
/* switch ( 控制表达式 ) {
case 常量:
语句
case 常量:
语句
default:
语句
}
- 已知变量type的值了,比如type=3,则直接跳转到case 3那里执行。
- 控制表达式的结果只能是整数型。
- 常量可以是常数,也可以是常数表达式的结果。
- 如果没有break,执行完case后会进入下一case里面直到遇见一个break或switch结束为止。
- switch-case结构用来判断点,而级联if-else结构可以判断一段范围。
4.4 循环的例子
4.4.1 循环计算
- 计算log2x :
int x;
int ret = 0;
scanf("%d",&x);
while ( x > 1 ) {
t = x; //一定要保存一下x的值
x /= 2;
ret ++;
}
printf("log2 of %d is %d.", x, ret);
4.4.2 算平均数
#include <stdio.h>
int main() {
int number;
int sum = 0; //做初始化
int count = 0;
scanf("%d", &number);
while ( number != -1 ) { //想象-1是终止符
sum += number;
count ++;
scanf("%d", &number);
}
printf("%f\n", 1.0*sum/count); //这里转换的数据类型
return 0;
}
4.4.3 猜数
- 要点:让计算机想一个数,让用户来猜,用户每输入一个数,就告诉他是大了还是小了,直到用户猜中为止,最后还要告诉用户他猜了多少次。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
srand(time(0));
int number = rand() % 100 + 1; //rand是内置函数
int count = 0;
int a = 0;
printf("我已经想好了一个1-100之间的数");
do
{
printf("请猜数:");
scanf("%d", &a);
count ++;
if ( a > number ) {
printf("你猜的数大了");
} else if ( a < number ) {
printf("你猜的数小了");
}
}while ( a != number );
printf("太棒了,你用了%d次就猜对了答案\n",count);
return 0;
}
4.4.4 整数求逆
- 对一个整数作%10的操作,就得到了个位数。
- 对一个整数作/10的操作,就去掉了个位数。
#include <stdio.h>
int main() {
int x;
scanf("%d",&x);
int digit;
int ret = 0;
while ( x > 0 ) {
digit = x % 10;
//printf("%d",digit);
ret = ret * 10 + digit;
printf("x = %d, ret = %d, digit = %d\n", x, ret, digit);
x /= 10;
}
printf("%d", ret);
return 0;
}
4.5 常见错误
——if语句:
- 别忘大括号
- 不要加分号
- ()里判定条件一般是 ==不是 =
5.1 循环控制
——break
- break作用是跳出整个循环。
- 素数(质数):只能被1和自己整除的数,不包括1。
int main() {
int x;
scanf("%d",&x);
int i;
int isPrime = 1; //注意这个比较特别,好好想为什么这样设置
for ( i = 2; i < x; i ++ ) {
if ( x % i == 0 ) {
isPrime = 0;
break; //跳出整个循环用的
}
}
if ( isPrime == 1) {
printf("x是素数\n");
} else {
printf("x是素数");
}
return 0;
}
——continue
- continue作用是跳过这个循环的剩余环节,进入下一次循环。
5.2 多重循环
5.2.1 嵌套的循环
——如何写程序输出100以内的素数?
注意不同的循环变量要区分开
int main () {
int x;
for ( x = 2; x < 100; x ++) {
int i;
int isPrime = 1; //注意这个比较特别,好好想为什么这样设置
for ( i = 2; i < x; i ++ ) {
if ( x % i == 0 ) {
isPrime = 0;
break; //跳出整个循环用的
}
}
if ( isPrime == 1) {
printf("x是素数\n");
} else {
printf("x是素数");
}
}
return 0;
}
5.2.2 从嵌套的循环中跳出
——凑硬币:如何从1角2角5角硬币中凑出10元?
- break和continue只能对所在的那一层循环操作
- 举例:接力break和goto语句:
int x;
int one, two, five;
int exit = 0;
int exit;
scanf("%d",&x);
for ( one = 1; one < x * 10; one ++ ) {
for ( two = 2; two < x * 10/2; two ++ ) {
for ( five = 5; five < x * 10 / 5; five ++) {
if ( one + two * 2 + five * 5 == x * 10 ) {
printf("可以用%d个1角和%d个2角和%d个5角得到%d元\n", one, two, five, x);
exit = 1; //break换成 goto out;(注意分号)(out可以换成别的名字)
break;
}
}
if ( exit == 1 ) break; //注意理解设置if条件的含义
}
if ( exit == 1 ) break;
}
//out:(记得加冒号)
- goto特别适合跳出多层循环,其他时候谨慎使用。
5.3 循环应用
5.3.1 求前n项和
——求数列f(n)=1+1/2+1/3+...+1/n.的前n项和
int main() {
int n;
int i;
double sum = 0.0;
scanf("%d",&n);
for ( i = 1; i <= n; i ++ ) {
sum += 1.0 / i;
}
printf("f(%d) = %f\n",n,sum);
——求数列f(n)=1-1/2+1/3-1/4+...+1/n.的前n项和
int main() {
int n;
int i;
double sum = 0.0;
int sign = 1;
scanf("%d",&n);
for ( i = 1; i <= n; i ++ ) {
sum += sign*1.0 / i;
sign = -sign;
}
printf("f(%d) = %f\n",n,sum);
5.3.2 求最大公约数
两种方法:第一种是枚举;第二种是辗转相除
5.3.3 整数分解
————————————————第六章没有按照课程顺序写——————————————
6.1 数组
6.1.1 数组定义与初始化
——定义数组:格式:类型 名称[元素数量] 如:int i[5]
——访问数组元素:数组名[下标] 注意下标是从0计数。 如:i[0] 指访问i数组中第1个元素
——初始化数组:(借鉴了这篇文章)(8条消息) C语言万字基础笔记总结(二)_爱打瞌睡的CV君的博客-优快云博客
- 将数组中所有元素初始化为0
int a[5] = {0}; //把一个数组中元素全部化为0
//事实上,这个只是把第一个元素赋值为0,其余各位是自动初始化为0(不写就是0)
int a[5] = {1};
//这里就是,第一个元素为1,其余元素为0
- 如果是给元组赋予不同的值,用逗号隔开即可
int a[5] = {1, 2, 3, 4, 5};
- 也可以只给一部分元素赋值,未被赋值的元素,自动初始化为0
int a[5] = {1, 2, 3};
//前三位成功赋值,后两位,自动初始化为0
- 也可以指定元素进行赋值,未被赋值的元素自动初始化为0
int a[5] = {[2] = 2, [4] = [4]};
//数组a的第 3 位被赋值为2,第 5 位被赋值为4,因为数组的下标是从0开始的!!!
- 也可以只给出元素的值,不指定数组的长度
int a[] = {1, 2, 3, 4, 5}
6.1.2 用数组做散列计算
#include <stdio.h>
int main() {
const int number = 10;//10是magic number,为了便于修改要在前面统一定义
int x;
int count[number];
int i;
for ( i = 0; i < number; i++ ) { //做数组初始化
count[i] = 0;
}
scanf("%d", &x);
while ( x != -1 ) {
if ( x >= 0 && x <= 9 ) {
count[x]++;
}
scanf("%d", &x);
}
for ( i = 0, i < number, i++ ) { //遍历结果
printf("%d:%d\n", i, count[i]);
}
6.1.3 二维数组
——定义二维数组:格式:类型 数组名[常量表达式] [常量表达式]
——访问二维数组:格式:数组名[下标][下标]
a[0][0]; // 访问a数组中第一行第一列的元素
b[2][3]; // 访问b数组中第三行第四列的元素
——初始化二维数组
- 由于二维数组在内存里面中是线性存放的,因此可以将所有的数据写在一个花括号里面
- 观察一下具体是怎么存放的:
#include <stdio.h>
int main()
{
int a[2][3] = {1, 2, 3, 4, 5, 6};
int i, j;
for (i = 0;i < 2;i++)
{
for (j = 0;j < 3;j++)
{
printf("a[%d][%d] = %d\n", i, j, a[i][j]);
}
}
return 0;
}
结果如下:
a[0][0] = 1
a[0][1] = 2
a[0][2] = 3
a[1][0] = 4
a[1][1] = 5
a[1][2] = 6
- 更直观的表示元素的分布,可以用大括号将每一行的元素括起来
int a[2][3] = {{1, 2, 3}, { 4, 5, 6}};
//或者写成这种样式:
int a[][3] = {
{1, 2, 3},
{4, 5, 6}
};
- 行数或列数可以省略一个但不能都省略
- 将整个二维数组初始化为0,则只需要在大括号里写一个0
int a[2][3] = {0};
- 对某些指定的元素进行初始化,其它元素自动初始化为0
int a[2][3] = {[0][0] = 1, [1][2] = 2};
6.1.4 函数的定义与使用
————————————————函数部分引用了这篇文章————————————————
(16条消息) c语言之函数 -----学习笔记_旭日初扬的博客-优快云博客
一、函数的定义
- 结构化程序设计的思想:把大问题分解成若干个小问题,每个小问题就是一个独立的子模块,以实现特定的功能、在C程序中,子模块的作用是由函数完成的.
- 函数定义:函数是一块代码,接受0个或多个参数,做一件事情,并返回0个或1个值。
- 一个c源程序可以由多个文件构成(c文件的后缀名.c)
- 一个源文件是一个编译单位
- 一个源文件可以由若干个函数组成(函数是c程序的基本组成单位)
- 每个c程序只能有一个main函数,其他都是子函数。
- 主函数可以调用子函数,子函数可以相互多次调用。
二、函数的分类
2.1、按函数的定义分类
2.1.1、标准函数
标准函数又称为库函数,由c系统提供,无需程序员定义,可直接使用,但需要在程序开头包含原型声明的头文件。如scanf()
2.1.2、自定义函数
由程序员根据自己的需求编写,自定义函数不仅要在程序中定义函数本身,必须还要在主函数中调用该函数
2.2、按有无返回值的分类
- return会停止函数的执行,并送回一个值。
2.2.1、有返回值的函数
该类函数被调用执行完毕,将向调用者返回一个执行结果,称为函数的返回值
int send( int a,int b)//有返回值
{
return a+b;
}
void main()
{
z=send(a,b);
printf("%d\n",z);
}
2.2.2、无返回值的函数
- 无返回值函数不需要向主调函数提供返回值
- 无返回值必须加void声明,否则默认为int
void printstar()
{
printf("************");
}
void main()
{
printsar();
}
2.3、按函数的形式分类
2.3.1、无参函数
在函数的声明、定义和调用中均不带参数,特点:在调用无参函数主调函数并不将数据传输给被调用函数,此类函数通常被用来完成指定的功能,可以返回或不返回函数值。
/*
格式:
类型说明 函数名()
{
// 函数体
}
例*/
int max()
{
// 函数体
}
//空函数 无函数体
//类型说明 函数名()
{
//无函数体 因程序设计需要,确定模块需求 先占位置 后在补上
}
int max()
{
}
2.3.2、有参函数
在函数定义、声明都都有参数。特点:主调函数调用被调函数时,主调函数必须把值传输给形参,以供被调函数使用
int max(int a,int b) // 有参函数
{
函数体
}
三、函数返回值及类型
3.1、函数的返回值
- 注意:函数与函数的关系是同等级别,不能嵌套
-
函数的返回值是通过函数中的return语句实现
-
return语句将被调函数中的一个确定值带回主调函数中
#include "stdio.h"
int max(int a, int b) // 有返回值 用参数的函数 a,b为形参
{
int c;
c=a>b?a:b;
return c;
}
void main() // 无返回值必须加void声明,否则默认为int
{
int x,y;
printf("请输入两个整数:");
scanf("%d%d",&x,&y);
printf("%d和%d的最大值为:%d\n",x,y,max(x,y));
}
/*
return expression; return a;
return(expression); return (a);
*/
3.2、函数的类型
3.2.1、常见函数类型
int max()
float max()
double max()
void max()
-
定义函数时,必须指明函数的返回值类型,而且return语句表达式中的类型应该与函数定义时首部的函数类型时一致的,如果二者不一致,则以函数定义时首部的函数类型为准。
3.2.2、一个函数中无论有几个return都只返回一个
一个函数中可以有一个以上的return语句,但不论执行那个return,都将结束函数的调用返回主调函数,即带返回值的函数只能有一个。
/*
一个函数中可以有一个以上的return语句,但不论执行那个return,都将结束函数的调用返回主调函数,即带返回值的函数只能有一个
*/
#include "stdio.h"
int max(int a,int b) // 定义函数max() a,b实参传递给形参x,y
{
if(a>b) // 如果a>b,返回a
return a;
return b; //否则返回b
}
void main()
{
int x,y;
printf("请输入两个整数:");
scanf("%d%d",&x,&y);
printf("%d和%d的最大值为:%d\n",x,y,max(x,y));
}
四、函数的参数及传递方式
4.1、函数的参数
函数的参数有两类:形参和实参
形参:函数定义时的参数,参数在函数未被调用时是没有确定值的,只是形式上的参数
实参:函数调用时使用的参数
参数可以是常量,变量,其他构造数据类型或表达式,调用时可写成:
order(2,3); // 实参时常量
order(x+y,x-Y); // 实参时表达式
- 实参是表达式时,先计算表达式的值,再将实参的值传递个形参,但要求它有确切的值,因为调用是要将值传递给形参的。
- 定义函数时必须说明形参的类型,形参只能是简单的变量或数组,不能是常量或表达式。
- 函数被调用前,形参不占内存的存储单元;调用以后,形参才被分配内存单元;函数调用结束后,形参所占用的内存也被收回或者释放。
- 实参的个数、出现的顺序和实参的类型,应该与函数定义中形参的设计一一对应。
- 当实参类型与形参声明不符时,编译器自动转换为形参类型。
#include <stdio.h>
void order(int a,int b) // a,b为形式参数(形参)
{
int t;
if(a>b) // 如果a>b,就执行一下3条语句
{
t=a;
a=b;
b=t;
}
printf("从小到大的顺序:%d %d\n",a,b); // 输出交换后的a,b的值
}
void main()
{
int x,y;
printf("请输入两个整数"); // 键入两个整数
scanf("%d%d",&x,&y);
order(x,y); // x,y为实际参数(实参)
}
4.2、函数参数的传递方式
c语言规定,实参对形参的数据传递是“值传递”(例子中x,y的值最后并没有改变),即单向传递,只是把实参的值传递给形参,而不能把形参的值再传递给实参。在内存当中,实参与形参是不同的单元,不管名字是否相同,因此函数中对形参值的任何改变都不会影响到实参的值。
#include "stdio.h"
void swap(int a,int b) // 自定义函数 无返回值类型的有参函数
{
int temp;
temp=a;a=b;b=temp; /*交换a,b的值*/
printf("a=%d,b=%d\n",a,b);
}
void main()
{
int x,y;
printf("请输入两个整数:\n");
scanf("%d%d",&x,&y); // 输入两个整数
printf("调用函数之前\n");
printf("x=%d,y=%d\n",x,y);
// 输出调用swap函数之前x,y的值
printf("调用函数中:\n");
swap(x,y); // x,y 实参
printf("调用函数之后:\n");
printf("x=%d,y=%d\n",x,y); // 输出调用swap函数之后x,y的值
}
void swap(int a, int b);
int main()
{
int a = 5;
int b = 6;
swap(a,b);
printf("%d-%d\n", a, b);
return 0;
}
void swap(int a, int b)
{
int t = a;
a = b;
b = t;
}
输出结果还是5-6
五、函数调用
- C程序是从主函数main()开始执行的,以main()函数体结束为止在函数体的执行过程中,通过不断地对函数的调用来执行的。
- 调用者 称为主调函数一般为main()函数,
- 被调用者 称为被调函数一般为自定义函数或者库函数。
- 被调函数执行结束,从被调函数结束的位置再返回主调函数当中,继续执行主调函数后面的语句。
5.1、函数的调用方式
5.1.1、(函数语句)第一种调用方式
函数名(实参列表);
函数名();
5.1.2、(函数表达式)第二种调用方式
m=max(x,y); // 将max()函数的返回值赋值给变量m
m=3*max(x,y); // 将max()函数的返回值乘3赋值给变量m
printf("Max is %d,max(x,y)"); // 输出max()函数的返回值
5.2、函数的声明
使用原则:先定义后使用(如果定义写在主函数后面,则在主函数前面要写一行原型声明)
有参函数的声明形式
函数类型 函数名(形参列表);
int tian(int a,int b); //写一行即可,完整定义在主函数后面
无参函数的声明形式:
函数类型 函数名();
void tain();
5.3、函数的嵌套调用
- 在c语言中,函数的关系是平行的,是独立的即函数的不能嵌套定义。
- c语言中函数的嵌套调用即在调用一个函数的过程中可以调用另外一个函数。
#include "stdio.h"
fun2(int x,int y)
{
int z;
z=2*x-y;
return z;
}
fun1(int x,int y)
{
int z;
z=fun2(x,x+y);
return z;
}
void main()
{
int a,b,c;
printf("请输入两个整数: \n");
scanf("%d%d",&a,&b);
c=fun1(a,b); // 调用fun1()函数
printf("%d\n",c);
}
5.4、函数的递归
- 如果在调用一个函数的过程中,有直接或间接的调用了该函数本身,这种形式称为函数的递归调用,这个函数就称递归调用。
int func(int a)
{
int b,c;
c=func(b);
}
/*用递归方法求n!*/
long fac(int n)
{
long m;
if(n==1)
m=1;
else
m=fac(n-1)*n;
return m;
}
main()
{
int n;
float y;
printf("input the value of n.\n");
scanf("%d",&n);
printf("%d!=%d\n",n,fac(n));
}
————————————————
版权声明:本文为优快云博主「旭日初扬」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/RONG_YAO/article/details/105852442
7.1 数组运算
- 数组变量本身不能被赋值,要把一个数组的所有元素交给另一个数组,必须采用遍历。
- 遍历数组通常用for循环,一般是i从0到小于数组长度,因为这样最大值刚好是数组下标
for (i = 0; i < length; i ++) {
b[i] = a[i];
}
- 数组作为函数参数时,必须再用另一个参数来传入参数大小。如下:
int search( int a, int b[],int length)//不能直接写数组长度
- size of 函数:sizeof是C语言的一种单目运算符,如C语言的其他操作符++、--等。它并不是函数。sizeof操作符以字节形式给出了其操作数的存储大小。操作数可以是一个表达式或括在括号内的类型名。操作数的存储大小由操作数的类型决定。
-
注意:sizeof操作符不能用于函数类型,不完全类型或位字段。不完全类型指具有未知存储大小的数据类型,如未知存储大小的数组类型、未知内容的结构或联合类型、void类型等。
如sizeof(max)若此时变量max定义为int max(),sizeof(char_v) 若此时char_v定义为char char_v [MAX]且MAX未知,sizeof(void)都不是正确形式。
7.2 搜索
搜索:在一个数组中找到某一个数的位置(或确认是否存在)。基本方法是遍历
7.3 排序初步
1、找数组里的最大值
int max(int a[],int len) {
int maxid = 0;
for (int i = 0; i < len; i ++ ) {
if ( a[i] < a[maxid] ) {
maxid = i;
}
}
return maxid;
}
应用:
int main() {
int a[] = {11,22,33,44,55,88,77,66,99,};
int maxid = max(a,sizeof(a)/sizeof(a[0]));
return 0;
}
2、互换位置,把最大值挪到数组最后一位
接上面的
//swap a[maxid] a[len-1]
int t = a[maxid];
a[maxid] = a[sizeof(a)/sizzeof(a[0])-1];
a[sizeof(a)/sizeof(a[0])-1] = t;
3、做循环
for (int i = len - 1; i > 0; i --) {
int maxid = max(a, i + 1);
int t = a[maxid];
a[maxid] = a[i];
a[i] = t;
4、遍历数组检查
for ( int i = 0; i < len; i ++ ) {
printf("%d",a[i]);
}
8.1 指针
关于指针详见另一篇笔记!
(4条消息) c指针详细笔记_zlshhdgrbk的博客-优快云博客
下面这个也可以结合着来看,不过上面那篇已足够详细,涵盖了指针及指针与数组、函数之间联系
————————关于指针,这篇文章讲的非常详细,在此基础上作了修改——————————
(16条消息) C语言指针详解(经典,非常详细)_liu100m的博客-优快云博客_指针c语言
要了解指针,多多少少会出现一些比较复杂的类型,所以我先介绍一下如何完全理解一个复杂类型,要理解复杂类型其实很简单,一个类型里会出现很多运算符,他们也像普通的表达式一样,有优先级,其优先级和运算优先级一样,所以我总结了一下其原则:从变量名处起,根据运算符优先级结合,一步一步分析.下面让我们先从简单的类型开始慢慢分析吧:
- int p; //这是一个普通的整型变量
- int *p; //首先从P 处开始,先与*结合,所以说明P 是一个指针,然后再与int 结合,说明指针所指向的内容的类型为int 型。所以P是一个返回整型数据的指针 。(int* p和int *p是一样的)
- int p[3]; //首先从P 处开始,先与[]结合,说明P 是一个数组,然后与int 结合,说明数组里的元素是整型的,所以P 是一个由整型数据组成的数组
- int *p[3]; //首先从P 处开始,先与[]结合,因为其优先级比*高,所以P 是一个数组,然后再与*结合,说明数组里的元素是指针类型,然后再与int 结合,说明指针所指向的内容的类型是整型的,所以P 是一个由返回整型数据的指针所组成的数组
- int (*p)[3]; //首先从P 处开始,先与*结合,说明P 是一个指针然后再与[]结合(与"()"这步可以忽略,只是为了改变优先级),说明指针所指向的内容是一个数组,然后再与int 结合,说明数组里的元素是整型的.所以P 是一个指向由整型数据组成的数组的指针
- int **p; //首先从P 开始,先与*结合,说是P 是一个指针,然后再与*结合,说明指针所指向的元素是指针,然后再与int 结合,说明该指针所指向的元素是整型数据.由于二级指针以及更高级的指针极少用在复杂的类型中,所以后面更复杂的类型我们就不考虑多级指针了,最多只考虑一级指针.
- int p(int); //从P 处起,先与()结合,说明P 是一个函数,然后进入()里分析,说明该函数有一个整型变量的参数,然后再与外面的int 结合,说明函数的返回值是一个整型数据
- Int (*p)(int); //从P 处开始,先与指针结合,说明P 是一个指针,然后与()结合,说明指针指向的是一个函数,然后再与()里的int 结合,说明函数有一个int 型的参数,再与最外层的int 结合,说明函数的返回类型是整型,所以P 是一个指向有一个整型参数且返回类型为整型的函数的指针
- int *(*p(int))[3]; //可以先跳过,不看这个类型,过于复杂从P 开始,先与()结合,说明P 是一个函数,然后进入()里面,与int 结合,说明函数有一个整型变量参数,然后再与外面的*结合,说明函数返回的是一个指针,,然后到最外面一层,先与[]结合,说明返回的指针指向的是一个数组,然后再与*结合,说明数组里的元素是指针,然后再与int 结合,说明指针指向的内容是整型数据.所以P 是一个参数为一个整数据且返回一个指向由整型指针变量组成的数组的指针变量的函数.
说到这里也就差不多了,我们的任务也就这么多,理解了这几个类型,其它的类型对我们来说也是小菜了,不过我们一般不会用太复杂的类型,那样会大大减小程序的可读性,请慎用,这上面的几种类型已经足够我们用了。
一、细说指针
指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。要搞清一个指针需要搞清指针的四方面的内容:指针的类型、指针所指向的类型、指针的值或者叫指针所指向的内存区、指针本身所占据的内存区。让我们分别说明。
先声明几个指针放着做例子:
例一:
- (1)int*ptr;
- (2)char*ptr;
- (3)int**ptr;
- (4)int(*ptr)[3];
- (5)int*(*ptr)[4];
mooc:
指针就是保存地址的变量
指针变量的值是其他变量的内存地址;普通变量的值就是实际的值
int *p = &i(p是指针,指向一个int)
int* p,q表示p是指针,q是普通变量
指针作为参数时,如void f(int *p),使用的时候必须f(&i)
*是一个单目运算符,用来访问指针的值所表示的地址上的变量
*p也是变量,这个变量可以放=左边也可右边
如:int k = *p ;*p = k+19(可修改p指向变量的实际值)
pitfall:int i = 8; scanf("%d",i)此时编译不会报错,但6这个地址是运行并不了的
指针作为数组参数
所以,数组变量是特殊的指针,数组变量本身表达的就是地址,比如在printf里取i的地址不用&
如:int a[10];int *p = a;
但是数组的单元里表达的是变量,需要&来取地址,如&a[0]
[]运算符可以对数组做,也可以对指针做,p[0]相当于*p
*运算符可以对指针做,也可以对数组做
int b[] --> int * const b;(const表示b不可改变,所以不能被赋值,没有b = a 的写法,两个数组之间不能直接被赋值)
8.2 字符类型
8.2.1 字符类型
- printf和scanf里用%c来输入输出字符
- 'a'表示的是一个字符,数字1和字符'1'不一样,每一个字符在计算机内部都有一个值来表达
- 也就是说变量类型为字符类型依然可以用数值型%d输出,只不过按照ASCII码来输出。
- %c的时候,scanf直接输入就行,不用带引号
c = 1;
d = '1';
printf("c = %d\n",c);//结果是1
printf("d = %d\n",d);//结果是49
49 == '1'//是对的
char c = 'A';
c ++;
printf("c = %c\n",c);//输出结果是B
混合输入:(和前面scanf记的不太一致,记得是用回车分隔,回头再看看)
scanf("%d %c",&i,&c);//多了一个空格
scanf("%d%c",&i,&c);//这二者有什么区别呢?目前理解为有了空格输入的时候就可以0~很多个空格
第一个输入12 1或12a或12 1都能输出打入的值
第二个输入12 1会输出空格的整数值32
一个字符加一个数字会得到ASCII表(大小写字母分开排列,小写在大写后)中那个数之后的字符
两个字符的减,得到他们在表中的距离
大小写转换:A+'a'-'A' 大写变小写; a+'A'-'a' 小写变大写
“在C语言中,小写字母转换为大写字母的方法是将小写字母的ASCII码值减去32(例:A=a-32);大写字母转换为小写字母的的方法是将大写字母的ASCII码值加上32(例:a=A+32)即可。”
8.2.2 逃逸字符
8.3.1 字符串
- 字符数组:char word[] = {'H','E','L','L','O'}但这不是c语言的字符串,因为不能用字符串的方式做计算
- 字符串:char word[] = {'H','E','L','L','O','/0'}或char word[] = {'H','E','L','L','O',0}但不能是'0'
下面这两段话没太理解,以后回来看
用单引号引起的一个字符大小就是一个字节。
而用双引号引起的字符串大小是字符的总大小+1,因为用双引号引起的字符串会在字符串末尾添加一个二进制为0的字符’\0’。
0标志字符串的结束,但不是字符串的一部分,计算字符串长度时也不包括这个0
字符串以数组的形式存在,访问时可以是以数组形式也可以是指针形式(更多以指针)
string.h里有很多处理字符串的函数(是一种标准库)
- 字符串常量:带双引号的,如"hello",会被编译器变成一个字符数组放在某处,数组长度为6,结尾还有个表示常数的0
如果两个字符串之间没有,来分隔,c语言会将其自动转换为一个大的字符串,这样便于书写
"7788""0033"和
"7788/
0033" 效果一样
c语言不能用运算符对字符串做运算,对字符可以
通过数组的方式可以遍历字符串
8.3.2 字符串变量
char *s = "hello,world!" 实际上相当于const char *s,不能用指针形式对字符串做修改如s[0]='B',
如果想要修改字符,应该用数组形式char s[] = "hello,world"
- 如果要构造一个字符串:数组 (表示这个字符串就在我这)
- 如果要处理一个字符串:指针(表示指针指向了一个字符串)
8.4.1 字符串输入输出
- %s,scanf读入一个单词(到空格,回车,tab为止)char string[8]; scanf("%s",string);
- 但是这样的scanf不安全,因为不知道要读入内容的长度,解决方法:“%7s”7表示最多接受7长度,这个数字要比数组的大小小一个。
常见错误
8.4.2 字符串函数
如何写一个程序计算用户输入数字的平均数,并输出所有大于平均数的数?