【C语言学习笔记】《C程序设计语言》 第1章(课前准备、学习框架、初步了解C语言)

Warning:
为了避免非零基础人群感到身体不适、头晕恶心、易怒及粗口,请不要查看以下内容。

经过长时间的精挑细选,并经过反复斟酌,最终决定采用 《C程序设计语言》这部经典的教材进行C语言学习,本专辑仅为个人学习心得体验,便于理清思路、理解深刻,希望能对自己和其它读者有所启发和帮助。废话不多说,直接进入学习。(本章通过覆盖尽可能多的基本知识使读者大体熟悉C语言 并不详细讲解)

第1章 导言

1.1 入门

学习一门新程序设计语言的唯一途径就是使用它编写程序。对于所有的初学者来说,编写的第一个程序几乎都是相同,即:在屏幕上打印出 ”Hello,World“。

#include <stdio.h>

main()
{
	printf("Hello, World\n");
	return 0;
}

这样,经过编译 运行,便在屏幕上打印出 Hello,World 字样,并自动换行。

#include <stdio.h> 用于包含头文件stdio.h
main() 是程序运行的入口,首先执行该函数。
printf() 则负责在屏幕上打印出字符。
return 0 代表程序执行完成,成功返回。
\ 为转义字符 \n表示换行符

花括号表示函数包含的命令内容,括号内表示函数的参数,各代码之间以分号隔开。

1.2 变量与算术表达式

首先,我们看一下这个程序:

公式: ℃ = (5/9) (℉-32)
0 -17
20 -6
40 4
60 15
80 26
100 37
120 48
140 60
160 71
180 82
200 93
220 104
240 115
260 126
280 137
300 148

程序代码如下:

#include <stdio.h>

/* 当fahr = 0, 20, ..., 300时
   分别打印华氏温度和摄氏温度对照表 */

int main()
{
	int fahr, celsius;
	int lower, upper, step;

	lower = 0;
	upper = 300;
	step = 20;

	fahr = lower;
	while (fahr <= upper)
	{
		celsius = 5 * (fahr - 32) / 9;
		printf("%d\t%d\n", fahr, celsius);
		fahr = fahr + step;

	}

	return 0;
}

其中两行:

/* 当fahr = 0, 20, …, 300时
分别打印华氏温度和摄氏温度对照表 */

表示注释,简单解释程序的用途,包含在/* */之间的字符将被编译器忽视。注释可使程序更易理解。

C语言种的所有变量先声明后使用,声明用于说明变量的属性,由一个类型名加一个变量表组成。如:

int fahr, celsius;
int lower, upper, step;

int表示整数型 float表示浮点数(小数),各种类型取值范围取决于机器。int通常为16位,取值范围在-32768~+32767之间,有的机器也为32位。float通常为32位,至少6位有效数字,取值范围在10 -38~1038

C语言除int和float类型外还提供以下类型:
char    字符 一个字节
short   短整型
long    长整型
double  双精度浮点型

这些类型的大小取决于机器,另外还存在这些基本数据类型的数组、结构、联合、指向这些类型的 指针 、 返回这些类型值的 函数。

1.3 for语句

#include <stdio.h>

int main()
{
	int fahr;
	
	for (fahr = 0; fahr <= 300; fahr = fahr + 20)
	{
		printf("%3d %6.1f\n", fahr, (5.0 / 9.0) * (fahr - 32));
	}
	return 0;
}

for是一种循环语句,它是对while的推广。for更加直观,圆括号中共包含3个部分,各部分之间用分号隔开。
第一部分 fahr = 0 为初始化部分,仅在进入循环前执行一次。
第二部分 fahr <= 300 是控制循环的测试或条件部分,如果结果为 true 则执行循环体。
第三部分 fahr = fahr + 20 将fahr增加一个步长 执行完循环体后执行本句

1.4 符号常量

为了处理 300、20 这样的“幻数”,我们使用#define指令把符号名(符号常量)定义为一个字符串。

#define 名字 替换文本

在该定义后出现的#define中定义的名字都将被替换文本进行替换。注:#define 末尾无数字

1.5 字符输入/输出

标准库提供了一次独写一个字符的函数,其中最简单的是getchar和putchar两个函数。
每次调用时,getchar函数从文本流中读入下一个输入字符,并将其作为结果值返回。

1.5.1 文件复制

利用getchar和putchar两个基本函数,我们可以实现数量惊人的代码。
最简单的例子就是把输入一次一个字符的复制到输出,基本思想如下:

读一个字符
while(该字符不是文件结束指示符)
输出刚读入的字符
读入下一个字符

将上述思想转换为C语言代码:

#include <stdio.h>

int main()
{
	int c;

	c = getchar();
	while (c != EOF)
	{
		putchar(c);
		c = getchar();
	}
	return 0;
}

关系运算符 != 为不等于
为了解决文件中有效数据与输入结束符的问题,C语言采取的解决办法为:在没有输入时,getchar函数返回一个特殊值,这个特殊值与任何实际字符都不同。这个值为 EOF(end of file,文件结束)。为了存贮这个值,我们需要它足以存放getchar返回的任何值,所以我们用int类型,而没有用char类型。

EOF定义在头文件<stdio.h>中,是一个整数型。

1.5.2 字符计数

下列程序用于字符计数,它在原理上与以上程序类似:

#include <stdio.h>

int main()
{
	long nc;

	nc = 0;
	while (getchar() != EOF)
		++nc;
	printf("%ld\n", nc);
	return 0;
}

其中,++nc 相当于 nc = nc + 1,至于nc++ 和 ++nc的区别,我们在后面的章节讨论。
下面用for语句展示另一种写法:

#include <stdio.h>;

int main()
{
	double nc;

	for (nc = 0; getchar() != EOF; ++nc)
		;
	printf("%0.f\n", nc);
	return;
}

for循环的循环体是空语句,用分号表示。所有工作都在测试(条件)部分与增加步长部分完成了。

1.5.3 行计数

下面的程序统计输入的行数,即统计换行符的个数。

#include <stdio.h>

int main()
{
    int c, nl;

	nl = 0;
	while ((c = getchar()) != EOF)
	{
		if (c == '\n')
			++nl;
	}
	printf("%d\n", nl);
	return 0;
}

本例子没啥好说的。注意 “==” 是“等于”关系运算符 而“=”是“赋值”运算符

1.5.4 单词计数

代码如下:

#include <stdio.h>

#define IN  1
#define OUT 2

int main()
{
    int c, nl, nw, nc, state;

    state = OUT;
    nl = nw = nc = 0;
    while ((c = getchar ()) != EOF)
    {
        ++nc;
        if (c == '\n')
            ++nl;
        if (c == ' ' || c == '\n' || c == "\t")
            state = OUT;
        else if (state == OUT)
        {
            state = IN;
            ++nw;
        }
    }
    printf("%d %d %d\n",nl ,nw, nc);
    return 0;
}

在这里使用了符号常量 IN OUT 而不是 0 1 可以使程序更易读。
至于if语句和逻辑运算符,我们在后面的章节会具体讨论。

1.6 数组

在这部分,我们用数组相关知识编写一个可以统计各个数字、空白符以及其它所有字符出现的次数。
下面是该程序的一种版本:

#include <stdio.h>

int main()
{
	int c, i, nwhite, nother;
	int ndigit[10];

	nwhite = nother = 0;
	for (i = 0; i < 10; ++i)
		ndigit[i] = 0;

	while ((c = getchar()) != EOF)
	{
		if (c >= '0' && c <= '9')
			++ndigit[c - '0'];
		else if (c == ' ' || c == '\n' || c == '\t')
			++nwhite;
		else 
			++nother;
	}

	printf("digits = ");
	for (i = 0; i < 10; ++i)
	{
		printf(" %d", ndigit[i]);
	}
	printf(", white space = %d, other = %d\n", nwhite, nother);

	return 0;
}

if (c >= ‘0’ && c <= ‘9’) 用于判断是否为数字 若为数字 该数字对应的值为 c - ‘0’
‘0’ ‘9’表示数字对应的为ASCII码 只有’0’ ‘1’ … ‘9’ 这样连续递增的值才可用上面的做法

1.7 函数

函数为计算的封装提供了一种简便的方法,此后使用函数时不需要考虑它是如何实现的。使用设计正确的函数,程序员无需考虑功能是如何实现的,而只需知道它具有哪些功能就够了。在C语言中可以简单、方便、高效地使用函数。我们经常会看到在定义后仅使用了一次的函数,这样做可以使代码更清晰。

到目前为止,我们使用的 printf getchar putchar 等函数都是函数库中提供的函数。现在,我们开始动手自己写一个函数。C语言没有提供求幂的函数。下面是power(m, n)函数的定义及调用。

#include <stdio.h>

int power(int m, int n);

int main()
{
	int i;

	for (i = 0; i < 10; ++i)
	{
		printf("%d %d %d\n", i, power(2, i), power(-3, i));
	}
	return 0;
}

int power(int base, int n)
{
	int i, p;

	p = 1;
	for (i = 1; i <= n; ++i)
	{
		p = p * base;
	}
	
	return p;
}

