C语言-Day01

“C语言部分-Day01”

一、第一个C程序

1.1 C语言Hello World

源码:helloword.c

#include <stdio.h> // 预处理指令,用于包含头文件

int main(void) {
	printf("HelloWorld\n");
	return 0;
}

1.2 文件分类

主要文件分类:

​ 头文件:如stdio.h

​ 源文件: 如main.c

​ 配置文件

​ 音视频文件

​ 图片

​ …

Q1:

头文件指令<>""的区别

<xxx.h> 到系统目录寻找头文件,适用于系统带的头文件

“xxx.h” 先到当前目录寻找头文件,再到系统目录寻找,适用于自己写的头文件

1.3 注释

C语言的注释方式,分为单行注释和多行注释

// 单行注释

/*
   这是多行注释
   这是多行注释
   这是多行注释
   这是多行注释
*/

1.4 C程序的编译

在Linux中,C源码的编译指令如下:

// 编译
gcc helloworld.c -o a.out

其实编译共分为四个步骤,只不过上面的指令一次性执行完成了,按细分如下:

1、预处理阶段:将头文件代码合入源码文件中

gcc -E helloworld.c -o hello.i

2、编译阶段:通过c语言文件,生成汇编代码文件

gcc -S hello.i -o hello.s

3、汇编阶段:将汇编代码文件翻译成二进制语言指令,再将指令打包成二进制目标文件

gcc -C hello.s -o hello.o

4、链接阶段:将多个目标文件和标准库函数文件合并成一个可执行目标文件

gcc hello.o -o hello

1.5 Visual Studio 调试程序的方法

1、打断点

2、F5 开始调试

3、F10 逐过程:一行行执行,如函数内没有断点,遇到函数会直接把函数执行完。

4、F11 逐语句:一行行执行,遇到函数会进入

5、shift + F11 跳出:在当前函数跳出,回到函数开始的位置

6、继续

如后续有断点,运行到下一个断点

如后续没有断点,执行完整个程序

二、变量和常量

2.1 变量

变量是运行过程中可以改变的量,在C语言中定义变量的方式如下:

// 定义一个变量
int num = 10;

计算机看到int类型,会分配4个字节的存储区用来存储整型数据

1.2.1 变量的初始化

在定义变量的时候,给变量赋值,叫做变量的初始化

// 单个变量初始化
int num1 = 10;
int num2;
num2 = 20;

// 多个变量的初始化
int num3,num4,num5;
num3 = 30;
num4 = 40;
num5 = 50;

// 多个变量连续初始化
int num6,num7,num8;
num6 = num7 = num8 = 30;

1.2.2 大小端问题

小端表示法:低地址存放低有效位,在x86架构中,通常使用此表示法

大端表示法:高地址存放低有效位

案例:假设有一个变量int i = 1, 一般占用4字节,它的真值为1,用机器码表示为:

00 00 00 01

使用小端表示法,在内存中的布局为:

      01 00 00 00
地址:低	→	 高

使用大端表示法,在内存中的布局为:

      00 00 00 01
地址:低	→	 高

2.2 常量

常量是程序在运行期间不可发生改变的量,其定义方式如下

// 使用const关键字定义常量
const int num = 10;

常量 和 常量表达式 的区别

对比项 常量 常量表达式
代码 const int num = 10 #define N 5
能否用于指定数组长度 不行 可以
能否用于switch语句 不行 可以

常量表达式用于指定数组的长度

#define N 5
int nums[N]; // ok

常量表达式用于switch语句

int i = 0;

switch (i)
{
    case N:
        printf("Hall");
    default:
        break;
}

2.3 常量指针和指针常量

常量指针和指针常量是两个概念,容易混淆

const int *p; // 指针常量
cont * int p; // 常量指针

// 记忆法:倒过来念

他们的对比如下:

指针常量 常量指针
代码 const int *p; int * const p;
修改指针指向的对象的值 不允许 允许
修改指针指向的对象 允许 不允许

例子如下:

// 指针常量
const int* p1 = &num1; 
// *p1 = 20;	// Error
p1 = &num2; 
num1 = 20; 


// 常量指针
int * const p2 = &num2; 
*p2 = 30; 
// p2 = &num1; // Error
num2 =  30; 

三、预处理指令

3.1 什么是预处理指令

预处理指令是以#号开头的指令,如

// 包含头文件
#include <stdio.h>

// 宏定义变量
#define N 5

// 宏函数
#define Foo(x) 1 + x

在VS中如何生成预处理文件?

项目 - 属性 - C/C++ - 预处理器 - 预处理到文件 - 是

3.2 宏函数的使用注意事项(重要)

如下宏函数的例子,可见通过Foo(num)调用可以得到正确的结果31

#include <stdio.h>

// 宏函数
#define Foo(x) 1 + x + x * x 

int main(void) {
	int num = 5;
	int sum = Foo(num); // 调用宏函数
	printf("sum = %d\n", sum);	// 输出31
	return 0;
}

注意事项一:宏函数需要括号

问题:如以上宏函数调用方式修改,则会出问题

int main(void) {
	int num = 5;
	int sum = 3 * Foo(num); // 此处修改
	printf("sum = %d\n", sum);	// 输出33,并非想得到的3 * 31 = 93
	return 0;
}

原因:这是因为宏函数只是做简单的文本替换,原语句替换后为:

int sum = 3 * 1 + 5 + 5 * 5 = 31

解决方法:对宏函数加括号

#define Foo(x) (1 + x + x * x) // 宏函数实现加上括号

注意事项二:宏函数的参数也需要括号

问题:以上写法还是有问题,如果调用方式再次改变:

int sum = Foo(num + 1);
printf("sum = %d\n", sum); // 输出18,而非想得到的Foo(6) = 43

原因:原语句替换后为:

int sum = 1 + 5 + 1 + 5 + 1 * 5 + 1 = 18

解决方法:为每个变量都加上括号

#define Foo(x) (1 + (x) + (x) * (x)) // 变量加上括号

注意事项三:多次++引起的副作用

问题:警惕宏函数导致的多次副作用

#include <stdio.h>

#define Foo(x) (1 + (x) + (x) * (x))

int main(void) {
	int i = 1;
	int sum = Foo(++i);	// ++i 会导致什么?

	printf("sum = %d, i = %d\n", sum, i );
	return 0;
}

原因:输出结果为21,并非想得到的Foo(2) = 7,因为原语句替换后:

// 此处会先++完,然后再进行数学计算
int sum = 1 + (++i) + (++i) * (++i) = 1 + 4 + 4 * 4 = 21

解决方法:避免这样使用宏函数

注意事项四:定义完备的多语句宏函数

问题:以下代码,按道理应该不会输出,但实际情况还会输出“World\n”

#include <stdio.h>

#define Foo() printf("Hello\n");  \
printf("Wrold\n"); 

int main(void) {
	if(0)
		Foo();	
	
	return 0;
}

// 将输出"World\n"

原因:宏函数展开后

if(0)
    #define Foo() printf("Hello\n");  \		// 此段受到if限制,未执行
printf("Wrold\n"); 		// 此段未受到if限制,所以仍输出

解决方法:将宏函数改成do…while语句

#define Foo() do { printf("Hello\n");  \
printf("Wrold\n"); } while(0);

3.3 为什么要使用宏函数?

宏函数如此麻烦为什么还要用它呢?

效率高!

普通函数调用会有额外的开销,像调用函数,会保存寄存器的值,传参…..,像函数返回,会传返回值,恢复寄存器的值…,而这些操作宏函数都没有


C语言-Day01
http://gsproj.github.io/2022/12/02/04_C++/01_C语言/day01/
作者
GongSheng
发布于
2022年12月2日
许可协议