一、初识C语言
1、C语言优点:高效性、可移植性、强大而灵活、面向程序员。
- 2、使用C语言的7个步骤:定义程序的目标;设计程序;编写代码;编译;运行程序;测试和调试程序;维护和修改程序。
- 3、可移植程序:其源代码无需修改或少量修改就能在不同计算机系统中成功编译的程序。
- 4、源代码文件:包含程序员使用的任何编程语言编写的代码。
- 目标代码文件:包含机器语言代码,它不必是完整的程序代码。
- 可执行文件:包含组成可执行程序的完整机器语言代码。
- 5、编译器的任务:把源代码(如C语言编写的代码)翻译成等价的机器语言代码(也叫目标代码)。
- 6、链接器的任务:把编译器翻译好的源代码以及库代码和启动代码组合起来,生成一个可执行程序。
二、C语言概述
- 1、如果程序窗口一闪而过,可以在return 0;的前面加getchar()。如果加过一个getchar()后仍然不行,那就再加一个getchar(),两个一定可以。或者只在return 0;的前面加system("pause");就可以解决。
-
2、printf("%d",n)中的%d相当于一个占位符,其作用是指明输出n值的位置。提醒程序要在该处打印一个变量,d表明把变量作为十进制整数打印。
-
3、提高程序可读性的技巧:选择有意义的函数名、写注释、在函数中用空行分隔概念上的多个部分、每条语句各占一行。
-
4、如何发现语法错误:先在编译之前浏览源代码看是否能发现一些明显的错误。在编译查看编译器是否发现错误。(编译器的一个常见毛病是报错的位置比真正的错误位置滞后一行)
-
如何发现语义错误:(语义错误指意思上的错误)编译器无法检测语义错误,人为一步一步查看代码的执行情况,是发现程序问题所在的良方。
-
定位语义错误的另一种方法:在程序中的关键点插入额外的printf()语句,以监视指定变量值的变化,并检查该程序变量的值。
-
5、变量的命名:可以用小写字母、大写字母、数字和下划线(_)来命名,名称的第1个字符必须是字符或下划线,不能是数字。
-
操作系统和C库经常使用以一个或两个下划线字符开始的标识符(如:_kcab),因此最好避免在自己的程序中使用这种名称。
三、数据类型关键字
-
1、浮点数表示法:3.16E3表示3.16×10³。
-
整数没有小数部分,浮点数有小数部分;
-
浮点数可以表示的范围比整数大;
-
对于一些算术运算(比如两个很大的数相减),浮点数损失的精度更多;
-
因为在任何区间内(比如1.0到2.0之间)都存在无穷多个实数,所以计算机的浮点数不能表示区间内的所有值。浮点数通常只是实际值的近似值。例如,7.0可能被存储为浮点值6.99999.
-
2、声明为变量创建和标记存储空间,并为其指定初始值,但最好不要把未初始化的变量和未初始化的变量放在同一条声明中。
-
3、prinft()函数的参数数目不定,可以有1个、2个、3个或更多。使用printf()函数时,要确保转换说明的数量与待打印值的数量相等。
-
4、%d表示十进制,%O表示八进制,%x表示十六进制。
-
5、short类型“可能”比int类型占用的空间少,long类型“可能”比int类型占用的空间多:C语言只规定了short占用的存储空间不能多于int,long占用的存储空间不能少于int。这样规定是为了适应不同的机器(32位和64位)。
-
C标准对基本数据类型只规定了允许的最小大小
-
6、char类型用于存储字符(如字母或标点符号),但从技术层面看,char是整数类型。因为char类型实际上存储的是整数而不是字符(如ASCII编码)。
-
7、用单引号‘’括起来的单个字符被称为字符常量,双引号“”括起来的单个字符是字符串。
-
8、bool类型,用于表示布尔值,即逻辑值true和false。文卫C语言用值1表示true,值0表示false,所以bool类型实际上也是一种整数类型。但原则上它仅占1位存储空间,因为对0和1而言,1位的存储空间就足够了。
-
9、浮点型常量:-1.56E+12和2.87e-3。正号可以省略,可以没有小数点(如,2E5)或整数部分(如,19.28),但不能同时省略两者;可以省略小数部分(如,3.E16)或整数部分(如,.45E-6),但不能同时省略两者。
-
10、printf()函数使用%f转换说明打印十进制记数法的float和double类型浮点数,用%e打印指数记数法的浮点数。
-
11、浮点值Nan(not a number的缩写):表示非数。
-
12、sizeof是C语言的内置运算符,不是函数,以字节为单位给出指定类型的大小,用%zd转换说明匹配sizeof的返回类型,即size_t类型。
printf("Type double has a size of %zd bytes.\n",sizeof(double));
13、printf()和scanf()函数与一般函数不同,它们的参数个数是可变的。函数要知道函数的参数个数才能正常工作,printf()和scanf()函数用第1个参数表明后续有多少个参数,即第1个字符串中的转换说明与后面的参数一一对应。
-
14、printf()何时把输出发送到屏幕上:最初,printf()语句把输出发送到一个叫做缓冲区(buffer)的中间存储区域,然后缓冲区中的内容再不断被发送到屏幕上。C标准明确规定了何时把缓冲区中的内容发送到屏幕:当缓冲区满、遇到换行字符(\n)或需要输入的时候(从缓冲区把数据发送到屏幕或文件被称为刷新缓冲区)。例如,前两个printf()语句既没有填满缓冲区,也没有换行符,但是下一条scanf()语句要求用户输入,这迫使printf()的输出被发送到屏幕上。
-
四、字符串和格式化输入/输出
-
1、字符串是一个或多个字符的序列。双引号不是字符串的一部分,双引号仅告知编译器它括起来的是字符串。
-
2、C中的字符串一定以空字符结束,这意味着数组的容量必须至少比待存储字符串中的字符数多1。比如数组中的字符串有40个存储单元,但只能存储39个字符,剩下一个留给空字符(\0)。
-
3、数组是同类型数据元素的有序序列,一定要是同类型的数据元素。
-
4、scanf()从第1个非空白字符开始读取,在遇到第一个空白(空格、制表符或换行符)时就不再读取输入,因此scanf()只会读取字符串中的一个单词,而不是一整句。
-
5、strlen()函数统计字符串长度时,不把空字符统计在内。
-
6、printf()函数中打印%符号:printf("%%"),用两个%即可。
-
7、 printf()函数的返回值是返回打印字符的个数。如果有输出错误,printf()则返回一个负值。
-
8、scanf()函数返回成功读取的项数。如果没有读取任何项,且需要读取一个数字而用户却输入一个非数值字符串,scanf()便返回0。当scanf()检测到“文件结尾”时,会返回EOF(end of file)。
-
9、字符串中,无论是表示成字符常量还是存储在字符数组中,都以一个叫做空字符的隐藏字符结尾。
-
10、C预处理器为预处理器指令(以#符号开始)查找源代码程序,并在开始编译程序之前处理它们。处理器根据#include指令把另一个文件中的内日用添加到该指令所在的位置。
-
五、运算符、表达式和语句
-
1、如果一个变量出现在一个函数的多个参数中,不要对该变量使用递增或递减运算符。
-
如果一个变量多次出现在一个表达式中,不要对该变量使用递增或递减运算符。
-
2、C表达式的一个最重要的特性:每个表达式都有一个值。
-
3、声明创建了名称和类型,并为变量分配一个值,但声明不是表达式语句。
-
4、序列点是程序执行的点,在该点上,所有的副作用都在进入下一步之前发生。在C语言中,语句中的分号标记了一个序列点。意思是,在一个语句中,赋值运算符、递增运算符和递减运算符对运算对象做的改变必须在程序执行下一条语句之前完成。
-
5、当作为函数参数传递时,char和short被转换成int,float被转换成double。
-
六、C控制语句:循环
-
1、关系表达式为真,求值得1;关系表达式为假,求值得0。
-
2、while循环和for循环都是入口条件循环,即在循环的每次迭代之前检查测试条件,所以有可能根本不执行循环体中的内容。
-
do-while循环是出口条件循环,即在循环的每次迭代之后检查测试条件,保证了至少执行循环体中的内容一次。适用于那些至少要迭代一次的循环。
-
3、当循环涉及初始化和更新变量时,用for循环比较合适,而在其他情况下用while循环更好。
-
while (scanf ("%d", &n) == 1); for (int i = 1; i < 10; ++i);
4、#define N 10和const int N = 10;等价,但一般多用第二种方法定义常量。
-
5、创建循环时注意3个方面:注意循环的测试条件要能使循环结束;
-
确保循环测试中的值在首次使用之前已初始化;
-
确保循环在每次迭代都更新测试的值。
-
七、C控制语句:分支和跳转
-
1、逻辑表达式的求值顺序是从左往右,一但发现有使整个表达式为假的因素,立即停止求值。
-
2、条件运算符(?:):是C语言中唯一一个三元运算符,又叫三目运算符。通用形式为:
A ? B : C
如果A为真,则表达式值为B;如果A为假,则表达式值为C。典型的例子是,把两个值中的最大值赋给变量:
max = ( a > b ) ? a : b;
如果a大于b,那么将max设置为a,否则设置为b。
-
条件运算符是一个整体。
-
3、continue和break
-
continue:终止本轮循环,开始下轮循环;
-
break:直接终止包含break的循环,并继续执行下一阶段。
-
4、switch语句:
-
switch ( number ) //注意:1 和 2 都必须是常量 { case 1: A; //如果number匹配 1,则执行 A,并跳出switch选择,接着执行 D break; case 2: B; //如果number匹配 2,则执行 B,并跳出switch选择,接着执行 D break; default: C; //如果都没有匹配,则执行default后的语句,即执行 C } D;
5、goto语句:C程序员可以接受一种goto的用法——出现问题时从一组嵌套循环中跳出(一条break语句只能跳出当前循环)。
-
6、为什么不建议使用goto语句:
-
①在程序比较简单时用goto语句是比较灵活的,但是当程序比较复杂时很容易造成程序流程的混乱;
-
②利用goto语句编写程序,对于后面看程序的人是很难理解的;
-
③调试程序的过程也会变得很困难。
-
7、所有的循环和switch语句都可以使用break语句,它使程序控制跳出当前循环或switch语句的剩余部分,并继续执行跟在循环或switch后面的语句。
-
所有的循环都可以使用continue语句,但是switch语句不行。
-
goto语句是程序控制跳转至相应标签语句。冒号用于分隔标签和标签语句,标签名遵循变量命名规则,标签语句可以出现在goto的前面或后面。
if (size > 12) goto a; goto b; a : cost = cost * 15; flag = 2; b : bill = cost * flag;
八、字符输入/输出和输入验证
-
1、getchar()和putchar()每次只处理一个字符。
-
2、缓冲分为两类:完全缓冲I/O和行缓冲I/O
-
完全缓冲I/O:指的是当缓冲区被填满时才刷新缓冲区(内容被发送到目的地),同常出现在文件输入中。
-
行缓冲I/O:指的是在出现换行符时刷新缓冲区。键盘输入通常是杭焕冲输入,所以在按下Enter键后才刷新缓冲区。
-
3、在C语言中,用getchar()读取文件检测到文件结尾时将返回一个特殊的值,即EOF(end of file的缩写);scanf()函数检测到文件结尾时也返回EOF。通常stdio.h头文件将EOF定义为-1,关键要理解EOF是一个值,标志着检测到文件结尾,并不是在文件中找得到的符号。
-
4、重定向输入:假设已经编译好了hello.c程序,并把可执行版本放入一个名为hello.exe的文件中。运行该程序,输入可执行文件名:hello.exe < words。“ < ”符号是重定向运算符,该运算符使words文件与stdin流关联,把文件words中的内容导入hello.c程序。
-
5、重定向输出:假设用hello.exe把键盘输入的内容发送到名为mywords的文件中,使用hello.c > mywords语句。“ > ”符号是第2个重定向运算符,它创建了一个名为mywords的新文件,然后把hello.c的输出(即你输入字符的副本)重定向至该文件中。
-
6、getchar()读取每个字符,包括空格、制表符和换行符;而scanf()在读取数字时则会跳过空格、制表符和换行符。
-
九、函数
-
1、实际参数是出现在函数调用圆括号中的表达式,形式参数是函数定义的函数头中声明的变量。
-
2、递归的要点:①每级函数调用都有自己的变量;
-
②每次函数调用都会返回一次;
-
③递归函数中位于递归调用之前的语句,均按被调用函数的顺序执行;
-
④递归函数中位于递归调用之后的语句,均按被调函数相反的顺序执行;
-
⑤虽然每级递归都有自己的变量,但是并没有拷贝函数的代码;
-
⑥递归函数必须包含能让递归调用停止的语句。
-
3、尾递归:是最简单的递归形式,它把递归调用置于函数的末尾,即正好在return语句之前。因为递归调用在函数的末尾,因此它相当于循环。
-
4、递归的优缺点:
-
优点:递归为某些编程问题提供了最简单的解决方案(如倒序计算)。
-
缺点:递归算法会快速消耗计算机的内存资源,且递归不方便阅读和维护。
-
5、如果主调函数不使用return返回的值,则必须通过地址才能修改主调函数中的值。
-
6、指针是一个值为内存地址的变量(或数据对象),指针变量的值是地址。
-
7、地址运算符&:后面跟一个变量名时,&给出该变量的地址。如&nurse表示变量nurse的地址。
-
地址运算符*:后跟一个指针名或地址时,*给出存储在指针指向地址上的值。如
nurse = 22; prt = &nurse; //指向nurse的指针 val = *ptr; //把ptr指向的地址上的值赋给val 执行以上3条语句的最终结果是把22赋给val
8、指针的两大作用:偏移和值传递。
-
十、数组和指针
-
1、如果不初始化数组,数组元素和未初始化的普通变量一样,其中存储的都是垃圾值;如果部分初始化数组,剩余的元素就会被初始化为0。
-
2、指针加1指的是增加一个存储单元。对数组而言,这意味着加1后的地址是下一个元素的地址,而不是下一个字节的地址。
-
3、数组指针和指针数组:
int (*p) [n] //数组指针 int * p[n] //指针数组
4、指针与整数相加:整数都会和指针所指向类型的大小(以字节为单位)相乘,然后把结果与原始地址相加。
-
5、指针求差:可以计算两个指针的差值。通常,球差的两个指针分别指向同一个数组的不同元素,通过计算求出两元素之间的距离。差值的单位与数组类型的单位相同。下例中,ptr2 - ptr1的值为2,意思是这两个指针所指向的两个元素相隔两个int,而不是2字节。只要两个指针都指向相同的数组,C都能保证相减的运算有效。如果指向两个不同数组的指针进行求差运算可能会得到一个值,或者导致运行时错误。
int ptr[4]; int *ptr1 = ptr[1]; int *ptr2 = ptr[3]; int n = prt2 - ptr1; //n的值为2
6、对于一个处理基本类型(如int)的函数时,要选择是传递int类型的值还是传递指向int的指针。通常都是直接传递数值,只有程序需要再函数中改变该数值时,才会传递指针。对于数组别无选择,必须传递指针,因为这样做效率高。
-
7、一般而言,如果编写的函数需要修改数组,在声明数组形参时则不使用const;如果编写的函数不用修改数组,那么在声明数组形参时最好使用const。
-
8、C把数组名解释为该数组首元素的地址,即数组名与指向该数组首元素的指针等价。概括而言,数组和指针的关系十分密切。如果str是一个数组,那么表达式str [ i ]和* (str + i)等价。
-
9、不能把整个数组作为参数传递给函数,但是可以传递数组的地址,然后函数可以使用传入的地址操控原始数组。
-