int power(int m, int n); 为函数的声明
power()为函数的调用
最下面的则为函数的定义

1.8 参数——传值调用

C语言中,所有函数参数都是“通过值”传递的。也就是说,传递给被调用的函数的参数值存放在临时变量中,而不是存放在原来的变量中。在C语言中,被调用的函数不能直接修改主调函数中变量的值,而只能修改其私有的临时副本的值。
传值调用的利大于弊。在被调用的函数中,参数可以看作是便于初始化的局部变量,因此额外使用的变量更少,这样程序可以更紧凑更简洁。
必要时,也可以让函数能够修改主调函数中的变量。这种情况下,调用者需要向被调用函数提供待设置值的变量的地址(从技术角度看,地址就是指向变量的指针),而被调用函数需要将对应的参数声明为指针类型,并通过它间接访问变量。
如果是数组参数,情况有例外。当把数组名作为参数时,传递给函数的值是数组起始元素的位置或地址,它并不复制本身。在被调用函数中,可以通过数组下标访问或修改数组元素的值。以后具体讨论。

1.9 字符数组

字符数组是C语言最常用的数组类型。下面我们看个例子,该程序可以读入文本,输出最长的文本行。
大体框架思路:

while(还有未处理的行)
if(该行比已处理的最长行还要长)
                  保存该行
                  保存该行的长度
打印最长的行

我们将编写一个函数 getline() 它读取输入的下一行

#include <stdio.h>
#define MAXLINE 1000

int getline(char line[], int maxline);
void copy(char to[], char from[]);


int main()
{
	int len;   /* 当前行长度 */
	int max;   /* 目前为止发现的最长行的长度 */
	char line[MAXLINE]; /* 当前的输入行 */
	char longest[MAXLINE]; /* 用于保存最长的行 */

	max = 0;
	while ((len = getline(line, MAXLINE)) > 0)
	{
		if (len > max)
		{
			max = len;
			copy(longest, line);
		}
		if (max > 0)
		{
			printf("%s", longest);
		}
	}
	return 0;
}

int getline(char s[], int lim)
{
	int c, i;

	for (i = 0; i < lim - 1 && (c = getchar()) != EOF && c != '\n'; i++)
		s[i] = c;
	if (c == '\n')
	{
		s[i] = c;
		++i;
	}
	s[i] = '\0';
	return i;
}

void copy(char to[], char from[])
{
	int i;

	i = 0;
	while ((to[i] = from[i]) != '\0')
		++i;
}

字符数组以 ‘\0’ 为结束符。具体后面再讲。目前仅需知道有这种类型就行。

1.10 外部变量与作用域

main函数中的变量是main函数的私有变量或局部变量。由于它们是在main函数中声明的,因此其它函数不能直接访问它们。其它函数中的变量同样如此。每个函数中的局部变量只在函数被调用时存在,在函数执行完毕退出时消失。这也是其它语言通常把这类变量称为“自动变量”的原因,今后我们用“自动变量”代替“局部变量”。(后面将讨论 static存储类,这种类型的局部变量在多次函数调用之间保持值不变)
由于自动变量只在函数调用期间存在,因此,在函数的两次调用之间,自动变量不保留前次调用时的赋值,且在每次进入函数时都要显示为其赋值。如果自动变量没有赋值,则其中存放的都是无效值。
除自动变量外,还可以定义位于所有函数外部的变量,也就是说,在所有函数中都可以通过变量名访问这种类型的变量。由于外部变量可以在全局范围内访问,因此,函数间可以通过外部变量交换数据,而不必使用参数表。再者,外部变量在程序执行期间一直存在,而不是在函数调用时产生、在函数执行完毕时消失。即使在对外部变量赋值的函数返回后,这些变量仍然保持原来的值不变。
外部变量必须定义在所有函数之外,且只能定义一次,定义后编译程序为它分配存储单元。在每个需要访问外部变量的函数中,必须声明相应的外部变量,此时说明其类型。声明时可以用extern语句显式声明,也可以通过上下文隐式声明。具体的我们在后面会讨论。
顺便提醒一下,现在越来越多的人把用到的所有东西都作为外部变量使用,因为似乎这样可以简化数据的通信,且在需要时总可以访问这些变量。但是,即使在不使用外部变量的时候,它们也是存在的。过分依赖外部变量会导致一些风险,因为它会使程序中的数据关系变得模糊不清,外部变量还可能被不经意间修改,而程序的修改又变得十分困难。

总结

到目前为止,我们已经对C语言的传统核心部分进行了介绍。借助于这些少量的语言元素,我们已经能够编写出相当规模的有用的程序。有了上面的框架,下面我们会逐个进行学习。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值