C语言 ——— 认识C语言
目录
认识 main 函数
全局变量和局部变量
extern 关键字
常量
字面常量
const 修饰的常变量
#define 定义的标识符常量
枚举常量
'\0' 是字符串结束标志
%s 打印字符串
strlen 函数
转移字符 \ddd 和 \xdd
转移字符 \ddd
转移字符 \xdd
测试题
选择语句
单分支选择语句
多分支选择语句
循环语句
while 循环语句
for 循环语句
do while 循环语句
break 关键字和 continue 关键字
break 关键字
continue 关键字
scanf 函数的返回值
牛刀小试
oj题 - 判断一个整数是否能被5整除
oj题 - 计算 y 的值
oj题 - 时间转换
oj题 - 三角形判断
static 关键字
static 修饰局部变量
static 修饰全局变量
static 修饰函数
认识指针
理解指针
实际例子
指针变量的大小
认识 main 函数
main 函数是程序的入口,程序执行时会从 main 函数的第一行开始执行,且一个工程中 main 函数有且只有一个
标准的 main 函数格式:
int main()
{
return 0;
}
int 是类型,这里指的是 main 函数的返回类型
return 是返回的意思,通常 main 函数返回 0,表示正常结束程序
古老的 main 函数格式:
void main()
{
}
这是比较古老的 main 函数格式,void 表示没有返回值,不用 return 返回
如果书籍或者教材中有这种 main 函数的写法,大概率可以不用看了
不接受参数的 main 函数格式:
int main(void)
{
return 0;
}
main 函数括号中的 void 表示这个 main 函数不接受任何参数
sizeof 操作符
sizeof 操作符的作用是计算 类型/变量 所占内存空间的大小,单位是字节(byte)
sizeof 计算内置类型所占空间的大小:
int main()
{
printf("%d\n", sizeof(char));
printf("%d\n", sizeof(short));
printf("%d\n", sizeof(int));
printf("%d\n", sizeof(long));
printf("%d\n", sizeof(long long));
printf("%d\n", sizeof(float));
printf("%d\n", sizeof(double));
return 0;
}
%d 的意思是以 10 进制的形式打印一个整数,因为 sizeof 关键字的返回类型是整形,所以使用 %d 的形式打印
运行代码:
可以看到 char 类型所占内存空间大小为 1 字节、short 类型所占空间大小为 2 字节...
因为 C语言 规定,只要 sizeof(long) >= sizeof(int) 即可,所以 sizeof(long) 的大小是 4 字节,在有些平台下 sizeof(long) 的大小是 8 字节
全局变量和局部变量
代码演示:
int b = 100;
int main()
{
int a = 10;
return 0;
}
定义在 main 函数内部的变量 a 就是局部变量
定义在 main 函数外部的变量 b 就是全局变量
生命周期:
变量 a 的生命周期是在 main 函数内部,当 mian 函数执行完了 return 后,变量 a 的生命周期就结束了
变量 b 的生命周期是整个工程结束时,变量 b 的生命周期才结束
当全局变量的变量名和局部变量的变量名冲突时:
int a = 100;
int main()
{
int a = 10;
return 0;
}
通过 printf 函数测试代码是否能正常执行,并且测试出有限执行的哪个变量
测试代码:
可以看到,优先执行了局部的变量 a
小结:
首先原则上变量名尽量不要冲突,但是当全局变量和局部变量的名字冲突的情况下,局部变量优先
extern 关键字
extern 关键字的作用是声明外部符号
例如:
有两个 .c 文件,分别为 add.c 文件和 test.c 文件
add.c 文件的代码:
int a = 2025;
那么想要在 test.c 文件中使用 add.c 文件中的变量 a,就需要使用 extern 关键字
test.c 文件使用变量 a:
extern int a;
int main()
{
printf("a = %d\n", a);
return 0;
}
要想使用 add.c 文件中的变量 a,要先在 test.c 文件中对变量 a 进行声明,才能正常使用
运行 test.c 文件:
常量
字面常量
字面常量顾名思义就是字面意义的常量,不能改变的量就叫常量
比如:圆周率、由 0-9 组成的数字、字符、字符串...
const 修饰的常变量
代码演示:
const int a = 10;
定义了一个 int 类型的变量 a,并且这个变量 a 被 const 修饰了,此时 a 就具有了常属性
也就表示 a 的值不能被改变了,只能在初始化的时候赋值
且 a 本质还是一个变量,因为不能给数组定义长度,只是 a 这个变量不能被修改,所以称 a 为常变量
#define 定义的标识符常量
代码演示:
#define SIZE 10
这个代码可以理解为给 10 这个常量取了一个名字叫 SIZE,此时的 SIZE 就是标识符常量
那么 SIZE 就能给其他同类型的变量赋值,且可以给数组定义长度
在执行代码时,程序会把有 SIZE 的地方全部替换为常量 10
枚举常量
枚举常量顾名思义是能被一一列举的常量,比如:性别、三原色、血型等
代码演示:
enum COLOR
{
Red,
Green,
Blue
};
enum 是定义枚举常量的关键字,COLOR 是枚举常量的结构体名称
其中 Red、Green、Blue 是枚举出的三原色,他们都有各自的默认值,Red 是 0,后面的依次递增
代码测试:
可以在初始化的时候重新给他们赋值,并且还是遵循依次递增的规定
'\0' 是字符串结束标志
'\0' 是表示字符串结束的标志,不论是用 %s 打印字符串,还是用 strlen 函数求字符串长度,都是遇到 '\0' 才结束,否则就会一直执行,知道遇到 '\0' 才停止
%s 打印字符串
%s 表示的是打印一串字符,一般和 printf 函数配合使用
代码演示:
char arr1[] = { 'a','b','c','d' };
char arr2[] = "abcd";
在数组 arr1 中存储了 4 个字符,但在数组 arr2 中不只是存储了 "abcd" 字符串,在 "abcd" 字符串末尾还存放了一个 '\0'
可以通过监视验证:
那么以 %s 分别打印两个数组时,arr1 数组就会除了打印字符还会打印其他未定义字符,直到遇到 '\0' 才会停止
代码验证:
strlen 函数
strlen 函数的作用是求字符串长度,所需要的头文件为 #include<string.h>
且 strlen 函数同样是求到 '\0' 之前的长度,当没有 '\0' 的话就会一直求长度,直到遇到 '\0' 为止
代码验证:
因为不知道数组 arr1 何时遇到 '\0' ,所以是随机值
解决办法:
可以在 arr1 数组中手动加上 '\0'
代码演示:
char arr1[] = { 'a','b','c','d','\0' };
这样不论是 %s 打印还是 strlen 求长度,都能正常输出结果
转移字符 \ddd 和 \xdd
转移字符 \ddd
其中 ddd 表示 1~3 个八进制的数字
代码演示:
printf("%d\n", '\130');
%d 是以十进制的方式打印,那么 130 这个八进制数字转换为十进制为 88
因为 130 各自的权重相乘在相加为 0*8^0 + 3*8^1 + 1*8^2 = 88
代码验证:
那么以 %c 字符形式打印,那么结果就会变成大写的 X,因为 88 所对应的 ASCII 码值就是 X
代码验证:
转移字符 \xdd
x 表示是十六进制的数字,其中 dd 表示 2 个十六进制的数字
代码演示:
printf("%d\n", '\x61');
十六进制的 61 转换为 十进制为:1*16^0 + 6*16^1 = 97
代码验证:
那么同样的道理,以 %c 的形式打印的话也就是打印 97 所对应的 ASCII 码值
代码验证:
测试题
代码演示:
printf("%d\n", strlen("C:\test\127\x56\test.c"));
问:strlen 求出的长度是多少
易错点:其中 '\t' '\127' '\x56' 是转移字符,本质只占用1个字节
所以统计后的结果应该是 14 字节
代码验证:
选择语句
单分支选择语句
代码演示:
int a = 0;
if (a == 0)
{
printf("a == 0\n");
}
else
{
printf("a != 0\n");
}
当满足 if 语句中的条件判断时,就不会执行 else 语句,否则就相反
所以代码中只会执行 if 语句内的代码
多分支选择语句
代码演示:
int a = 0;
if (a > 0)
{
printf("a > 0\n");
}
else if (a < 0)
{
printf("a < 0\n");
}
else
{
printf("a == 0\n");
}
既不满足 if 语句的条件,也不满足 else if 语句的条件,所以最后会打印 else 语句中的代码
循环语句
循环语句顾名思义就是循环执行循环内部的代码,直到满足特定的条件才会跳出循环
while 循环语句
代码演示:
int a = 0;
while (a < 5)
{
printf("a = %d\n", a);
a++;
}
满足条件,进入 while 循环,进入 while 循环时,先打印,变量 a 再自增 1 ,最后直到 a 不小于 5 时就循环结束
代码验证:
for 循环语句
代码演示:
for (int a = 0;a < 5;a++)
{
printf("a = %d\n", a);
}
for(表达式1; 表达式2; 表达式3) 执行逻辑是:
先执行 表达式1,再判断 表达式2 是否满足条件,满足条件时进入 for 循环内部,执行完内部的代码后再回到 表达式3并且执行,执行完 表达式3 后再判断是否满足 表达式2 ,满足就再次进入循环(循环往复),不满足就跳出循环
代码验证:
do while 循环语句
代码演示:
int a = 0;
do
{
printf("a = %d\n", a);
a++;
} while (a < 5);
do while 循环语句的特点是:不论是否满足进入循环的条件,都会先进入循环内部执行一次代码,执行完之后再判断是否满足条件,满足就继续执行,不满足就跳出循环
代码验证:
break 关键字和 continue 关键字
break 关键字
break 关键字的作用是结束并且跳出当前循环
代码演示:
for (int a = 0;a < 5;a++)
{
printf("a = %d\n", a);
if (a == 3)
{
break;
}
}
当 a 等于 3 时就会执行 break,跳出循环
代码验证:
需要注意的是 break 只能跳出当前循环,如果是双重循环或者多重循环时,只能跳出 break 所在的那层循环
代码演示:
for (int i = 0;i < 1;i++)
{
for (int a = 0;a < 5;a++)
{
printf("a = %d\n", a);
if (a == 3)
{
break;
}
}
printf("i = %d\n", i);
}
当内部循环走到 break 跳出时,外部的循环依然要执行
代码验证:
continue 关键字
continue 关键字的作用是跳过循环内部 continue 下面的代码,从头执行循环(当然要判断是否满足循环条件,满足再继续执行,否则就结束循环)
代码演示:
for (int a = 0; a < 5; a++)
{
if (a == 3)
continue;
printf("a = %d\n", a);
}
当变量 a 等于 3 时就执行 continue,跳过后面的 printf 函数,重新执行循环
代码验证:
小结
break 和 continue 关键字只能在循环内部使用,否则就会报错
scanf 函数的返回值
scanf 函数的功能可以从键盘上读取数据,并且存储到对应的变量中
而 scanf 函数的返回值是读取数据的个数
代码演示:
int a = 0;
int b = 0;
int ret1 = scanf("%d", &a);
printf("ret1 = %d\n", ret1);
int ret2 = scanf("%d %d", &a, &b);
printf("ret2 = %d\n", ret2);
第一个 scanf 函数只从键盘接收一个数据,并且用 ret1 接收 scanf 函数的返回值
第二个 scanf 函数从键盘接收了两个数据,分别打印 两个 scanf 函数的返回值,看是否分别是 1 和 2
代码验证:
注意:
当 scanf 函数没有正常接收数据时,会返回 EOF(文件结束标志),可以通过让 scanf 函数接收 Ctrl+Z 键,就能返回 EOF
牛刀小试
oj题 - 判断一个整数是否能被5整除
题目描述:
输入一个正整数,判断这个数是否能被 5 整除,要求能多次输入输出
代码实现:
int input = 0;
while (scanf("%d", &input))
{
if (input % 5 == 0)
{
printf("%d能被5整除\n", input);
}
else
{
printf("%d不能被5整除\n", input);
}
}
代码解析:
直接利用 scanf 函数的返回值作为 while 循环的判断语句,只要正常接收了数据,就会进入循环
并且用 % 取模操作符进行判断,当 input%5 的余数为 0 时,就是能被 5 整除,否则就不能
代码验证:
oj题 - 计算 y 的值
题目描述:
已知一个函数 y=f(x),当 x>0 时, y=1,当 x=0 时,y=0,当 x<0 时,y=-1
要求输入 x,求出对应的 y
代码演示:
int x = 0;
int y = 0;
scanf("%d", &x);
if (x > 0)
y = 1;
else if (x < 0)
y = -1;
else
y = 0;
printf("y = %d", y);
oj题 - 时间转换
题目描述:
给定秒数 seconds,把秒转换为小时、分钟、秒,并且分别输出
代码演示:
int seconds = 0;
scanf("%d", &seconds);
int h = seconds / 3600;
int m = (seconds % 3600) / 60;
int s = seconds % 60;
printf("%d秒 = %d小时%d分钟%d秒", seconds, h, m, s);
代码解析:
1 小时为 3600 秒,所以 seconds 除以 3600 秒就能计算出有多少个小时
seconds 取模上 3600 秒就能计算出不足一小时的有多少秒,再除以 60 秒,就能计算出还有多少分钟
seconds 直接取模上 60 秒,就能计算出不足以 60 秒的有多少秒
代码验证:
oj题 - 三角形判断
题目描述:
输入三角形的 3 条边 a、b、c,判断是否能构成三角形,如果不能构成三角形直接输出"Not a triangle!",如果能构成三角形则判断这个是什么三角形,如果是等边三角形则输出"Equilateral triangle!",如果是等腰三角形则输出"Isosceles triangle!",如果是普通三角形则输出"Ordinary triangle!",并且要求能多组输入输出
代码演示:
int main()
{
int a = 0;
int b = 0;
int c = 0;
while(scanf("%d %d %d", &a, &b, &c) == 3)
{
if (a + b > c && a + c > b && b + c > a) //先判断是否能构成三角形:任意两边之和不能大于第三边
{
if (a == b && b == c) //等边三角形三条边都相等
{
printf("Equilateral triangle!\n");
}
else if ((a == b && a != c) || (a == c && a != b) || (b == c && b != a)) //等腰三角形任意两边相等
{
printf("Isosceles triangle!\n");
}
else //普通三角形
{
printf("Ordinary triangle!\n");
}
}
else
{
printf("Not a triangle!\n");
}
}
return 0;
}
代码验证:
static 关键字
static 修饰局部变量
被 static 修饰的局部变量称之为静态局部变量
代码演示:
void test()
{
static int a = 5;
a++;
printf("%d ", a);
}
int main()
{
for (int i = 0; i < 10; i++)
{
test();
}
printf("\n");
return 0;
}
对 test 函数内部定义的局部变量 a 进行了 static 修饰,此时的 a 就是静态局部变量
如果忽略 static 修饰的话,那么程序运行的结果就是 10 个 5,但是加上了 static 修饰,结果就会不同
代码验证:
可以看到,并没有每次进入 test 函数时都重新给变量 a 初始化,而是延续了上次递增的值
原因是因为变量 a 所存储的区域被 static 改变了,从栈区转换到了静态区
在 C语言 学习的时候,可以把内存大概划分为栈区、堆区、静态区
示意图:
所以 static 修饰局部变量改变了变量的存储位置,使得这个静态变量的生命周期延长了,直到程序结束时这个变量的生命周期才结束
static 修饰全局变量
被 static 修饰的全局变量称之为静全局部变量
add.c 文件代码演示:
static int val = 2025;
test.c 文件代码演示:
extern int val;
int main()
{
printf("%d", val);
return 0;
}
在 add.c 文件中定义了一个全局变量,并且在 test.c 文件中用 extern 关键字声明了 val 变量
如果忽略 static 关键字的话,在 test.c 中能够正常运行代码,因为任何一个全局变量都具有外部链接属性
但是全局变量 val 被 static 修饰后,val 变量就没有了外部链接属性,static 把 val 变量的外部链接属性转换为了内部链接属性,所以 val 变量只能在它所处的 add.c 文件内部使用
static 修饰函数
被 static 修饰的函数称之为静态函数,被修饰后的作用和 static 修饰全局变量后的作用相似
add.c 代码演示:
static int Add(int x, int y)
{
return x + y;
}
test.c 代码演示:
extern int Add(int x, int y);
int main()
{
printf("%d\n", Add(5, 3));
return 0;
}
Add 函数被 static 修饰后,同样把 Add 函数的外部链接属性转换为了内部链接属性
并且 Add 函数只能在 add.c 文件内部使用,其他任何 .c 文件都不能使用了
认识指针
指针是来管理内存的,通常使用指针来存放变量的地址
理解指针
代码演示:
int a = 10;
int* pa = &a;
& 操作符的作用于取地址,&a 就是取出变量 a 的地址
pa 的类型是 int* ,* 的意思是表明 pa 是指针变量,用来存放指针的,int 的意思是表明所要存放地址的数据类型
当变量 a 的地址存放到了 pa 中后,就可以利用 * 解引用来操作变量 a
代码演示:
*pa = 100;
*pa 可以理解为通过 pa 所存放的地址找到变量 a,再对 a 重新赋值
代码验证:
实际例子
写一个函数,交换两个整数的值
代码演示:
void swap(int* x, int* y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
int main()
{
int a = 3;
int b = 5;
swap(&a, &b);
printf("a = %d, b = %d\n", a, b);
return 0;
}
因为形参和实参是各自独立的空间,所以只有把 a 和 b 的地址传递给 swap 函数,才能改变 a 和 b 的值
代码验证:
指针变量的大小
因为指针是用来存放地址的,所以不论是什么类型的指针,在不同的平台下都是固定的
在 32 位平台下:
在 64 位平台下: