--渚_花(讨论) 2023年10月5日 (四) 22:08 (CST)
#include <stdio.h> #include <stdlib.h> int main(){ printf("Hello world!\n"); getchar(); return 0; }
上面是C语言的hello world代码。如果你是初学者的话,作为学习第一门编程语言的仪式,先尝试一下执行以下上面的hello world代码吧。具体网上有大量教程。
初学者学习一门任何一门新语言的第一个程序,都是在命令行上打印hello world,这是一个传统。因此也有下面这个编程笑话
“ | 某程序员对书法十分感兴趣,退休后决定在这方面有所建树。于是花重金购买了上等的文房四宝。一日,饭后突生雅兴,一番磨墨拟纸,并点上了上好的檀香,颇有王羲之风范,又具颜真卿气势,定神片刻,泼墨挥毫,郑重地写下一行字:hello world | ” |
——https://zhuanlan.zhihu.com/p/603433946 |
推荐初学者使用VSCode或者Codeblocks电脑的这两个软件。另外,如果是使用VSCode的话。推荐使用Code Runner插件,这个插件可以省去大量的配置工作
如果报错了怎么办?因为上面的代码太基础了,所以会有很多初学者遇到和你一样的错误。解决方法是,把错误信息复制下来,百度
晕字怎么办 | ||
---|---|---|
|
本教程将尝试以用一些与其他教程不同的方式,比如将常量运算和变量运算分离等,解决大量新人难以理解的问题,比如很多新人到了指针之后就完全学不懂的问题
本教程的示例代码较少,几乎没有习题和作业
为什么学习编程语言都要以hello world入门呢?这是因为每门编程语言的hello world程序,都“麻雀虽小五脏俱全”,里面携带着这门编程语言大量的语法信息
这一章开始会出现大量的专有名词。其中很多第一次出现的专有名词后面会详细解释,但有一些名词并不会给出解释,需要读者自己通过语境进行理解。给出大量专有名词一是为了给读者一种“C语言是一个好大的开放世界”的探索的感觉,二是为了方便读者遇到问题时进行表述,期望解决“我遇到了某个问题不知道怎么描述”的现象。这里很多内容只需要熟悉,而不需要完全弄懂
为什么是第零章?这是因为程序员计数都是从0开始计数的。比如C语言的数组,C++的vector
,Python的列表中最开始的元素的下标是0而不是1,尽管lua是个例外
“ |
きみ2に |
” |
——Patricia,花开物语印象曲 |
#include <stdio.h> // 每一行//后的内容是注释,注释不会对代码执行结果产生影响 #include <stdlib.h> // 上面一行引用了一个名字叫做`stdio.h'的头文件,这一行引用了一个名字叫做`stdlib.h'的头文件 int main(){ // 每一行两个斜线后面的内容表示【注释】,注释并不会被识别为代码内容 printf("Hello world!\n"); getchar(); // 在一些设备上如果不加上这一行代码,可能会出现类似于闪退的情况 return 0; }
我们一行一行开始讲。前两行是功能为引用头文件的编译预处理指令,其中#include
表示引用,<>
内是被引用的头文件名字。前两行分别引用了名字叫做stdio.h
和stdlib.h
的头文件
#include
语句所在行
<>
把头文件名字框起来。如果引用程序员自己写的头文件,则使用""
stdio
当做是studio
。实际上,std
是standard
的缩写,i
表示input
,o
表示output
。该头文件的功能是给出标准输入输出的函数声明(这里涉及到链接)。stdlib
中的lib
是library
,这里并不会是图书馆,而是库的意思然后是3-7行。这里实现了一个名字叫main()
的函数。int
的意思是整形(一种数据类型,简称类型),写在函数名字前面表示这个函数的返回值的类型是int
类型
int
是integer
的缩写,这个数据类型通常用来表示和存储整数int
类型,更常用的称呼是int
类型main()
函数是一个特殊的函数,被称为入口函数。之所以main()
函数被叫做入口函数,是因为编译后的可执行文件会从调用main()
函数开始
int main(void)
,这个具体在函数声明时会讲到,不推荐这样写int main(int argc, char **argv)
(第二个形式参数(或简称形参)char **argv
也可以写成char argv[]
,这两种写法都是正确的,这里涉及到命令行参数的问题,后面的内容会详细讲到main()
,这种写法省略了前面的int
。早期的C语言是可以省略int
的,但现在不推荐这样写{
写到main()
函数的下面,这种写法是正确的。这种写法涉及到代码风格问题,下面会详细讲到main()
函数后面由左大括号和右大括号框柱的内容叫代码块,代码块中有多条语句,每一条语句后面都要加一个分号。如果调用了main()
函数,代码块中的语句会从上到下从左到右逐条执行
可以看到,main( )
代码块内部有缩进,这个会在后面代码风格部分讲到
#include <stdio.h> #include <stdlib.h> int main(){ printf("Hello world!\n"); getchar(); return 0; }
现在简单介绍函数调用。第四行调用了一个叫做printf()
的函数,该函数可用于打印字符串
puts()
实现打印字符串功能。它与printf()
函数有区别,但在此代码中可以互相替换printf()
中的f
是format的缩写,意为按照指定格式打印
函数(function)调用的语法是function_name(argument1, argument2, ...)
,函数名字后面需要跟着一个括号,括号中按顺序填充函数参数(argument)。如果函数没有参数,可以将括号内部置空,但不允许省略括号
printf("Hello world!\n"); getchar();
上面两行分别调用了两个函数,第一个是printf()
。这里它只有一个参数,为字符串常量"Hello world\n"
。第二个函数getchar()
没有参数
getchar()
换成system("pause")
。这是为了让程序阻塞在这里,以防止程序窗口一执行完printf()
语句就自动关闭,就像闪退一样,这样就会看不到打印效果。后面的代码示例会省略getchar()
getchar()
函数的本来用途是,从'标准输入流'stdin
中读取一个字符并返回。它会等待用户通过键盘键入字符,待用户输入回车后,再从缓冲区中读取字符system()
函数的本来用途是,调用系统命令或shell命令,其唯一的一个字符串常量参数是将要被调用的命令
pause
系统命令会等待用户在键盘键入任意键
Press Any Key to Continue
这时软件用户只需要在键盘上键入任意键即可继续。然后很多用户打电话询问公司Any Key
在键盘的哪个位置。原来很多用户认为这里需要按下一个键盘上一个名字叫做Any Key
的键,而用户在键盘上找不到这个键。所以后来很多提示语都变成了Press Enter to Continue
,而实际上用户键入任何键都可以代码的倒数第二行return 0;
的意思是,main()
函数的返回值是0
,或者说,main()
函数返回0
main()
函数的返回值具有特殊的意义,它表示程序的返回值。通常如果一个程序的返回值为0
,表示程序正常运行,用其他值(比如-1
)表示程序异常终止
echo (?$)
来查看最后一个运行的程序的返回值关于注释:C语言的注释有两种,分别是//这种注释可以注释掉//后面的内容
和/* 这种注释可以跨行 */
两种。注释内容不会被识别为代码内容,也不会被执行,可用于便于未来的程序员包括未来的自己阅读代码
//
,以将该行代码内容变为注释,这样被注释掉的代码不会被执行,以对比代码执行结果较未被注释掉前的区别,以达到调试的效果#include <stdio.h> #include <stdlib.h> int main(){ // 我是一条注释 printf("1\n"); /* printf()函数 printf("2\n"); * 可以通过这种方式 printf("3\n"); * 打印数字 printf("4\n"); */ printf("5\n"); printf("6\n"); return 0; }
下面是上面代码的执行结果,请注意数字2 3 4不会被打印。至于为什么不会被打印,留作习题
1 5 6
不同的代码规范与代码风格略有区别,但是有一点是确定的,如果要参与别人的代码项目,必须按照对方的代码风格贡献代码,除非对方的代码风格本就特别混乱。很多大厂会指定程序员的代码风格,以
这一节的所有示例代码的目的都是为了讲解代码风格,所以不要把注意点放在这些代码的实际功能和执行效果上。这里的部分示例代码是C++代码。下面部分内容可能涉及到没学到的内容,暂时不要求理解
事实上,编译器在编译的时候,会忽略掉换行空格与tab等空白字符。所以你写成这样也可以,尽管这样会很不易读
#include <stdio.h> #include <stdlib.h> int main(){printf("Hello world!\n"); getchar(); return 0;}
教程最开始的hello world示例代码是右派写法,即把左大括号{
放到每一行右面。对应的,下面是左派写法,即把左大括号放到第二行的开头。一些代码规范会要求在一些情况使用左派,一些情况使用右派
#include <stdio.h> #include <stdlib.h> int main() { // 左派写法 printf("Hello world!\n"); getchar(); return 0; }
需要注意的是,右大括号,和左派写法的左大括号,需要单独占一行。不推荐下面的写法
#include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) { if (argc == 1) // 左大括号要单独占一行 { // 推荐这样的左大括号用法,单独占一行 printf("There is no argument\n"); // 这一行并没有和上一行的左大括号写在同一行 return 0; } printf("The number of arguments is %d\n", argc-1); // 不推荐上一行这样写,推荐将printf()函数另起一行 return 0; }
一行最好不要写太长,如果写太长的话,推荐分行写。如果if ()
语句或for ()
语句一行需要写很长的话,我个人推荐下面的写法
// 提前声明,我不是不会auto for ( // 在写 `if ()' 语句和 `for()' 语句时,最好在`if'和`for' 之后敲一个空格,以与函数调用区分开 vector<int>::iterator iter = v.begin(); // 括号内的内容推荐多一个缩进 iter != v.end(); iter++){ // 请注意这里和下面,右小括号和左大括号放在一起时的写法。这里的写法比较不容易区分iter++是否是大括号内的内容 count++; int val = *iter; if ( val >= lower_value && // 表达式太长需要换行时,运算符通常写在当前行末尾,而不是第二行开头 val < upper_value && // 为了好看,这里的`upper_value'前面敲了两个空格 is_prime(val) ){ // 请注意这里和上面右小括号和左大括号放在一起时的写法 sum += val; } else sum -= val; }
关于空格的使用:通常在每个二元运算符左右各加一个空格,而一元运算符后面不加空格。通常在每个逗号和分号后面加一个空格
有时为了排版好看,故意用多个空格或tab,比如下面代码
void Bit_vector::write_buffer_to_file(FILE* output){ fwrite(&capacity, sizeof(int), 1, output); fwrite(&end_bit_index_of_byte, sizeof(int), 1, output); fwrite(&end_byte_index, sizeof(int), 1, output); fwrite(buffer, sizeof(char),end_byte_index+1, output); }
上一章写起来特别抽象,可能对于没有编程基础的人很难理解。问题不大,直接进入第一章。本教程有意将常量运算与变量运算分开,以便于讲解更深入的内容
先介绍一个简单的printf()
打印功能,这里用于打印int
类型的数据。printf()
函数的详情功能可以查看这个页面,下面也会详细介绍
"%d\n"
中的%d
是占位符
printf()
函数会扫描第一个参数的字符串扫描并打印,如果扫描到占位符,则用printf()
后面的参数代替占位符printf()
函数第一个参数有两个占位符。打印时,第一个占位符被替换成了第二个参数9
,第二个占位符被替换成了第三个参数30
%d
用于打印整形,%c
用于打印char
类型等,详情查看这个页面"%d\n"
中的'\n'
表示换行,即把光标移动到下一行的开始位置
2023930192359
#include <stdio.h> #include <stdlib.h> int main(){ printf("%d\n", 2023); printf("%d\n%d\n", 9, 30); printf("%d\n", 9+10); printf("%d\n", 3+10*2); // (*)的优先级大于(+) printf("%d\n", 10-3-2); // (-)是左结合的 printf("%d\n", 10-(3-2)); // (())可以改变运算的优先级 return 0; }
上面代码的执行结果是下面
2023 9 30 19 23 5 9
下面先介绍运算符的运算顺序、优先级与结合性。关于运算符具体的功能,将在后面的内容分节介绍
上面代码中,printf("%d\n", 3+10*2);
的结果是打印23
。注意这里的运算顺序,并不是先运算(3+10)*2
,而是先运算3+(10*2)
。我们可能认为这理所当然,而在C语言中,这涉及到一个优先级的问题。在这里,*
的优先级是高于+
的
优先级和结合性可以查看, 这个网站。括号可以改变运算的优先级
然后说什么是结合性。在运算10-3-2
的时候,我们理所当然认为应该先运算(10-3)-2
,而不是10-(3-2)
。因为,-
是左结合的,或者叫从左到右运算。一些运算符是右结合的,比如(=)。虽然还没到学变量的时候,但还是给出下面的代码
#include <stdio.h> #include <stdlib.h> int main(){ int a; a = 3+2;// (=)是右结合的,所以会先计算(=)右边的内容,等到结果5计算完毕后,再进行(=)运算 printf("%d\n", a); return 0; }
优先级相同的运算符的结合性一定相同。运算时,先运算优先级高的,然后运算优先级低的。同等优先级的,按照结合性进行从左到右或者从右到左运算。括号可以改变优先级
上面我们涉及到的都是int
类型的常量,现在介绍一个新的类型char
和它的编码表示。一个char
类型的大小是一个字节(byte),一个字节是8位二进制数字。下面一段介绍什么是二进制数字
我们经常使用的是十进制数字,十进制数字是由0123456789
这些数字之中的一个或多个组成的串(但不可以有前导零)。二进制数字是一串用0
或1
组成的串(可以有前导零)
0b
,八进制数字前缀为0
,十六进制数字常用前缀为0x
0b00001010
这样的8位二进制数字是合法的。十进制数字通常不可以有前导零,所以1023
是合法的十进制数字,而01023
不是合法的十进制数字二进制数字和十进制数字可以互相换算,详情可以查看此文。下面是二进制数字0b01001010
换算成十进制数字的例子,其中,每一个二进制位的位置对应着一个权重。运算时,将每一位的值乘以这一位对应的权重,然后求和,就可以得到十进制数字
128 64 32 16 8 4 2 1 每一位的权重 * * * * * * * * 乘以 +--+--+--+--+--+--+--+--+ ob| 0| 1| 0| 0| 1| 0| 1| 0|每一位的值 +--+--+--+--+--+--+--+--+ 0+64+ 0+ 0+ 8+ 0+ 2+ 0 得到的数字 = 74 加在一起就是对应的十进制数字
计算机科学常见数字表示还有十六进制,它由0123456789ABCDEF
16个字符组成,其中字母可以小写。下面给出了每一个字符对应的十进制数字对应表,以及一个十六进制数字换算示例
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | F| E| D| C| B| A| 9| 8| 7| 6| 5| 4| 3| 2| 1| 0| +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |15|14|13|12|11|10| 9| 8| 7| 6| 5| 4| 3| 2| 1| 0| +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
65536 4096 256 16 1 * * * * * +-----+-----+-----+-----+-----+ | 0xa| 6| 0| f| 3| | 10| 6| 0| 15| 3| +-----+-----+-----+-----+-----+ 655360+24576+ 0+ 240+ 3=680179
上文提到,char
类型是一个字节大小,而一个字节是8个二进制位。因为每个二进制位都有两种可能(分别是0
或1
),所以char
类型的数字一共有256种,可以用来表示-128~127
或0~255
之间的数字,也可以用来表示字符,毕竟char
是character
的缩写
char
类型的值可以用来表示ASCII码字符,ASCII码字符与char
类型的值的对应关系可以查看这个网页。单个ASCII字符的字面量,例如单个'a'
,的类型也是char
类型
0xf
中看出它的值是int
类型的十进制数字15
。而对于变量,无法从它的名字看出它的值
int
类型的字面量没有后缀,所以没有后缀的,不带小数点的数字字面量都是int
类型TODO:可以使用两个十六进制数字来表示一个字节
TODO:大端与小端
TODO:sizeof()关键字
位运算一共有6种,分别是按位非~
、按位与&
、按位或|
、按位异或、左移、右移
我们从最简单的运算:逻辑非运算讲起。我们知道,计算机通过0
或1
的编码来存储数据。非运算很简单,即在运算后,把操作数每一个二进制位中的0
变成1
,把1
变成0
。逻辑非的真值表以及一个运算示例如下
类型转换分为两种,一种是自动类型转换,一种是手动类型转换
C语言中手动类型转换的语法是第一行。C++中支持更复杂的类型转换,可以使用第二条语法来增强可读性
(type) value type(value)
手动类型转换常用于解决除法运算结果是整数的问题
#include <stdio.h> #include <stdlib.h> int main(){ printf("%lf\n", (double)10 / 3); // 先将10转换为double类型,然后10/3就变成了(double)除以(int)。根据上述自动类型转换内容,除号的两个操作数均变为(double),所以运算结果是3.33333 printf("%lf\n", 10/(double)3); // printf("%lf\n", (double)(10 / 3)); return 0; }
上面代码的执行结果是下面。为什么第三行输出是3.000000
,因为它是先进行的(int 10)/(int 10)
,得到(int 3)
,然后由(int 3)
转为double
类型
3.333333 3.333333 3.000000
#include <stdio.h> #include <stdlib.h> // 习惯将不同作用的代码用空行分开。比如上面的代码是引用头文件,下面的代码是函数声明 int f(); // 一些教程会在函数被实现之前,在开头给出其声明 // 这里的空行也是如此,用于分开函数声明和函数实现 int f(void){ // 此函数先给出的声明,然后给出的实现 return 10; } // 习惯为了好看,在函数实现后加一个空行 char g(){ // 此函数声明时同时实现 return '+; //43 } int main(){ printf("%d\n", f()); printf("%c\n", g()); printf("%d\n", f()+g()); printf("%c\n", getchar()); return 0; }
上面代码的执行结果是下面
10 63 53
然后程序会阻塞到这里。此时输入一个字符,假设是a,然后回车,然后程序就会将这个字符打印出来,然后退出
上面代码声明了三个函数,这三个函数的返回类型第一个和第三个是int
类型,第二个是char
类型。这两个函数都没有参数,没有参数的函数可以在其参数列表置空,也可以写入void
编译器将C语言代码编译成可执行文件,可执行文件内部是可以直接由CPU执行的机器码。机器码可读性很差,所以通常将一些机器码简记为汇编。汇编有两种风格,分别是Intel和AT&T,这两种风格有很大差别。此处使用AT&T汇编风格。linux系统中可以使用objdump
命令查看一个可执行文件的反汇编代码
48 83 ec 08 sub $0x8, %rsp # 汇编代码的注释使用的是井号,而不是两个斜线
上面的示例中,第一行机器码(用十六进制数字表示)对应的汇编代码为第二行
运算时,CPU会将数据存储到寄存器中,一些CPU内有16个寄存器。CPU的运算单元可以根据寄存器的值以及机器代码来取出其中两个寄存器的值,进行运算,并将运算结果存入其中一个寄存器中。在调用函数时,寄存器用于传递函数参数
#include <stdio.h> int main(){ printf("%d\n", 3+10*2): return 0; }
上面的C语言代码可以编译后的可执行文件的反汇编是下面的汇编代码,可以通过看注释来体验一下最底层CPU层面代码是怎样运行的。需要注意的是,几乎所有编译器都会进行优化,所以你进行反编译的时候,极有可能不会得到和下面相同的反汇编代码(所以下面的反汇编是我根据现有代码改编的) TODO:添加代码高亮,将注释对其
<main>: mov $0xa, %edx # 将寄存器%edx赋值为立即数$3.汇编中,立即数前面需要加$符号,0x表示该立即数十六进制数字表示。$0xa对应十进制数字是`10' mov $0x2, %eax # 将寄存器%eax赋值为立即数2。此时,%edx寄存器的值为10,%eax寄存器的值为2 imul %eax, %edx # 让寄存器%eax的值乘以%edx的值,结果保存在%edx寄存器中 mov $0x3, %eax # 将寄存器%eax的值赋值为立即数3。此时,%edx寄存器的值为20,%eax寄存器的值为3 add %edx, %eax # 让寄存器%edx的值乘以%eax的值,结果保存在%eax寄存器中 mov %eax %esi # 将寄存器%eax的值赋值给%esi,这样%esi的值就是刚刚的运算结果。%esi用于传递printf()函数的第二个参数 lea 0xe83(%rip), %rax # % 这一行和下两行可以忽略 mov %rax, %rdi # 这一行和上一行用于将字符串常量"%d\n"作为第一个参数传入 mov $0x0 %eax call <printf@plt> # 调用printf()函数。下面几行可以忽略 mov $0x0, %eax # 对应return 0。%eax用于传递函数的返回值 leave ret # 函数调用结束,返回
前两章都是关于常量运算的,这里开始变量运算
TODO:变量的名字不可以是关键字 TODO:变量的关键字标记
const
关键字可以标记一个变量为常变量,后面会详细介绍TODO:静态变量。虽然这里还没有讲到变量的自加运算和指针,但还是给出一个静态变量的使用示例 TODO:CSAPP中将program stack译作程序栈
TODO:需要注意自加与自减运算
TODO:的返回值是0或1
TODO:可以没有else
TODO:if-else if-else 是一种特殊的 if-else 使用方式
TODO:是否是计数
TODO:math函数库
TODO:调换下顺序
TODO:如果goto来goto去可能会太乱,不推荐使用
TODO:用计算10000!%10001为例引入群论,顺便介绍王义和的离散数学引论 TODO:stack overflow
我们都知道,C语言是编译型编程语言,也就是说,C语言代码被编译器编译为可执行文件,然后才可以执行。本质上,被执行的并不是C语言代码,而是被可执行文件
TODO:介绍malloc free函数,内存泄漏和堆
TODO:一个先有鸡还是先有蛋的问题
TODO:C语言和C++的struct不同 TODO:介绍sizeof()操作,因为结构体内部分配内存情况未知
编程范式大概可分为两种:面向过程和面向对象(object-oriented programming,简称OOP或OO看上去很唬人——我在用面向对象编程哎——其实不是什么新鲜东西)
面向结果编程(认真你就输了) |
---|
还有一种编程范式是面向结果编程,通常用于糊弄编程作业。比如下面的Python代码用于糊弄打印前10个质数的作业
print(2) # Python 用井号开始注释,语句后不需要加分号 print(3) # Python 的 print()函数可以自动换行 print(5) '''Python 也可以用这种方式作为注释''' print(7) '''这种注释的本质是一个没有被赋值给变量的字符串''' print(9) '''这种注释可以跨行''' print(11) # 你发没发现,你现在可以看懂一些Python代码了 print(13) print(17) print(19) print(23) print(29) |
TODO:需要在同一个文件夹或目录下 TODO:其中file是一个FILE*类型的文件句柄。这其中,文件句柄并不是文件本身,但是程序员可通过文件句柄来读取文件
#include <stdio.h> #include <stdlib.h> int main(){ const char* file_name = "file.txt" FILE* file = fopen("file.txt", "r+"); if (file == NULL){ printf("Unable to open %s\n"); return -1;// 在程序出现错误时,通常不return 0 } for(char c; c = fgetc(); c!=EOF){ putchar(c); } printf("\n"); return 0; }
这一章就要实践起来了,打开Code::Blocks或者VSCode,以及安装一个linux虚拟机(推荐用VMVare和Ubuntu入门),因为很多指令在虚拟机环境下更容易操作
“ | 纸上得来终觉浅,绝知此事要躬行 | ” |
——陆游《冬夜读书示子聿》 |
TODO:macro名字的由来
很多C语言教程会在这部分使用实现一个2048游戏、十步万度游戏、扫雷游戏或者迷宫游戏等作为最后一章的实践内容
TODO: 二叉树的定义和操作,这里选择不带有头结点的二叉树,每次执行返回头结点的指针
TODO:printf()函数debug
你已经攻略了C语言娘了,现在,尝试一下下面的内容吧
C语言是学习其他编程语言的跳板。如果能学明白C语言,那么其他编程语言自然不会有太多吃力。因为C语言已经有50多年的历史,在它之后的语言都或多或少参考了一些C语言
C语言被大量应用在嵌入式。可以尝试买一个51单片机玩玩,大概80软妹币以内,然后学习模电和数电,走上嵌入式工程师的道路。找时间我也可以出一个单片机教程
除了嵌入式以外,C语言的应用场景较少。可以尝试学一下C++。在高效性上C++的执行效率仅次于C语言。以后有时间我会考虑做一个C++教程
可以直接学CSAPP(Computer System: A Programmers' Perspective,深入理解计算机系统),这个是计算机科学最经典的教材之一,据说一些国外的大学要学好几遍这本教材[来源请求]
可以攻略考取大学的计算机专业
可以尝试参加一些代码项目,哪怕只能贡献翻译也挺好的。可以帮我把这个烂摊子收拾一下U:渚 花/Lua参考手册(翻译)
可以看一些开源代码。如果看不懂的话推荐先学习数据结构与常见算法,和常见设计模式
可以尝试学一下软件工程的内容。学完之后就可以尝试为一些开源软件贡献代码了
可以尝试攻略一下洛谷娘
可以尝试攻略一些常见的编程语言娘,比如Java娘、Python娘等。Python的设计和其他很多主流编程语言不同,被称为Pythonic
Python哲学 |
---|
在Python命令行输入 >>> import this |
可以尝试攻略一下世界上最好的编程语言娘PHP娘
此教程主要参考菜鸟教程、苏小红《C语言程序设计 第四版》(也推荐这本)
待添加的内容
atoi()
这种可读性很差的库函数名是历史遗留问题。包括函数声明和实现分离,也是历史遗留问题,虽然很多教程还是会这样教,但我认为现在已经完全无必要
一个static关键字的例子
#include <stdio.h> #include <stdlib.h> // 如果pcount不是NULL,则在其指向的内存地址中写入该函数执行次数 // 否则仅返回加法返回值 int add(int a, int b, int* pcount){ static int count = 0; // 静态变量只会在函数第一次被调用时被赋值 count++; if (pcount){ // 等价于 if (pcount != NULL) *pcount = count; } return a+b; } int main(){ int count = 0; add(1, 2, NULL); printf("%d\n", add(1, 2, &count)); printf("%d\n", count); printf("%d\n", add(3, 4, &count)); printf("%d\n", count); return 0; }
下面是上面代码的执行结果
3 2 7 3