一站式C学习笔记

1.定义一个变量,就是分配一块存储空间并给它命名;给一个变量赋值,就是把一个值保存到这块存储空间中。变量的定义和赋值也可以一步完成,这称为变量的初始化(Initialization)

2.澄清一下函数声明、函数定义、函数原型(Prototype)这几个概念。比如void threeline(void)这一行,声明了一个函数的名字、参数类型和个数、返回值类型,这称为函数原型。在代码中可以单独写一个函数原型,后面加;号结束,而不写函数体,例如:

void threeline(void);

这种写法只能叫函数声明而不能叫函数定义,只有带函数体的声明才叫定义。上一章讲过,只有分配存储空间的变量声明才叫变量定义,其实函数也是一样,编译器只有见到函数定义才会生成指令,而指令在程序运行时当然也要占存储空间。那么没有函数体的函数声明有什么用呢?它为编译器提供了有用的信息,编译器在翻译代码的过程中,只有见到函数原型(不管带不带函数体)之后才知道这个函数的名字、参数类型和返回值,这样碰到函数调用时才知道怎么生成相应的指令,所以函数原型必须出现在函数调用之前,这也是遵循“先声明后使用”的原则。

3.局部变量可以用类型相符的任意表达式来初始化,而全局变量只能用常量表达式(Constant Expression)初始化例如,全局变量pi这样初始化是合法的:

double pi = 3.14 + 0.0016;

但这样初始化是不合法的:

double pi = acos(-1.0);
然而局部变量这样初始化却是可以的。程序开始运行时要用适当的值来初始化全局变量,所以初始值必须保存在编译生成的可执行文件中,因此初始值在编译时就要计算出来,然而上面第二种Initializer的值必须在程序运行时调用acos函数才能得到,所以不能用来初始化全局变量。请注意区分编译时和运行时这两个概念。为了简化编译器的实现,C语言从语法上规定全局变量只能用常量表达式来初始化,因此下面这种全局变量初始化是不合法的:
int minute = 360;
int hour = minute / 60;

虽然在编译时计算出hour的初始值是可能的,但是minute / 60不是常量表达式,不符合语法规定,所以编译器不必想办法去算这个初始值。

如果全局变量在定义时不初始化则初始值是0,如果局部变量在定义时不初始化则初始值是不确定的。所以,局部变量在使用之前一定要先赋值,如果基于一个不确定的值做后续计算肯定会引入Bug。

4.局部变量的存储空间在每次函数调用时分配,在函数返回时释放

 

5.例 6.1. 求1-100的素数

#include <stdio.h>
int is_prime(int n)
{
	int i;
	for (i = 2; i < n; i++)
		if (n % i == 0)
			break;
	if (i == n)
		return 1;
	else
		return 0;
}
int main(void)
{
	int i;
	for (i = 1; i <= 100; i++) {
		if (!is_prime(i))
			continue;
		printf("%d\n", i);
	}
	return 0;
}

6.indent工具可以把代码格式化成某种风格

例子 石头剪刀布
$ indent -kr -i8 main.c 
$ cat main.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void)
{
	char gesture[3][10] = { "scissor", "stone", "cloth" };
	int man, computer, result, ret;
	srand(time(NULL));
	while (1) {
		computer = rand() % 3;
		printf
		    ("\nInput your gesture (0-scissor 1-stone 2-cloth):\n");
		ret = scanf("%d", &man);
		if (ret != 1 || man < 0 || man > 2) {
			printf("Invalid input!\n");
			return 1;
		}
		printf("Your gesture: %s\tComputer's gesture: %s\n",
		       gesture[man], gesture[computer]);
		result = (man - computer + 4) % 3 - 1;
		if (result > 0)
			printf("You win!\n");
		else if (result == 0)
			printf("Draw!\n");
		else
			printf("You lose!\n");
	}
	return 0;
}
-kr选项表示K&R风格,-i8表示缩进8个空格的长度。如果没有指定-nut选项,则每8个缩进空格会自动用一个Tab代替。注意indent命令会直接修改原文件,而不是打印到屏幕上或者输出到另一个文件,这一点和很多UNIX命令不同。可以看出,-kr -i8两个选项格式化出来的代码已经很符合本章介绍的代码风格了,添加了必要的缩进和空白,较长的代码行也会自动折行。美中不足的是没有添加适当的空行,因为indent工具也不知道哪几行代码在逻辑上是一组的,空行还是要自己动手添,当然原有的空行肯定不会被indent删去的。
7.gdb调试
程序中除了一目了然的Bug之外都需要一定的调试手段来分析到底错在哪。到目前为止我们的调试手段只有一种:根据程序执行时的出错现象假设错误原因,然后在代码中适当的位置插入printf,执行程序并分析打印结果,如果结果和预期的一样,就基本上证明了自己假设的错误原因,就可以动手修正Bug了,如果结果和预期的不一样,就根据结果做进一步的假设和分析。本章我们介绍一种很强大的调试工具gdb,可以完全操控程序的运行,使得程序就像你手里的玩具一样,叫它走就走,叫它停就停,并且随时可以查看程序中所有的内部状态,比如各变量的值、传给函数的参数、当前执行的代码行等。掌握了gdb的用法之后,调试手段就更加丰富了。但要注意,即使调试手段丰富了,调试的基本思想仍然是“分析现象->假设错误原因->产生新的现象去验证假设” 这样一个循环,根据现象如何假设错误原因,以及如何设计新的现象去验证假设,这都需要非常严密的分析和思考,如果因为手里有了强大的工具就滥用而忽略了分析过程,往往会治标不治本地修正Bug,导致一个错误现象消失了但Bug仍然存在,甚至是把程序越改越错。本章通过初学者易犯的几个错误实例来讲解如何使用gdb调试程序,在每个实例后面总结一部分常用的gdb命令。

表 10.1. gdb基本命令1

命令描述
backtrace(或bt)查看各级函数调用及参数
finish连续运行到当前函数返回为止,然后停下来等待命令
frame(或f) 帧编号选择栈帧
info(或i) locals查看当前栈帧局部变量的值
list(或l)列出源代码,接着上次的位置往下列,每次列10行
list 行号列出从第几行开始的源代码
list 函数名列出某个函数的源代码
next(或n)执行下一行语句
print(或p)打印表达式的值,通过表达式可以修改变量的值或者调用函数
quit(或q)退出gdb调试环境
set var修改变量的值
start开始执行程序,停在main函数第一行语句前面等待命令
step(或s)执行下一行语句,如果有函数调用则进入到函数中

表 10.2. gdb基本命令2

命令描述
break(或b) 行号在某一行设置断点
break 函数名在某个函数开头设置断点
break ... if ...设置条件断点
continue(或c)从当前位置开始连续运行程序
delete breakpoints 断点号删除断点
display 变量名跟踪查看某个变量,每次停下来都显示它的值
disable breakpoints 断点号禁用断点
enable 断点号启用断点
info(或i) breakpoints查看当前设置了哪些断点
run(或r)从头开始连续运行程序
undisplay 跟踪显示号取消跟踪显示

表 10.3. gdb基本命令3

命令描述
watch设置观察点
info(或i) watchpoints查看当前设置了哪些观察点
x从某个位置开始打印存储单元的内容,全部当成字节来看,而不区分哪个字节属于哪个变量

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值