今天是大年初七,经过了几天的拜年,我又来开始更新我的C语言的系列了,一个Java程序员学C语言的历程,俗话说,学习一门新的语言,入门的书,比较重要,国内都是谭浩强,讲道理,真的就是垃圾,笔者今天要推荐的就是《C程序设计语言》这本书是由C语言之父:里奇写的,应该没有比这本更好的C语言的入门的书了吧!
学过其他的语言的大佬,还是没有学过其他的语言的小白,相信大家学习任何一门语言的第一个程序的都是helloworld
,那么我也带大家写一个helloworld
,在写这个程序之前,大家的环境要准备好,我用的clion
,当然有些人喜欢用vscode
,都是可以,萝卜青菜各有所爱嘛!这儿我啰嗦一句,在学C语言之前,希望大家先看完前置的课程《CMake一篇就够了》这样基本前置的环境都准备了,那就让我们手写第一个程序吧!具体的代码如下:
// 包含标准库的内容
#include
main() // 定义名为main的函数,它不接受参数值
{ //main函数的语句都被括在花括号中
printf("hello world \n"); //main函数调用库函数printf以显示字符串序列 \n 代表换行符
}
运行的结果如下:
可以看到我们第一个C语言的程序,就写出来了,我们需要了解下,这一大串的代码的意思了,首先就是第一句#include
,从java的程序员的角度,我们可以理解为就是导包,好嘛!但是有的人说了,我是小白,我不理解怎么办,往下看,可以看到我们调用了一个printf函数
,但是我们没有写这个函数,从哪儿来的呢?就是从stdio.h
中引入的。程序其中一个很大的功能就是帮我们解决重复的问题,例如我们这儿的打印语句,总不能我每次要打印一些东西的时候,我都要写一次这个函数吧!所以聪明的人类,将这个打印的然后封装起来了,就只要写一次,我们只需要在使用的时候引入的这个头文件就可以了,我们可以打开这个头文件看看里面有什么东西,具体的如下:
可以看到实现了这么多的函数,都是可以调用的,就是重复的功能封装成一个函数,这就是抽象的能力,也是我们程序员需要锻炼的能力之一。
我们继续往下看,main() {}
这一个整体,我们称之为函数,其中main
是叫函数名,这儿我们可以理解为主函数,java的程序员应该是很好理解的,这儿就不赘述,但是对于小白来说,不太好理解,那么我就来说说看,计算机是机器,它比较蠢,所以你必须要给它一个入口,不然它不知道从什么地方开始运行你的代码,这儿main() {}
,我们称之为主函数,就是计算机会这个函数开始运行。
最后就是看下,我们函数体中的内容了,printf("hello world \n");
就是一个打印的语句,调用的是标准库中的函数,这儿我就不多赘述了,后面等讲到这个标准库的时候,我们再来详细的讨论。
这儿还有一个需要我们注意的,就是结尾一个\n
是什么意思呢?是换行的意思,在每一种有些特殊的符号,我们需要转义,\
就是转义字符,常见的特殊的符合如下:
特殊符号 | 含义 |
---|---|
\n | 换行 |
\t | 制表符 |
\b | 回退符 |
" | 双引号 |
\ | 反斜杠 |
我们看看一个程序,使用公式
摄 氏 度 = ( 5 / 9 ) ( 华 氏 度 − 32 ) 摄氏度=(5/9)(华氏度-32) 摄氏度=(5/9)(华氏度−32)
打印对应华氏度与摄氏度的对照表。具体的代码的如下:
#include
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("%3d %6d\n", fahr, celsius);
fahr = fahr + step;
}
}
从这个程序上,我们接触一些新的概念:注释、声明、变量、算术表达式、循环、格式化输出。
注释
为了更好解释自己的写的代码,一般程序都会有注释的,但是好的程序,一线就看懂了,不用过多的注释的,注释写多了可能会误导别人,注释有单号注释,用//
表示,多行注释用/**/
表示,上面的程序//温度表的下限
就是注释。
声明变量
所有的变量必须先声明后使用,声明通常放在函数起始处,在任何可执行语句之前,声明用于说明变量的属性,它由一个类型名和一个变量表组成。常见的数据类型如下:
类型 | 含义 |
---|---|
char | 字符一个字节 |
short | 短整型 |
long | 长整形 |
double | 双精度浮点数 |
循环
while循环语句的执行方式:首先测试圆括号中的条件,如果条件为真,则执行循环体;然后再重新测试圆括号中条件,如果为真,则再执行循环体;当圆括号中的条件测试结果为假时,循环结束,并继续执行跟在while循环语句之后的下一条语句。
格式化输出
printf函数中的%d表示一个占位符,后面输出的变量就会按照这种格式去输出。常见的C语言中的格式化输出的如下:
格式 | 含义 |
---|---|
%d | 按照十进制整数打印 |
%6d | 按照十进制整数打印,至少6个字符宽 |
%f | 按照浮点数打印 |
%6f | 按照浮点数打印,至少6个字符宽 |
%.2f | 按照浮点数打印,小数点后面有两位小数 |
%6.2f | 按照浮点数打印,至少6个字符宽,小数点后有两位小数 |
%O | 八进制数 |
%x | 十六进制数 |
%c | 字符 |
%s | 字符串 |
%% | 百分号本身 |
讲完了一些的前置的知识,我们来看下这个程序的运行结果吧,具体的如下:
上面的程序有个比较严重的问题,由于我们使用了整型的运算,这样算出来的结果不是很正确,比如说我们0华氏度对应的摄氏度应该是-17.8,而不是-17。为了得到更加精确的结果,应该使用浮点算数运算。我们需要将程序进行适当的修改。具体的代码的如下:
#include
main() {
float fahr, celsius;
int lower, upper, step;
lower = 0;
upper = 300;
step = 20;
fahr = lower;
while (fahr <= upper) {
celsius = (5.0/ 9.0) * (fahr - 32) ;
printf("%3.0f %6.1f\n", fahr, celsius);
fahr = fahr + step;
}
}
运行的结果如下:
可以看到我们的计算的结果是正确的。我们这儿计算的结果比之前更加准确了。但是我这儿想引出一个很重要的计算的规则。
我们学的强类型的语言,所有我们要对类型很敏感,一个变量不单单有值,它还有对应的类型,一个变量只有在有值和类型才是一个整体。
上面的代码我们看了下,存在大量的变量的初始化和使用,那么我们能有什么办法,将对应的代码给简化掉,我们可以用我们for循环,将上面的代码给简化掉,具体的代码的如下:
#include
main() {
int fahr;
for (fahr = 0; fahr <= 300; fahr = fahr + 20) {
printf("%3d %6.1f\n", fahr, (5.0 / 9.0) * (fahr - 32));
}
}
运行的结果如下:
我们可以看到循环主要有三部分,主要是:for (fahr = 0; fahr <= 300; fahr = fahr + 20) {}
其中fahr = 0
是for循环的初始化的部分,仅在进入循环前执行一次,第二部分fahr <= 300
是控制循环的测试或条件的部分。循环控制将对该条件求值,如果结果为真,则执行循环体。此后执行第三部分fahr = fahr + 20
,将循环变量fahr增加一个步长,并再次对条件求值。如果计算得到的条件为假,循环将终止。
这个时候可能有人会问,我们什么时候用while,什么时候用for呢?我的答案就是你开心就好。当然也有个标准:for比较适合初始化和增加步长的单条语句并且逻辑相关的情形,因为它将循环控制语句集中放在一起。
我们再来看看上面的程序,我们可以看到程序中有300,20,0,这些数,这些数在我们语言中称为魔法数。这不是一个好的东西,万一有一天我们要改温度的上限,从300改成400,那么我就需要修改整个程序中的300,那么很容易改忘,改漏。那么有没有什么好的方式对这些魔法数进行集中管理呢?很明确的告诉你,有,就是符号常量。我们再改一个版本,具体的如下:
#include
#define LOWER 0
#define UPPER 300
#define STEP 20
main() {
int fahr;
for (fahr = LOWER; fahr <= UPPER; fahr = fahr + STEP) {
printf("%3d %6.1f\n", fahr, (5.0 / 9.0) * (fahr - 32));
}
}
运行的结果如下:
#define
指令可以把符号名(或称为符号常量)定义为一个特定的字符串:#define 名字 替换的文本
替换的文本可以是任何字符序列,而不仅限于数字。
前面我们学了输出,今天我们来学下输入,老规矩,先看代码,具体的代码如下:
#include
main() {
int c;
while ((c = getchar()) != EOF) {
putchar(c);
}
}
其中有getchar()
函数是从键盘输入的文本流中读入下一个输入字符,并将其作为结果值返回。
putchar()
读取对应的变量中的字符,打印在屏幕上。
EOF
文件的结束符。
所以上面的程序可以理解为:我们输入什么,就会打印出来什么,如果输入结束符,就会结束对应的循环,也就是ctrl+D。
有了前面的基础的知识,我们可以写一个程序用来计算我们输入的字符数,具体的代码如下:
#include
main() {
double nc;
for (nc = 0; getchar() != EOF; ++nc);
printf("%.0f\n", nc);
}
运行的结果如下:
我们输入了十个字符,其中还有一个回车,所以这儿打印出来的是11个,没有问题。
有了前面的基础知识,我们可以写一个程序用来计算我们输入的字符的行数,具体的代码如下:
#include
main() {
double c, n1;
n1 = 0.0;
while ((c = getchar()) != EOF) {
if (c == '\n')
++n1;
}
printf("%0.f \n", n1);
}
这儿我们学到了一个新的东西,就是if判断,括号中间的是条件,如果条件满足的情况下,就会执行++n1
操作,如果条件不满足的情况下,就不会执行++n1
操作。其中++n1 = n1=n1 + 1
但是这个++ 或者–有前置的操作,还有就是后置的操作,这个等我写变量的时候,再和大家讲清楚。运行的结果如下:
可以看我们的程序的输出的结果是对的。
经过前面的学习,我们写出如下的代码:
#include
#define IN 1
#define OUT 0
main(){
int c, n1, nw, nc, state;
state = OUT;
n1 = nw = nc = 0;
while ((c = getchar()) != EOF) {
++nc;
if(c == '\n')
n1 ++;
if(c == ' ' || c == '\n' || c == '\t')
state = OUT;
else if(state == OUT){
state = IN;
++nw;
}
}
printf("%d %d %d \n", n1, nw, nc);
}
看看上面的代码,我们又学到新的东西了,一个是 ||
这个符号,是或的意思,就是所有的条件有一个成立的话,就算整个表达式是结果是真的,还有一个就是&&
这个是且的意思,如果所有的条件都满足的话,才会成立,其他的情况的都是不成立的。然后就是一种新的格式:
if(表达式)
语句1;
else
语句2
上面的格式表达式成立的时候,就会执行语句1,如果表达式不成立的话,就会执行语句2。
还有一种格式,具体的如下:
if(表达式1)
语句1
else if(表达式2)
语句2
else
语句3
上面的格式表达式1成立的话,就会执行语句1,表达式2成立的话,就会执行语句2,如果都不成立的话,就会执行语句3
我们先看一个程序,具体的代码如下:
#include
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);
}
上面的程序,我们接触到了一个新的知识,就是数组,数组我们需要知道就是下标,数组的长度,数组的下标不能超过数组的长度。如果超过了,就会下标越界。数组我们可以理解为一连串的内存的空间,然后存同一类型的值,最后通过下标访问对应位置的值。
上面的程序就是进行计数的,我们输入的值,进行对应的计数,数组的0-9的位置分别表示对应的数字0-9的个数,运行的结果如下:
先看下面的程序:
#include
// 函数定义
int power(int m, int n);
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 = 0; i <= n; ++i)
p = p * base;
return p;
}
从上面的程序我们可以得到函数的一般的类型,具体的如下:
返回值类型 函数名(0个或多个参数声明)
{
声明部分
语句序列
}
注意:
上面的程序的运行结果如下:
C语言中所有的参数都是通过值传递的,也就是传递的参数是值,也就是修改参数的值,不会影响原来的参数的值,但是很多人需要修改参数的值,也要影响原始参数的值。这个时候就需要传入的是指针。
计算机中所有的值都以二进制存到某个地址上,而指针就是用来存地址,既然地址给你了,就能找到这个变量,那么你的修改就会影响这个个变量的值。
我们先看下面的程序,具体的如下:
#include
#define MAXLINE 1000
int getLine(char line[], int maxline);
void copy(char to[], char from[]);
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 = 0;
while ((to[i] = from[i]) != '\0')
++i;
}
上面的程序,就是复制你输入的字符串,然后打印出来,这儿我们只要主要到一点的内容,就是在C语言中字符串一定要以\0
结尾,不然打印的内容有问题的。
我们再来看一个程序,具体的如下:
#include
#define MAXLINE 1000
int max;
char line[MAXLINE];
char longest[MAXLINE];
int getLine(void);
void copy(void);
main() {
int len;
max = 0;
while ((len = getLine()) > 0)
if (len > max) {
max = len;
copy();
}
if (max > 0)
printf("%s", longest);
return 0;
}
int getLine(void) {
int c, i;
for (i = 0; i < MAXLINE - 1 && (c = getchar()) != EOF && c != '\n'; ++i)
line[i] = c;
if (c == '\n') {
line[i] = c;
++i;
}
line[i] = '\0';
return i;
}
void copy(void ){
int i = 0;
while ((longest[i] = line[i]) != '\0')
++i;
}
上面的程序是原来的程序的改编版,主要是为了介绍局部变量和外部变量,局部变量的生命周期是随着函数的消亡而消亡,而外部变量是随着程序的消亡而消亡的,所以局部变量是一个个体,它的修改不会影响别的,但是外部变量,只要函数修改的这个外部变量,其他函数也是可见的。
本篇博客主要简单的介绍的C语言的全览,后面我打算按照书上的内容一点点的介绍对应的内容,最后祝大家开工大吉。