目录
[4.实例:hello world](#4.实例:hello world)
[1.if (条件){}else{}](#1.if (条件){}else{})
2.switch(条件){case:结果1...default}
其中会用到关键字:break(结束此过程并跳出)和continue(结束此次循环不执行后面语句继续循环)
[int a[10];](#int a[10];)
[区分: int a[5] 和 a[5] 的区别:](#区分: int a[5] 和 a[5] 的区别:)
[2、间接运算 *](#2、间接运算 *)
一.编译一个C语言代码
1.编译器vim
使用:vim/vi test.c 创建.c文件(若存在则打开), 结束:ESC +保存 : wq/ZZ(保存并退出)/:q(仅退出)

按'i'进入输入模式 输入时使用英文输入法。
2.编译文件:
++编程语言分为两个:编译型(交给cpu运行,cpu只认识二进制,编译器将文件变成二进制机器语言),解释型(交给解释器,由cpu运行解释器进而来运行文件)++
vim是编译器,gcc test.c 进行编译
3.执行编译形成可执行文件:./a.out
4.实例:hello world
objectivec
#include <stdio.h>
// 单行注释 注释是给自己和同事看的,编译器不看
/*
多行注释
这里也是注释
练习:
输出I'm a student,不要在刚才示例的基础上修改。要重新创建文件,把流程自己写一遍。
clear清屏
四、变量
*/
/*
# 表示预处理指令,在编译之前要做的事
编译:将C语言代码翻译成机器码
include 是预处理指令,包含头文件,为什么要包含头文件,为了声明
声明:解释说明标识符的含义
标识符:就是我们在编程中自己起的名字
< > 表示在标准库中查找头文件,<>包含官方提供的头文件,自己写的头文件用""包含
stdio.h std标准 i输入 o输出 标准输入输出头文件,声明了和输入输出相关的内容
在这段代码中,使用stdio.h声明 printf函数
*/
/*
长见的拼写错误 mian
main(主函数)程序的入口,整个程序逻辑开始执行的地方。一个C语言程序有且只有一个main函数。
*/
int main()
{//{}范围内都是main函数的内容
/*
print
f: format
printf全称格式化输出
格式化:给字符串格式化,将变量中的值放到字符串中
\n 换行,但是在ubuntu的终端上,\n 表示换行而且回车(光标回行首) \r是回车
*/
printf("hello world\n");
return 0;
}
二.变量
1.变量类型
int(整形) ; char(字符型); float/double(浮点型); 数据类型 标识符[](数组型) ; 数据类型* 标识符(指针型)
2.定义变量
数据类型(关键字) 变量名称(标识符) int a
关键字:C语言中带有特殊含义的字符串,不可用来定义变量名称 标识符:自己起的名字
3.命名规则
数字字母下划线组成:~!@#¥%......&*()---+ 都不可以用 大小写区分,不可:数字开头,使用关键字当名称 ,使用汉字拼音
4.变量赋值与初始化
赋值使用'='
变量初始化:int a=0;
5.进制
进制转换基础
C 语言没有二进制常量的写法。
十进制: 逢十进一。 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
二进制: 逢二进一。 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101
1110 1111 10000
八进制: 逢八进一。 00 01 02 03 04 05 06 07 010 011 012 013 014 015 016 017 020
十六进制:逢十六进一。 0x0 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xa 0xb 0xc 0xd
0xe 0xf 0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1a 0x1b 0x1c 0x1d
0x1e 0x1f 0x20
十六进制不分大小写
前缀
十进制 没有前缀 八进制 0 十六进 0x (在 C 语言代码中,八进制和十六进制常量必须写前缀,为了让编译器认识。)
格式
十进制格式: %d
++八进制格式: %o %#o 带前缀的打印++
++十六进制格式:%x %#x 带前缀的打印++
进制转换:
二转八:三个一位;转十六:四个一位;转十:2^n的和
其他转二:八:一位分三;十六:一位分四; 十:短除法(对二取余数,再将本次的商作为被除数继续对二取余,直到商为0时为止。将得到的余数,从后向前依次读出,即为转换的二进制数)
6.格式符
在使用printf,scanf时声明格式:%d 十进制输出 %s字符串输出 %c字符输出 %o八进制 %x十六进制 %f单精度浮点型输出(++默认输出小数点后6位 float类型是约数 7位有效数字,当浮点数进行比较不可用==,因为是约数只是大概的数字,所以要用 %来规定他的误差大小到多少合适++)
objectivec
#include <stdio.h>
int main()
{
int a = 10;//定义整型变量a,并且初始化10
//左边第一个a的值放在了%d的位置,中间a的值放在了%o的位置,最右边a的值放在了%x位置
printf("%d %o %x\n", a, a, a);//分别以十进制,八进制,十六进制输出a的值
//输出 10 12(八进制) a(十六进制)
float b = 1.23;//定义浮点型变量b,初始化位1.23
printf("%f\n", b);//输出b 默认输出小数点后6位 float类型是约数 7位有效数字
//如果b的值是123.0,那么输出小数点后只有4位是有效的,最后两位是什么都无所谓
return 0;
}
7.编码规范:(个人习惯)
成对符号一次打全例如{} (); 时刻使用tab进行空格(美观)
三.输入输出printf,scanf
1.注意:
printf:%d确定输出的只能是十进制数据
scanf:禁止写\n,系的什么类型就要输入什么类型 输入多个数据可用空格和回车分来数据, &标识符 (数组除外,数组名本身是首元素地址) ++输入结束后默认都会给个\n++
objectivec
#include <stdio.h>
int main()
{
int a;//定义整型变量
/*
""是scanf想输入的内容 %d表示想输入一个十进制整数不可输出其他类型数据
&a是取地址运算,运算得到变量a的地址,告诉scanf将输入的整数放在变量a中
*/
scanf("%d", &a);
printf("a = %d\n", a);//输出a的值
return 0;
}
2.输入与输出函数缓存
数据由输入方 放到缓存(当需要输出的时候进行输出) 输出方
输入函数scanf:输入结束后默认都会给个\n
清除缓存会清除到\n为止
三.🔺运算符
++优先级:括号() 、 单目 !++ --、 算术运算、 关系运算、 逻辑运算、 条件运算、 赋值、 逗号++
1.算术运算符
-
- * / %(模除:取余数)
/ 整数相除结果还是整数,不能整除的部分直接舍掉 10/6 = 1
除法的两个只要有一个是小数,那么结果就是小数
除数不能为 0
% 两个操作数都是整数 除数不能为 0 10%6 = 4
- * / %(模除:取余数)
2.关系运算符
> < > = < = == !=
运算结果:1(真非0都为真-1真) / 0(假)
关系运算符不可连着写a<b<c ×(结果是a<b真为1》1<c真) 只可(a<b)&&(b<c)这种
3.逻辑运算符
双目运算:&& 逻辑与 || 逻辑或 单目运算: ! 逻辑非
结果为0/1
特性:与操作左为假不算右边( 左边表达式为假触发短路特性);或操作左为真不算右边( 左边表达式为真触发短路特性) 所以一般简单的放左边。
4.条件运算符
? :
操作数 1? 操作数 2: 操作数3( 条件运算的运算结果只有两种,要么是操作数 2 ,要么操作数 3 ,操作数 1 的值是真,结果就是操作数 2, 否则就是操作数3 。 就是 if-else 的逻辑 )
运算结果是数值,由操作数2 和操作数 3 决定。
条件运算符是 C 语言唯一一个三目运算符(三元运算符)。
5.sizeof()运算符
sizeof(int)为4
计算类型或者变量的长度,单位是字节byte。字符串长度时算\0占位大小
注意:
4 字节 int 整型
1 字节 char 字符型
2 字节 short 或者 short int 短整型 ( 整数类型,占内存比 int 小 )
4 字节 float 单精度浮点型( 7 位有效数字)
8 字节 double 双精度浮点型(比 float 能表示数的范围更大,有效数字也更多, 16 位有效数字)
32位4 64位8 指针
6.自增自减运算符
给变量自身 +1 或者 -1 ,运算对象是变量。
难点
++ 和 -- 除了改变变量自身的值,还产生了一个运算结果
运算结果
前缀形式:运算结果是变量改变之后的值;
后缀形式:运算结果是变量改变之前的值;
如果使用 ++ 和 -- 只想改变变量的值,不使用它们的运算结果,那么前缀后缀就无所谓了。
7.赋值运算符'='
二元运算 += -= *= %= /=
second = second + 10; 等价于 second += 10;
second = second - 10; 等价于 second -= 10;
second = second * 10; 等价于 second *= 10;
second = second / 10; 等价于 second /= 10;
second = second %10; 等价于 second %= 10;
8.逗号运算符
++逗号表达式的算法 ,是最后一个逗号后面的值,所有逗号都会执行,从左到右执行。++
研究逗号表达式的算法没有实际的含义,但是笔试题容易考
objectivec
#include <stdio.h>
int main()
{
int a = (1,2,3,4,5,6,7,8,9);//(1,2,3,4,5,6,7,8,9)形成一个逗号表达式,9是它的最终结
果
printf("%d\n", a);//9
return 0;
}
//常见错误
#include <stdio.h>
int main()
{
int a = 0, b = 1;
//这不是语法错误,因为条件是一个逗号表达式,a==0这个条件在这里没有作用,所以逻辑一定是有问
题的。
if(a==0,b==0)//以b==0作为逗号表达式的结果
{
printf("hehe\n");//所以不输出 hehe
}
return 0;
}
9.位运算符(位运算只能对整数运算)
1.why:
当我们的运算目标不是整个变量了,而是变量中某些具体的二进制位的时候,我们要使用位运算。
2.源码反码补码
源码:将整数正常转换为二进制数,最高位 0 表示正数,最高位 1 表示负数。
15
0000 1111
-15
1000 1111
无符号整数: unsigned 修饰
unsigend char a = 15;//1 个字节的整数,没有符号位,最高位就是普通数据
反码:它只是一个过程,源码转补码过程中产生反码。
-15 的反码
1111 0000
补码:反码 +1 是补码,负数在内存中以补码形式存在,正数的补码和源码一样。
-15 的补码是
1111 0001
N - N N 加 -N
0000 1111
1111 0001
总结:正数原反补相同,负数:反码=源码取反(符号位除外) 补码:反码加一
3.左移右移
左移:低位置补0,右移:高位置补符号位 溢出位置不要
右移N位,相当于除以2 的N次幂,一点用都没有
4.~按位取反
单目运算,将无符号变量中所有的位, 1 变成 0 , 0 变成 1 , 有符号数符号位不变
5.按位与&
& 二元运算 对应的位,有一个为 0 结果就是 0,
作用:给给定位置赋值0:
unsigned char a;
a = a&~(1<<n) ;//将变量a 的 n 位写 0 ,其他位不变 n>=0 最低位读作 0 位
读取给定位置
6.按位或|
| 指定的二进制位,写 1
a = a | 1 << n ; 给 a 的第 n 位写 1 , n >= 0 最低位是 0 位
7.按位异或^
^ 指定位取反 对应的位,相同得 0 ,不同得 1
a = a^1 << n ; 将 a 的第 n 位取反
应用:不引入第三个变量交换
法1:
int a = 10, b = 20;
a = a+b;//a = 30 b = 20
b = a-b;//a = 30 b = 10
a = a-b;//a = 20 b = 10
法2:
int a = 10, b = 20;
a = a^b;
b = a^b;
a = a^b;
四.选择结构
1.if (条件){}else{}
objectivec
//在这段if-else中,语句1和语句2是被选择性执行的代码,由条件决定到底执行谁
if( 条件 ) //条件的运算结果是真 执行语句1,只有一条语句1可不写{}
{语句1}
else //条件的运算结果是假 执行语句2
{语句2.}
//if后面不是必须有else结构,如果写了else,必须有与之匹配的if
并列:if{}else{} if{}else{}
嵌套:if(){if(){}else{}}else{}
多分支:if(){}else if(){}else{}(整个if - else if 结构中,。只要有一个条件为真,那么后面的条件就不判断)
2.switch(条件){case:结果1...default}
它可以完全被 if-else 结构替代,但是 switch-case 不能替代 if-else 结构。
它比较适合一个表达式有若干整型运算结果的判断。
在对于一个表达式的若干整型结果判断的逻辑上,使用 switch 结构比 if 结构更加优雅。
其中会用到关键字:break(结束此过程并跳出)和continue(结束此次循环不执行后面语句继续循环)
objectivec
//计算表达式的结果,表达式的结果必须是整数类型 int char short
switch ( 表达式 )
{
case 值1://case后面的值必须是整型常量,1 2 3 'a' 'b' 'c' 都可以; 这个值switch的表达式可
能产生的一个结果,switch结构会找到合适的case匹配switch表达式的结果,从匹配到的case开始执行代
码
//case可以写无数个
语句1;
case 值2:
语句2;
default://当没有任何case被匹配到的时候,匹配default,可以没有default
语句3;
}
objectivec
#include <stdio.h>
int main()
{
int week;
scanf("%d", &week);//输入整数
switch(week)//匹配week的值
{
case 4://如果week值是4,那么输出dream
printf("dream\n");
break;//结束switch结构
case 5://如果week值是5,那么输出happy
printf("happy\n");
break;
case 6://如果week值是6,那么输出sleep go on sleep case只表示开始,不能表示结束
printf("sleep\n");
case 7://如果week值是7,那么输出go on sleep
printf("go on sleep\n");
break;
default://如果week值不是 4 5 6 7 ,那么输出hard work
printf("hard work\n");
break;//最后的break写不写无所谓
}
return 0;
}
五.循环结构
1.while(){}
objectivec
while ( 条件 ) //条件为真 执行循环体,循环体执行完毕再判断条件,条件为真再执行循环体
{
循环体。
}
while(1)死循环 ctrl+c 强制结束程序运行
objectivec
#include <stdio.h>
int main()
{
int i = 1;//定义变量i作为循环变量
//用于循环条件中的变量,我们称为循环变量
while ( i<6 )
{
i++;//i值+1 写成++i也可以,因为没有使用++的运算结果
printf("hello world! %d\n", i);//2 3 4 5 6
}
printf("while end i=%d\n", i);//6
return 0;
}
2.do{}while()
objectivec
do
{
循环体。
}while( 条件 );
//第一次循环体是无条件执行的,然后判断条件为真再执行循环体,
3.for(;;){}
objectivec
for( 表达式1; 表达式2; 表达式3 )
{
循环体。
}
/*
表达式1:在循环开始的时候,只执行1次,给循环变量初始值,在C99版本之前,表达式1不能定义变量
表达式2:循环条件
表达式3:每次循环体执行完毕后执行一次表达式3,改变循环变量的值
两个;都不可以省略,是标准的格式
3个表达式都可以不写 for(;;){}
当省略表达式2的时候,循环条件恒为真。
*/
4.三种循环区别
不同的循环适合表达不同的逻辑。
在循环效率上是没有用区别的。
for 以循环次数作为循环条件(数数)
while 不以循环次数作为循环条件(不数数)
do-while 校验循环体的执行结果,根据对结果的预期决定是否需要循环 (登录)
5.循环控制语句
continue 结束本次循环体, while 和 do-whlie 下一步判断条件; for 下一步执行表达式 3 。
break 结束整个循环。下一步执行循环下面的代码。
return 不是循环控制的语句,结束函数。
六.一维数组(集合)
1.why:
对于同一类数据的批处理,比同类数据每个一个变量没在一起操作不便捷
2.定义:
int a[10];
int arr [ 10 ];
数组长度是 10 是数组中元素的个数 不能省略,不能使用变量
每个元素的类型是 int 类型
数组名叫 arr
arr[0];// 第一个元素的下标一定是 0
arr[9];// 最后一个元素的下标一定是 长度 -1
不能给整个数组一次性赋值,只能逐个元素赋值
arr[0] = 10;
arr[1] = 100;
区分: int a[5] 和 a[5] 的区别:
int a[5] 是在定义一个数组
a[5] 是数组一个元素
3.赋值
C/C++ 中没有给数组整体赋值的语法,只能给每个元素单独赋值利用循环赋值
objectivec
#include <stdio.h>
int main()
{
int grade[10];//定义数组grade,长度为10,类型是int
grade[0] = 1;//给0元素赋值
grade[1] = 10;//给1元素赋值
int i;
for(i = 3;i < 10;i++)//给3~9元素赋值
{
grade[i] = i;//数组元素的下标可以用变量表示
}
grade[2] = 250;//给2元素赋值,给数组元素赋值不需要顺序
4.初始化问题
完全初始化: int a[10] = {1,2,3,4,5,6,7,8,9,10};
每个元素都有值,按顺序给每个元素初始化
部分初始化: int a[10] = {1,2};
初始化使用的数据的数量小于元素的个数,没有初始化数据的元素默认是0
默认初始化: int array[] = {1,2,3,4,5,0,0,0};
省略数组长度,根据初始化数据的数量决定数组长度。
5.如何遍历
每个元素都过一遍,不管对元素进行什么操作都叫遍历。
遍历数组一般使用for循环,并且使用循环变量作为数组的下标。
for(i = 0;i < 10;i++)
{
printf("%d ", grade[i]);//打印所有元素的值
}
printf("\n");
return 0;
}
4.初始化
完全初始化: int a[10] = {1,2,3,4,5,6,7,8,9,10}; 每个元素都有值,按顺序给每个元素初始化
部分初始化: int a[10] = {1,2}; 初始化使用的数据的数量小于元素的个数,没有初始化数据的元素默认是0
默认初始化: int array[] = {1,2,3,4,5,0,0,0}; 省略数组长度,根据初始化数据的数量决定数组长度
5.遍历
利用循环遍历
objectivec
//每个元素都过一遍,不管对元素进行什么操作都叫遍历。
//遍历数组一般使用for循环,并且使用循环变量作为数组的下标。
int grade[10] = {0};
int i;
for(i = 0;i < 10;i++)//一般使用循环变量作为数组下标,所以循环变量一般从0开始
{
printf("%d\n", grade[i]);
}
6.类型问题
数组的元素可以定义成任何类型。能定义出什么类型的变量,就能定义出什么类型的数组。
char a[10]; short b[100]; float c[20]; double x[30];
七.🔺字符
1.字符数组
区别:
在语法上和其他类型数组都一样,但是多了一种用法,就是可以存放字符串,C 语言没有字符串类型的变量,只能使用字符数组保存字符串。
字符串常量:
1.一些连续的字符,++以字符++ ++'\0'为结尾,就是字符串。++
2.在 C 语言中字符串常量++必须由++ ++""包含,当由""包含字符串时,系统会默认在尾部加上'\0'++
"hello world" h e l l o 空格 w o r l d '\0'
"w" 有双引号就是字符串常量 'w' '\0'
"" 空字符串 '\0'
初始化:
可以使用字符串常量初始化字符数组
char s[] = "hello"; //使用字符串常量初始化字符数组 也可以 char s[] = {"hello"}; 数组 s 长度是 6
char a[] = {'h','e','l','l','o','\0'};//数组 a 长度是 6 数组 a 中的数据是字符串,因为以 '\0'
char b[] = {'h','e','l','l','o'}; //数组 b 长度是 5 ,数组 b 中的数据不是字符串,因为没有以 '\0' 结尾
char c[10] = {'h','e','l','l','o'}; //数组 c 中的数据,是字符串,因为以 '\0' 结尾,部分初始化自动补 0,0 就是'\0' 字符串 长度是 5 ,第一个 '\0'就是字符串结尾(输出时遇到第一个 '\0' 就停止,因为遇到了字符串的结尾, ++'\0'++ ++后面的内容都不属于字符串了++。)
注意点:
- 字符串常量中的 '\0' 是由 c 编译系统自动加上的。
- 字符串常量是由双引号引起来的。
- 在字符串部分,我们会接触两种长度:
字符串长度 字符串中不算'\0' 的字符个数 "hello world" 字符串长度是 11
字符串占内存的大小(长度) "hello world" 12 因为要给 '\0' 留一个字节的空间
如果定义字符数组保存字符串 "hello world" ,数组长度至少是 12
2.输入输出
字符输入输出
输入: getchar(); 输入一个字符 char a = getchar(); //将输入的字符赋值给变量a
输出: putchar(); 输出一个字符 char a = 'b'; putchar(a);// 输出变量 a 的字符
字符串输入输出
++输入: gets(); scanf读取字符串 不能输入空格 hello world 只能读取hello剩余的 "world" 会留在输入缓冲区中,可能影响后续的输入。此时如果再用gets,即使输入其他但是一定会打印缓存中的world,gets和scanf会自动在输入的字符串结尾加'\0'++
输出: puts(); puts会自动换行,printf需要自己加\n
3.字符串处理函数#include <string.h>
函数可嵌套调用 ,但是不要嵌套太多,因为影响可读性,虽然代码变短了,但是不简洁。
求字符串长度的函数:
strlen(arr)读作:string length
int strlen( const char *string );
参数:字符串常量或者 数组名
返回值:是整数,字符串长度,++不算++ ++'\0'++
字符串拷贝函数:
读作: string copy
char *strcpy( char *strDestination, const char *strSource );
将一个字符串拷贝到一个字符数组中
参数 1 :是数组,字符数组的数组名
参数 2 :字符串常量或者字符数组的数组名
返回值:是参数 1
字符串连接函数:
读作: string catenate
char* strcat(char* strDestination, const char* strSource);
将两个字符串拼接成一个字符串
strcpy 和 strcat 必须我们来保证数组有足够大的空间,函数不负责。
参数 1 :是一个含有字符串的数组,将参数 2 的字符串拼接到参数 1 的字符串后面
参数 2 :字符串常量,字符数组
返回值:参数 1
字符串比较函数:
读作: string compare
int strcmp( const char *string1, const char *string2 );
参数 1 :可以是字符串常量,也可以是数组名
参数 2 :可以是字符串常量,也可以是数组名
返回值:整数 s1>s2 结果 >0 s1==s2 结果 0 s1<s2 结果是 <0
写代码使用 strcmp 时,都和 0 比较,不要和 1 和 -1 做比较。
4.遍历字符串
objectivec
#include <stdio.h>
int main()
{
char a[100] = "hello";//数组a存放字符串 hello
int i = 0;
while(a[i] != '\0')//a[i] != '\0'是遍历字符串的结束条件,遍历字符串不管数组有多长,只
看是否到达第一个'\0'
{
putchar(a[i]);//输出字符 也可以使用printf("%c", a[i])
i++;//找下一个字符
}
putchar('\n');
//或者
for(i = 0;a[i] != '\0';i++)//使用for循环,不数数,只看是否到达第一个'\0'
{
putchar(a[i]);
}
putchar('\n');
return 0;
}
八.二维数组
1.本质:(具体在指针部分详解)
特殊的一维数组,其中的每个元素是另一个一维数组的首地址,当我们需要若干功能相似,类型相同的一维数组时,用二维数组。 (类型同一维数组一样)
2.定义
int a [ 2 ][ 3 ]; //a是一个二维数组,它的每个元素是一个一维数组(int[3]),a有2个元素,每个元素是一个一维数组。 (所以二维数组可以省略行但是不能省略列)

3.赋值
和一维数组一样只能单独赋值不可一群赋值
4.初始化
完全初始化:数据的数量个元素的数量相同
int a[2][3] = {{1,2,3},{4,5,6}}; ==int a[2][3] = {1,2,3,4,5,6};
部分初始化:数据的数量小于数组的元素个数int a[2][3] = {1,2,3};
默认初始化:定义二维数组时,第一个[]里面的数字是二维数组的长度,第二个[]里面的数字是作为元素的一维数组的长度++【所以可以省略行但是不可省略列】++
int a [][ 3 ] = {{ 1 , 2 , 3 },{ 4 , 5 , 6 }};
a 的长度是 2 a [ 0 ] = { 1 , 2 , 3 } a [ 1 ] = { 4 , 5 , 6 }
int a [][ 3 ] = { 1 , 2 , 3 , 4 };
a 的长度是 2 a [ 0 ] = { 1 , 2 , 3 } a [ 1 ] = { 4 , 0 , 0 }
5.遍历
遍历二维数组使用双重 for 循环,外层循环遍历第一个角标,内层循环遍历第二个角标
#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("%d\n", a[i][j]);
}
}
return 0;
}
九.▲函数
1.what和why:
函数是对代码的封装,因为有些代码会经常使用一遍遍的打很烦,所以封装成函数提高效率减少代码量。
2.分类
系统函数: printf scanf putchar getchar puts gets strlen strcpy strcat strcmp
自定义函数: 对于完整的函数实现,包含四个部分:
返回值类型 函数名(形参列表( 可以为空 )) { 函数体 }
3.函数的调用与执行
使用要先调用,调用要先声明(++声明在预处理和主函数之前,因为函数是调用才执行,不写在前面声明,调用不到)++
主调函数和被调函数:函数 A 调用函数 B ,那么 A 就是主调函数, B 就是被调函数。
调用函数的语法:
函数名 ( 实参 );
4.形参与实参(参数列表)
作用 :主调函数给被调函数传数据
实参:主调函数要传给被调函数的数据。
形参:定义在被调函数参数列表中的变量,它和普通变量的使用方式一样,而且它会被实参初始化。 形参的作用域 就是被调函数的函数体。
++形参变量是在执行被调函数的时候创建,创建的时候会被实参初始化。++
注意要点:
- 信息传递的方向: 由 主调函数 传递给 被调函数。
- 形参与实参,个数要一致,类型要一致。
5.返回值
作用:被调函数将运算得到的结果传给主调函数(void的类型可无返回值)
注意要点:
信息传递的方向: 被调函数 传给 主调函数
返回值类型: 在定义函数的时候,函数名左边的类型,决定函数的返回值类型。
++void表示没有返回值。void 类型不能定义变量。++
注意:返回值只能返回一个值。
++不可以返回数组,因为C语言的数组不能整体赋值。++
return :当函数中执行了 return 函数马上结束,接下来执行主调函数中被调函数的下一行代码。 return 后面可以有表达式,表达式的运算结果就是要传给主调函数的值。定义函数时的返回值类型决定了return表达式的结 果的类型。
6.变量作用域
变量起作用的范围。
{} 就是作用域,在 {} 里面定义的变量,出了 {} 范围不能使用。
7.局部变量与全局变量
定义:
局部变量: 定义在函数中的变量,就是局部变量,是有作用域限制的。
全局变量: 定义在函数以外的变量,是全局变量,它没有作用域的限制。
对比:
- 对于全局变量,如果没有进行初始化操作。那么这个变量默认为 0 。局部变量默认没值。
- 生命周期:全局变量从程序开始执行创建,到程序结束删除。局部变量从所在的{} 开始执行创建, {} 执行完删除。
- 作用域: 全局变量程序的任何地方都可以用。 局部变量只能在定义的{}里使用。
- 全局变量不能重名。 局部变量在同一个作用域里也不能重名。
- 全局变量与局部变量 重名的问题。 当小作用域和大作用域变量重名时,优先使用小作用域的变量
8.数组与函数
数组作为参数传过去,实参数组名与数组长度,形参指针和n
#include <stdio.h>
void print_array(int *p, int n)
{
//p指向实参数组,p[i]就是数组的元素
int i;
for(i=0; i<n; i++)
{
printf("%d, ", p[i]);
}
printf("\n");
}
int main()
{
int a[6] = {1,2,3,4,5,6};
int b[8] = {6,6,6,6,6,6,6,6};
print_array(a,6);//首地址和数组长度作为实参
print_array(b,8);
return 0;
}
**sizeof()**计算数组长度
sizeof(数组名)可以计算数组占内存大小
所以sizeof(a)/sizeof(int)的确可以计算数组长度,但是有数组名的作用域一定是定义数组的作
用域,此时不计算也知道数组有多长,所以一般在主函数那有点多余,形参那 32 位系统,任何类型的指针变量都占 4 个字节,也计算不出数组大小,所以传参加上长度。
9.总结
-
返回值类型 函数名(形参列表 ( 可以为空 ))
{ 函数体(代码+return ) }函数名();//声明
void fun()//函数
{
int a;
}
int main()
{
fun();//使用
a = 10;
return 0;
}
2.返回值类型是void 表示没有返回值
如果一个函数的返回值是void 类型,它能用 return 吗?
能 return ; return后面不能有值,仅仅是结束函数
3.变量声明周期(即其作用域):全局和局部(同名优先靠近数据)。
十.🔺指针
1.what:
指针变量的本质是变量;变量用来表达逻辑 ;整型变量表示整数 ;float变量表示小数 ;指针变量表示地址 指针变量的运算是对地址的运算。是存放地址的变量
地址:为了方便查找变量,计算机按照字节为单位对内存进行编址,每个字节存放8各个二进制位。

2.地址运算
1**、地址偏移**
-
- 地址+N( 整数) 地址-N(整数) (加减都是N个类型大小地址)
运算结果:什么类型的地址运算就得到什么类型的地址。
- 地址+N( 整数) 地址-N(整数) (加减都是N个类型大小地址)
2**、间接运算 ***
* 单目运算,乘法是二元运算
间接运算只能对地址运算,运算结果不是数值,而是地址对应的对象。变量、字符串常量、数组。
对谁的地址间接运算,就得到谁。
对什么类型的地址间接运算,就得到什么类型的 对象 。
对象 & 对象的地址
对象的地址 * 对象
3.定义指针变量
//复合声明
int* p;
// * 说明p是指针变量,指针变量存放地址,由于地址有类型,所以我们还要说清楚地址的类型,int表示p
存放的地址类型的int类型的地址
/*
在C语言中,出现在定义中的符号都不是运算符,而是用来表示类型的符号
这样的符号C语言中一共有3个
int a;
int* a; * 表示指针类型变量的类型
int a[10]; [] 表示数组类型
int a(); () 表示函数类型
*/
初始化与赋值:int* a=&b;*a=10(=b=10)
输出格式符使用:%p (推荐) %x %#x %d %o (都会有警告)
意义之一:在被调函数中,操作主调函数中的变量。此时指针变量作为被调函数的形参,实参是主调函数中量的地址。
//错误
#include <stdio.h>
void fun(int b);
int main()
{
int a;
fun(a);//使用实参a给形参b初始化,形参b和实参a是完全独立的两个变量,所以给b赋值和a没有关系
printf("%d\n", a);//随机值
return 0;
}
正确范例
7.指针指向字符串常量:
char s1[] = "hello world";
char* s2 = "hello world"; //将字符串常量的首地址初始化了指针变量s2,s2指向字符串常量"hello
world"
实际上s2指向的是字符串常量中的h
指向:当一个指针变量存发一个对象的地址,我们称指针变量指向这个对象。
void fun(int b)//期望在fun中给a赋值
{
b = 10;
}
//正确
void fun(int* b)//由于实参是&a,是int类型的地址,所以形参是int*,因为int*变量可以存放int类
型的地址
{
*b = 10;//于是*b运算得到main中的a,实现了给a赋值
}
int main()
{
int a;
/*
因为我要在fun中给main中的a赋值,由于不在一个作用域,那么不能在fun直接使用a,于是我们
可以将&a传给fun,在fun中使用间接运算得到a,然后就可以对a赋值了
*/
fun(&a);//所以实参是&a
printf("%d\n", a);
return 0;
}
4.指针指向字符串常量
char s1[] = "hello world";
char* s2 = "hello world"; // 将字符串常量的首地址初始化了指针变量 s2 , s2 指向字符串常量 "hello
world"
实际上 s2 指向的是字符串常量中的 h
指向:当一个指针变量存发一个对象的地址,我们称指针变量指向这个对象。
int main()
{
char s1[] = "hello world";
char* s2 = "hello world";
printf("%s\n", s1);//hello world
printf("%s\n", s2);//hello world
s1[0] = 'w';//因为数组元素全是变量,所以可以对s[0]赋值
//下面代码不是语法错误
*s2 = 'w';//运算得到字符串常量中的h,h也是常量,因为h是字符串常量中的一部分,对常量赋值会
崩溃
return 0;
}
5.指针变量的大小
32 位计算机 4 个字节编址 32 位计算机,所有地址都是 4 个字节
64 位计算机 8 个字节编址 64 位计算机,所有地址都是 8 个字节
6.指针与数组关联
1 )数组名是数组首元素的地址,是元素类型的地址, 是常量 int a[10] ; a 是 a[0] 的地址,是 int
类型的地址
2 )数组元素在内存中是连续排列的。 int a[10] ; &a[1] 比 &a[0] 大 4 个字节
只要有数组的首地址,就可以根据偏移运算得到每个元素的地址,从而得到每个元素。
#include <stdio.h>
int main()
{
int a[5];
int i;
printf("a = %p\n", a);//输出数组的首元素地址
for(i = 0;i < 5;i++)
{
/*
循环打印5个元素的地址
&a[0]和a是一样
每个元素的地址都比前面的元素大4个字节
*/
printf("%p\n", &a[i]);
}
return 0;
}
++数组名是地址,能偏移运算,能间接运算。++
下标运算符
\] 是二元运算,左值是地址,右值是整数, \*(左值+右值)
(地址+整数)先偏移运算得到i元素地址,然后\*间接运算得到i元素本身
int main()
{
int a[5] = {12,23,34,45,56};
int i;
for(i = 0;i < 5;i++)
{
//a+i就是 a[i]的地址 a[i] 本质上就是 *(a+i)
//a+i是地址的偏移运算,得到i元素的地址
printf("%p %p\n", &a[i], a+i);
}
for(i = 0;i < 5;i++)
{
//因为a+i是i元素的地址,所以*(a+i)就是数组的i元素
printf("%d %d\n", a[i], *(a+i));
}
return 0;
}
将数组的首元素地址赋值给指针,我们叫指针指向数组。
定义一个指针指向一个数组,指针的类型因该是数组的元素类型。
### 7.空指针与野指针
#### 野指针:悬垂指针(别名)
当指针中存放的地址不是有效的地址,就是野指针。
char* fun()
{
char s[10];
return s;
}
int main()
{
char* p = fun();//p 就是野指针
char* p2;//没有初始化也是野指针
*p2 = 'A';//或许好使,或许崩溃
return 0;
}
野指针非常危险,对野指针间接运算不知道得到谁的地址,会造成不可预料的后果(程序有可能崩溃,也可能不崩溃)。
#### 空指针
使用 NULL (既是 0 ) 给指针赋值,就是空指针。
NULL 在 stdio.h 中声明
当我们使用的指针还不确定该指向哪个对象的时候,应该赋值为空指针。
char\* p = NULL ;
\* p = 'A' ; // 程序必然崩溃
## 十一.字符串
### **C语言对字符串的处理有两个要素:**
由于C语言没有字符串类型的变量,所以对于字符串的操作主要依赖于逻辑。
字符串的首地址。C语言所有对字符串的操作都是从首地址开始的。
2 '\\0' 是字符串结束的表示。C语言对字符串所有的操作都是到'\\0' 结束的。
### 常用方法:
char s[] = "hello";
char* s1 = "hello";//指针s1指向字符串常量"hello",将"hello"这个字符串常量的首地址初始化指
针s1
s1 = "hello world";//将字符串常量"hello world"的首地址赋值给指针s1
strcpy(s, "hello");//参数1是数组首地址,参数2 是"hello"的首地址
### 输出:
%s是字符串的格式符,需要的参数是字符串的首地址,处理到'\\0' 为止。
## 十二.内存分配
### 1.内存分区
常量区 1 'a' "hello world"
堆区 动态内存分配,我们自己决定变量的生命周期
栈区 局部变量
静态区 全局变量和静态局部变量
栈( stack ):普通的局部变量都在栈空间。作用域开始执行创建对象,作用域执行完毕自动释放对象。
堆( heap ):调用 malloc 函数创建,调用 free 释放。不释放会造成 " 内存泄漏 " 。
静态( bss : Block Started by Symbol 和 data ):全局变量或者静态变量。程序执行创建对象,程序结束自动释放对象。
堆、栈、静态的主要区别就是对于对象的生命周期管理不同。所以我们到底要将变量定义在哪个空间,取决于我们需要一个什么样的生命周期。
### 2.**堆空间内存的申请与释放**#include \