本篇文章将接着上篇继续介绍C语言的基础知识,使读者对C语言能够有一个大概的认识. 不会细写每一个知识点, 但是能够入门C语言, 进行初步的C语言代码阅读.
1. 选择语句
假设你要决定是否出门玩耍,如果天气晴朗,你就出门;如果天气不好,你就呆在家里。这是我们生活中的例子.
当编写程序时,经常需要根据不同情况执行不同的操作。C语言中的选择语句允许我们根据条件来选择执行哪些代码。
int main()
{
int isSunny = 0; // 假设1表示晴朗,0表示不晴朗
printf("天气好吗(1/0)?");
scanf("%d\n", &isSunny);
if (isSunny)
{
printf("今天天气晴朗,出门玩耍!\n");
}
else
{
printf("今天天气不好,呆在家里吧。\n");
}
return 0;
}
当然, C语言中还有switch语句用于实现选择.
2. 循环语句
循环语句在编程中用于重复执行一段代码,直到满足某个条件为止。我们在这里介绍while循环.
int main()
{
int line = 0;
printf("好好学习\n");
while (line < 20000)
{
printf("写代码: %d\n", line);
line++;
}
if (line >= 20000)
{
printf("好offer");
}
else
{
printf("继续加油\n");
}
return 0;
}
可以看到, 在运行窗口中打印了两万次的写代码计数.
3. 函数
我们来看下面的代码:
程序执行后在控制台输入10 20, 按下回车就可以进行求和的一个计算.
在求和部分的代码我们是使用了一个表达式来计算结果, 那么我们可以使用一个函数来进行这个计算的工作.
类似于之前strlen()求字符串长度的函数, 该函数完成了求长度的工作, 然后把结果返回并给到len, 最后打印.
也类似于数学中的函数, 比如函数, 通过给入不同的x值, 可以得出不同的函数值.
// 定义一个返回类型为int, 名为的函数, 在主函数中让 n1 传给 函数参数x, n2 传给 函数参数y.
int Add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int n1 = 0;
int n2 = 0;
// 输入
scanf("%d %d", &n1, &n2);
// 求和
//int sum = n1 + n2;
int sum = Add(n1, n2);
// 打印
printf("%d\n", sum);
return 0;
}
我们可以将函数想象成一个工厂, 工厂的原理是利用原材料进行加工, 生产得到产品.
函数也是一样, 它就相当于是工厂的角色, n1和n2就相当于是原材料, z就是相当于产品的结果.
那么n1, n2就是 输入数据, z就是 输出数据.
在函数Add{}中的代码就称为 函数体.
注: 也可以直接在返回语句中进行计算.
int Add(int x, int y)
{
return x + y;
}
4. 数组
要存储1-10的数字,怎么存储?
C语言中给了数组的定义:一组相同类型元素的集合.
4.1 数组的定义
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; //定义一个整形数组,最多放10个元素
4.2 数组的下标
C语言规定: 数组的每个元素都有一个下标,下标是从0开始的。数组可以通过下标来访问的。
比如:
int arr[10] = { 0 };
//如果数组10个元素,下标的范围是0-9
4.3 数组的使用
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int i = 0;
while (i < 10)
{
printf("%d ", arr[i]);
i = i + 1;
}
return 0;
}
5. 操作符
C语言提供了许多操作符, 我们经常能看到, C语言是非常灵活的, 这个"灵活"就体现在它提供了非常多的操作符.
算术操作符
+ - * / %
/
和%
的区别:
/
是取商, %
是取余数.
再比如37÷4=9......1, 其中37是被除数,4是除数,商是9,但有余数1 , 对应的9是/运算结果, 1是%运算结果.
/
除号两端都是整数的时候, 执行的是整数除法,如果两端只要有一个浮点数就执行浮点数的除法
%
操作符的两个操作符只能是整数
移位操作符
>> <<
位操作符
& ^ |
赋值操作符
= += -= *= /= &= ^= |= >>= <<=
单目操作符
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置、后置--
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
- sizeof不是一个函数, 而是一个单目操作符, 用于拿到操作数的类型长度(以字节为单位)
- 前置后置++, 前置后置--
- (类型) -> 强制类型转换
int a = 3.14;
// 3.14 字面浮点数, 编译器默认理解为 double 类型
所以需要添加强制类型转换操作符.
关系操作符
>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”
逻辑操作符
&& 逻辑与
|| 逻辑或
在C语言中,&&
(逻辑与)和||
(逻辑或)是两个用于组合条件的逻辑运算符。
&&
(逻辑与):
&&
运算符用于将两个条件组合在一起,只有当两个条件都为真时,整个组合条件才为真。如果任何一个条件为假,整个组合条件就为假。
示例:
#include <stdio.h>
int main() {
int age = 25;
int isStudent = 1;
if (age >= 18 && isStudent == 1) {
printf("你是成年学生。\n");
} else {
printf("你不符合条件。\n");
}
return 0;
}
在上面的例子中,age >= 18
和isStudent == 1
两个条件都必须为真,才会打印"你是成年学生"。如果其中任何一个条件为假,就会打印"你不符合条件"。
||
(逻辑或):
||
运算符用于将两个条件组合在一起,只要其中一个条件为真,整个组合条件就为真。只有当两个条件都为假时,整个组合条件才为假。
示例:
#include <stdio.h>
int main() {
int hasMoney = 0;
int hasCreditCard = 1;
if (hasMoney == 1 || hasCreditCard == 1) {
printf("你有支付手段。\n");
} else {
printf("你没有支付手段。\n");
}
return 0;
}
在上面的例子中,hasMoney == 1
和hasCreditCard == 1
两个条件中只要有一个为真,就会打印"你有支付手段"。如果两个条件都为假,才会打印"你没有支付手段"。
总结一下:
&&
(逻辑与)要求所有条件都为真才为真。
||
(逻辑或)只要有一个条件为真就为真。
条件操作符
exp1 ? exp2 : exp3
条件表达式 `exp1 ? exp2 : exp3` 是C语言中的一种三元运算符,也称为条件运算符或三元条件表达式。它允许你在一个表达式中根据条件的真假来选择执行不同的操作。这个运算符的结构如下:
条件 ? 表达式1 : 表达式2
- 如果条件为真,表达式1将被执行,其结果将成为整个表达式的值。
- 如果条件为假,表达式2将被执行,其结果将成为整个表达式的值。
#include <stdio.h>
int main() {
int x = 10;
int y = 20;
int max = (x > y) ? x : y;
printf("最大值是:%d\n", max);
return 0;
}
逗号表达式
逗号表达式就是逗号隔开的一串表达式, 特点是从左向右依次计算, 整个表达式的结果是最后一个表达式的结果. 其形式如下:
exp1, exp2, exp3, …expN
下标引用, 函数调用和结构成员
[] () . ->
// []
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
arr[3] = 20; // []就是下标引用操作符 arr和3就是[]的操作数
return 0;
}
// 函数调用操作符
int Add(int x, int y)
{
return x + y;
}
int main()
{
int sum = Add(2, 3); // ()就是函数调用操作符, Add, 2, 3都是()的操作数
return 0;
}
6. 常见关键字
auto break case char const continue default do double else enum
extern float for goto if int long register return short signed
sizeof static struct switch typedef union unsigned void volatile while
C语言提供了丰富的关键字,这些关键字都是语言本身预先设定好的,用户自己是不能创造关键字的。
auto: 在现代C语言已经不常用, 最早用于定义自动存储类别的变量.
break: 一般常用在循环结构中, 比如for, while, do while, 也用于选择结构的switch, case选择.
case: 用于switch语句中. 还有default关键字也用于switch语句中.
continue: 也是用于循环的关键字
goto: 实现跳转
char: 是数据类型用于表示字符. 像short, int, long, float, double也都是属于数据类型的. 但是也都是C语言中预定好的关键字.
const: 用于修饰常属性.
enum: 枚举
struct: 结构体
union: 联合体(共用体)
extern: 声明外部符号
register: 寄存器
static: 静态的
signed: 有符号的
unsigned: 无符号的
typedef: 类型重命名
void: 空, 表示无(函数的返回类型, 函数参数)
注意: 变量命名不能使用关键字.
6.1 关键字typedef
typedef 顾名思义是类型定义,这里应该理解为类型重命名.
比如:
typedef unsigned int uint; // 把 unsigned int 重命名为 uint
typedef struct Node // 结构体类型, 使用起来很抽象, 不方便. 所以在前面加上typedef, 并在结尾给它起名为Node.
{
int data;
struct Node* next;
}Node;
int main()
{
unsigned int num = 0; // unsigned int 较长, 输入麻烦
uint num2 = 1;
struct Node n; // 正常情况下要这么写, 很抽象
Node n2; // Node 此时就等价于 struct Node
return 0;
}
6.2 关键字static
在C语言中:
static是用来修饰变量和函数的
1.修饰局部变量-称为静态局部变量
2.修饰全局变量-称为静态全局变量
3.修饰函数-称为静态函数
我们先来看如下的例子:
void test()
{
int a = 1;
a++;
printf("%d ", a);
}
int main()
{
int i = 0;
while (i < 10)
{
test();
i++;
}
return 0;
}
分析代码可知,
1. main函数开始执行,初始化整数变量 i 为0。
2. 进入 while循环,条件 i < 10成立,进入循环体。
3. 在循环体内部,调用 test 函数。
4. test函数内部定义了一个整数变量 a,并初始化为1,然后执行 a++,将 a 的值增加到2。
5. 接着,printf 函数打印 a 的值,即2,然后输出一个空格。
6. test 函数执行完毕,返回到 while 循环中,增加 i 的值到1。
7. 回到循环条件,i 仍然小于10,所以继续执行下一轮循环。
8. 重复上述步骤,每次调用 test 函数都会使 a 的值增加到2,然后打印2。
9. 当循环执行10次后,i 的值增加到10,不再满足 i < 10 的条件,循环结束。
因此,代码的执行结果是打印出10个2,而不是10个3。 每次调用 test 函数都会创建一个新的局部变量 a,并且该变量的生命周期仅在函数调用期间 。因此,a 的值在每次调用 test 函数时都会重新初始化为1,然后增加到2。
运行结果:
static修饰局部变量
前面提到, static可用于修饰局部变量, 那么我们尝试将其使用到test()中的变量a, 然后来看一下结果会是什么.
分析代码, 关键字static
在这里的作用是将变量a
变成一个静态局部变量, 它的生存期会持续整个程序的执行过程, 而不是每次函数调用都重新初始化.
总结如下
static修饰局部变量的时候,当局部变量出了作用域是不销毁的. 本质上,static修饰局部变量的时候,会将变量的存储位置进行更改.
我们在学习C/C++的时候, 经常会考虑变量的管理.
在内存中一般分为如下区域:
1. 栈区: 用于存放局部变量(...当然也有其他东西, 这里暂且省略). 栈区中局部变量的特点是进入它的作用域就创建, 离开作用域就销毁.
2. 堆区: 用于动态内存管理, 涉及函数有malloc/free, calloc, realloc等.
3. 静态区: 变量被static修饰之后就会存放于静态区, 成为静态变量. 静态变量则是离开它的作用域 不销毁, 会直至整个程序生命周期结束才销毁. (全局变量也会放在静态区)
所以实质上, 存储位置的改变影响了变量a
的生命周期,生命周期变长,和程序的生命周期一样。
static修饰全局变量
前文提到, 可以在{}
外部定义全局变量, 那么我们也可以把全局变量定义到外部文件中.
如图, 当我们在add.c这个源文件中创建变量g_val
, 此时它就是一个全局变量, 那么要在test.c中使用g_val
的话, 就需要使用extern
关键字在test.c中进行声明.
那么接下来当我们使用static去修饰g_val
这个全局变量时, 上述程序会直接报错.
static int g_val = 2023;
可以看到, 报错:无法解析的外部符号g_val
. 虽然g_val
已经在test.c中声明了, 但是依然不能使用.
补充: 1. 全局变量是具有外部链接属性; 2. 一个可执行的C程序是需要经过编译 + 链接两个步骤.
那么报错的原因是static
修饰全局变量的时候, 全局变量的外部链接属性被更改为内部链接属性, 使得其他源文件(.c)无法再使用到该全局变量.
我们使用的直观感受是
g_val
的作用域变小了.
static修饰函数
static修饰函数与static修饰全局变量较为类似
首先依旧是熟悉的加法操作的函数, 只不过这次将它放在add.c中, 并在main()所在文件test.c进行声明.
可以看到, 正常求和并打印了结果. 当它被static
修饰之后, 我们来看.
补充: 函数也是具有外部链接属性的
// add.c
static int Add(int x, int y)
{
return x + y;
}
可以看到, 报错:无法解析的外部符号Add
, 由文件位置可知是链接属性的问题.
所以, 一个函数本来是具有外部链接属性的, 但是被static
修饰的时候,外部链接属性就变成了内部链接属性,其他源文件(.c)就无法使用了。
7. #define
定义常量和宏
在C和C++中,#define
预处理指令用于定义常量和宏。它的一般语法如下:
#define 常量名 值
或者
#define 宏名(参数列表) 替换文本
- 定义常量:
使用 #define
来定义常量可以让你在代码中使用具体的值,这些值在编译时会被替换成代码中的文本。例如:
#define PI 3.14159265
这个定义将创建一个名为 PI
的常量,它在代码中的所有出现都会被替换为 3.14159265
。
- 定义宏:
使用 #define
还可以定义宏,宏是一种带有参数的代码替换机制。例如,你可以定义一个用于交换两个变量值的宏:
#define SWAP(a, b) { int temp = a; a = b; b = temp; }
然后,在你的代码中可以这样使用:
int x = 5, y = 10;
SWAP(x, y);
这将被展开为:
{ int temp = x; x = y; y = temp; }
宏允许你创建一些代码片段的快速替代,但要小心使用,因为它们没有类型检查,可能会导致一些不直观的错误。
注意事项:
- 常量名通常用大写字母表示,以区分于变量名。