您的位置:首页 > 编程语言 > C语言/C++

C语言理论复习,重点回顾

2019-05-26 16:56 78 查看

puts(“C语言中文网”);
这里有一个生疏的词汇puts,用来让计算机在屏幕上显示文字。
更加专业的称呼:
"在屏幕上显示文字"叫做输出(Output);
每个文字都是一个字符(Character);
多个字符组合起来,就是一个字符序列,叫做字符串(String)。
puts 是 output string 的缩写,意思是”输出字符串“。
在C语言中,字符串需要用双引号" "包围起来,C语言中文网什么也不是,计算机不认识它,“C语言中文网"才是字符串。
puts 在输出字符串的时候,需要将字符串放在( )内。
在汉语和英语中,分别使用。和.表示一句话的结束,而在C语言中,使用;表示一个语句的结束。puts(“C语言中文网”)表达了完整的意思,是一个完整的语句,需要在最后加上;,表示当前语句结束了。
总结起来,上面的语句可以分为三个部分:
puts( )命令计算机输出字符串;
"C语言中文网"是要输出的内容;
;表示语句结束。
字符串输出 用puts代替printf 效率更高
一些相似的中英文标点符号:
中文分号;和英文分号;;
中文逗号,和英文逗号,;
中文冒号:和英文冒号:;
中文括号()和英文括号();
中文问号?和英文问号?;
中文单引号’‘和英文单引号’ ';
中文双引号“ ”和英文双引号” "。
严格区分语法,避免低级错误
另外最重要的一点是:“相同”字符在全角和半角状态下对应的编码值(例如 Unicode 编码、GBK 编码等)不一样,所以它们是不同的字符。
每种编程语言的源文件都有特定的后缀,以方便被编译器识别,被程序员理解。源文件后缀大都根据编程语言本身的名字来命名,例如:
C语言源文件的后缀是.c;
C++语言(C Plus Plus)源文件的后缀是.cpp;
Java 源文件的后缀是.java;
Python 源文件的后缀是.py
JavaScript 源文件后置是.js。
我们平时所说的程序,是指双击后就可以直接运行的程序,这样的程序被称为可执行程序(Executable Program)。在 Windows 下,可执行程序的后缀有.exe和.com(其中.exe比较常见);在类 UNIX 系统(Linux、Mac OS 等)下,可执行程序没有特定的后缀,系统根据文件的头部信息来判断是否是可执行程序。

可执行程序的内部是一系列计算机指令和数据的集合,它们都是二进制形式的,CPU 可以直接识别,毫无障碍;但是对于程序员,它们非常晦涩,难以记忆和使用。
嵌入式系统
而在嵌入式系统方面,可用的C语言编译器就非常丰富了,比如:
用于 Keil 公司 51 系列单片机的 Keil C51 编译器;
当前大红大紫的 Arduino 板搭载的开发套件,可用针对 AVR 微控制器的 AVR GCC 编译器;
ARM 自己出的 ADS(ARM Development Suite)、RVDS(RealView Development Suite)和当前最新的 DS-5 Studio;
DSP 设计商 TI(Texas Instruments)的 CCS(Code Composer Studio);
DSP 设计商 ADI(Analog Devices,Inc.)的 Visual DSP++ 编译器,等等。

通常,用于嵌入式系统开发的编译工具链都没有免费版本,而且一般需要通过国内代理进行购买。所以,这对于个人开发者或者嵌入式系统爱好者而言是一道不低的门槛。

不过 Arduino 的开发套件是可免费下载使用的,并且用它做开发板连接调试也十分简单。Arduino 所采用的C编译器是基于 GCC 的。

还有像树莓派(Raspberry Pi)这种迷你电脑可以直接使用 GCC 和 Clang 编译器。此外,还有像 nVidia 公司推出的 Jetson TK 系列开发板也可直接使用 GCC 和 Clang 编译器。树莓派与 Jetson TK 都默认安装了 Linux 操作系统。

在嵌入式领域,一般比较低端的单片机,比如 8 位的 MCU 所对应的C编译器可能只支持 C90 标准,有些甚至连 C90 标准的很多特性都不支持。因为它们一方面内存小,ROM 的容量也小;另一方面,本身处理器机能就十分有限,有些甚至无法支持函数指针,因为处理器本身不包含通过寄存器做间接过程调用的指令。

而像 32 位处理器或 DSP,一般都至少能支持 C99 标准,它们本身的性能也十分强大。而像 ARM 出的 RVDS 编译器甚至可用 GNU 语法扩展。

下图展示了上述C语言编译器的分类。

int 一般占用 4 个字节(Byte)的内存,共计 32 位(Bit)。如果不考虑正负数,当所有的位都为 1 时它的值最大,为 232-1 = 4,294,967,295 ≈ 43亿,这是一个很大的数,实际开发中很少用到,而诸如 1、99、12098 等较小的数使用频率反而较高。
只有 short 的长度是确定的,是两个字节,而 int 和 long 的长度无法确定,在不同的环境下有不同的表现。short 至少占用 2 个字节。
int 建议为一个机器字长。32 位环境下机器字长为 4 字节,64 位环境下机器字长为 8 字节。
short 的长度不能大于 int,long 的长度不能小于 int。
在 32 位环境以及 Win64 环境下的运行结果为:
short=2, int=4, long=4, char=1
在 64 位 Linux 和 Mac OS 下的运行结果为:
short=2, int=4, long=8, char=1
%hd用来输出 short int 类型,hd 是 short decimal 的简写;
%d用来输出 int 类型,d 是 decimal 的简写;
%ld用来输出 long int 类型,ld 是 long decimal 的简写。
为了解决这个问题,C语言推出了一种新的类型,叫做 wchar_t。w 是 wide 的首字母,t 是 type 的首字符,wchar_t 的意思就是宽字符类型。wchar_t 的长度由编译器决定:wchar_t 类型位于 <wchar.h> 头文件中,它使得代码在具有良好移植性的同时,也节省了不少内存,以后我们就用它来存储宽字符。

上节我们讲到,单独的字符由单引号’ ‘包围,例如’B’、’@’、‘9’等;但是,这样的字符只能使用 ASCII 编码,要想使用宽字符的编码方式,就得加上L前缀,例如L’A’、L’9’、L’中’、L’国’、L’。’。

注意,加上L前缀后,所有的字符都将成为宽字符,占用 2 个字节或者 4 个字节的内存,包括 ASCII 中的英文字符。
putchar、printf 只能输出不加L前缀的窄字符,对加了L前缀的宽字符无能为力,我们必须使用 <wchar.h> 头文件中的宽字符输出函数,它们分别是 putwchar 和 wprintf:
putwchar 函数专门用来输出一个宽字符,它和 putchar 的用法类似;
wprintf 是通用的、格式化的宽字符输出函数,它除了可以输出单个宽字符,还可以输出宽字符串(稍后讲解)。宽字符对应的格式控制符为%lc。
<stdio.h> 头文件中的 putchar、puts、printf 函数只能用来处理窄字符;
<wchar.h> 头文件中的 putwchar、wprintf 函数只能用来处理宽字符。

  1. getchar()
    最容易理解的字符输入函数是 getchar(),它就是scanf("%c", c)的替代品,除了更加简洁,没有其它优势了;或者说,getchar() 就是 scanf() 的一个简化版本。

下面的代码演示了 getchar() 的用法:
1.#include <stdio.h>
2.int main()
3.{
4. char c;
5. c = getchar();
6. printf(“c: %c\n”, c);
7.
8. return 0;
9.}
输入示例:
@↙
c: @
你也可以将第 4、5 行的语句合并为一个,从而写作:
char c = getchar();
2) getche()
getche() 就比较有意思了,它没有缓冲区,输入一个字符后会立即读取,不用等待用户按下回车键,这是它和 scanf()、getchar() 的最大区别。请看下面的代码:
1.#include <stdio.h>
2.#include <conio.h>
3.int main()
4.{
5. char c = getche();
6. printf(“c: %c\n”, c);
7.
8. return 0;
9.}
输入示例:
@c: @
输入@后,getche() 立即读取完毕,接着继续执行 printf() 将字符输出,所以没有按下回车键程序就运行结束了。

注意,getche() 位于 conio.h 头文件中,而这个头文件是 Windows 特有的,Linux 和 Mac OS 下没有包含该头文件。换句话说,getche() 并不是标准函数,默认只能在 Windows 下使用,不能在 Linux 和 Mac OS 下使用。
3) getch()
getch() 也没有缓冲区,输入一个字符后会立即读取,不用按下回车键,这一点和 getche() 相同。getch() 的特别之处是它没有回显,看不到输入的字符。所谓回显,就是在控制台上显示出用户输入的字符;没有回显,就不会显示用户输入的字符,就好像根本没有输入一样。

回显在大部分情况下是有必要的,它能够与用户及时交互,让用户清楚地看到自己输入的内容。但在某些特殊情况下,我们却不希望有回显,例如输入密码,有回显是非常危险的,容易被偷窥。

getch() 使用举例:
1.#include <stdio.h>
2.#include <conio.h>
3.int main()
4.{
5. char c = getch();
6. printf(“c: %c\n”, c);
7.
8. return 0;
9.}
输入@后,getch() 会立即读取完毕,接着继续执行 printf() 将字符输出。但是由于 getch() 没有回显,看不到输入的@字符,所以控制台上最终显示的内容为c: @。

注意,和 getche() 一样,getch() 也位于 conio.h 头文件中,也不是标准函数,默认只能在 Windows 下使用,不能在 Linux 和 Mac OS 下使用。
对三个函数的总结
函数 缓冲区 头文件 回显 适用平台
getchar() 有 stdio.h 有 Windows、Linux、Mac OS 等所有平台
getche() 无 conio.h 有 Windows
getch() 无 conio.h 无 Windows

关于缓冲区,我们将在下节《进入缓冲区(缓存)的世界,破解一切与输入输出有关的疑难杂症》中展开讲解。
输入字符串
输入字符串当然可以使用 scanf() 这个通用的输入函数,对应的格式控制符为%s,上节已经讲到了;本节我们重点讲解的是 gets() 这个专用的字符串输入函数,它拥有一个 scanf() 不具备的特性。

gets() 的使用也很简单,请看下面的代码:
1.#include <stdio.h>
2.int main()
3.{
4. char author[30], lang[30], url[30];
5. gets(author);
6. printf(“author: %s\n”, author);
7. gets(lang);
8. printf(“lang: %s\n”, lang);
9. gets(url);
10. printf(“url: %s\n”, url);
11.
12. return 0;
13.}
运行结果:
YanChangSheng↙
author: YanChangSheng
C-Language↙
lang: C-Language
http://c.biancheng.net http://biancheng.net↙
url: http://c.biancheng.net http://biancheng.net
gets() 是有缓冲区的,每次按下回车键,就代表当前输入结束了,gets() 开始从缓冲区中读取内容,这一点和 scanf() 是一样的。gets() 和 scanf() 的主要区别是:
scanf() 读取字符串时以空格为分隔,遇到空格就认为当前字符串结束了,所以无法读取含有空格的字符串。
gets() 认为空格也是字符串的一部分,只有遇到回车键时才认为字符串输入结束,所以,不管输入了多少个空格,只要不按下回车键,对 gets() 来说就是一个完整的字符串。

也就是说,gets() 能读取含有空格的字符串,而 scanf() 不能。
总结
C语言中常用的从控制台读取数据的函数有五个,它们分别是 scanf()、getchar()、getche()、getch() 和 gets()。其中 scanf()、getchar()、gets() 是标准函数,适用于所有平台;getche() 和 getch() 不是标准函数,只能用于 Windows。

scanf() 是通用的输入函数,它可以读取多种类型的数据。

getchar()、getche() 和 getch() 是专用的字符输入函数,它们在缓冲区和回显方面与 scanf() 有着不同的特性,是 scanf() 不能替代的。

gets() 是专用的字符串输入函数,与 scanf() 相比,gets() 的主要优势是可以读取含有空格的字符串。

scanf() 可以一次性读取多份类型相同或者不同的数据,getchar()、getche()、getch() 和 gets() 每次只能读取一份特定类型的数据,不能一次性读取多份数据。
清空输出缓冲区
清空输出缓冲区很简单,使用下面的语句即可:
fflush(stdout);
fflush() 是一个专门用来清空缓冲区的函数,stdout 是 standard output 的缩写,表示标准输出设备,也即显示器。整个语句的意思是,清空标准输入缓冲区,或者说清空显示器的缓冲区。

Windows 平台下的 printf()、puts()、putchar() 等输出函数都是不带缓冲区的,所以不用清空
使用 scanf() 清空缓冲区
scanf() 还有一种高级用法,就是使用类似于正则表达式的通配符,这样它就可以读取所有的字符了,包括空格、换行符、制表符等空白符,不会再忽略它们了。并且,scanf() 还允许把读取到的数据直接丢弃,不用赋值给变量。

请看下面的语句:
scanf("%*[^\n]"); scanf("%*c");
第一个 scanf() 将逐个读取缓冲区中\n之前的其它字符,% 后面的 * 表示将读取的这些字符丢弃,遇到\n字符时便停止读取。此时,缓冲区中尚有一个\n遗留,第二个 scanf() 再将这个\n读取并丢弃,这里的星号和第一个 scanf() 的星号作用相同。由于所有从键盘的输入都是以回车结束的,而回车会产生一个\n字符,所以将\n连同它之前的字符全部读取并丢弃之后,也就相当于清除了输入缓冲区。
然而在实际开发中,往往都是几千行、上万行、百万行的代码,将这些代码都放在一个源文件中简直是灾难,不但检索麻烦,而且打开文件也很慢,所以必须将这些代码分散到多个文件中。对于多个文件的程序,通常是将函数定义放到源文件(.c文件)中,将函数的声明放到头文件(.h文件)中,使用函数时引入对应的头文件就可以,编译器会在链接阶段找到函数体。
在一个函数内部修改全局变量的值会影响其它函数,全局变量的值在函数内部被修改后并不会自动恢复,它会一直保留该值,直到下次被修改。C语言允许在代码块内部定义变量,这样的变量具有块级作用域;换句话说,在代码块内部定义的变量只能在代码块内部使用,出了代码块就无效了。
双层递归的调用关系和数据结构中二叉树的结构完全吻合,所以双层递归常用于二叉树的遍历。

单层递归每次只等待一个函数的结果,双层递归每次要等待两个函数的结果,这就是它们之间最本质的区别。
标准C语言(ANSI C)共定义了15 个头文件,称为“C标准库”,所有的编译器都必须支持,如何正确并熟练的使用这些标准库,可以反映出一个程序员的水平。
合格程序员:<stdio.h>、<ctype.h>、<stdlib.h>、<string.h>
熟练程序员:<assert.h>、<limits.h>、<stddef.h>、<time.h>
优秀程序员:<float.h>、<math.h>、<error.h>、<locale.h>、<setjmp.h>、<signal.h>、<stdarg.h>

以上各类函数不仅数量众多,而且有的还需要硬件知识才能使用,初学者要想全部掌握得需要一个较长的学习过程。我的建议是先掌握一些最基本、最常用的函数,在实践过程中再逐步深入。由于课时关系,本教程只介绍了很少一部分库函数,其余部分读者可根据需要查阅C语言函数手册,网址是 http://www.cplusplus.com
include 的用法有两种,如下所示:
#include <stdHeader.h>
#include “myHeader.h”
使用尖括号< >和双引号" “的区别在于头文件的搜索路径不同:
使用尖括号< >,编译器会到系统路径下查找头文件;
而使用双引号” ",编译器首先在当前目录下查找头文件,如果没有找到,再到系统路径下查找。

也就是说,使用双引号比使用尖括号多了一个查找路径,它的功能更为强大。
在头文件中定义定义函数和全局变量」这种认知是原则性的错误!不管是标准头文件,还是自定义头文件,都只能包含变量和函数的声明,不能包含定义,否则在多次引入时会引起重复定义错误。对 #define 用法的几点说明
1.1) 宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单粗暴的替换。字符串中可以含任何字符,它可以是常数、表达式、if 语句、函数等,预处理程序对它不作任何检查,如有错误,只能在编译已被宏展开后的源程序时发现。

  1. 宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起替换。

  2. 宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用#undef命令。#define PI 3.14159

3.int main(){
4. // Code
5. return 0;
6.}
7.
8.#undef PI
9.
10.void func(){
11. // Code
12.}
表示 PI 只在 main() 函数中有效,在 func() 中无效。

  1. 代码中的宏名如果被引号包围,那么预处理程序不对其作宏代替,例如:
    1.#include <stdio.h>
    2.#define OK 100
    3.int main(){
  1. printf(“OK\n”);
  2. return 0;
    6.}
    运行结果:
    OK

该例中定义宏名 OK 表示 100,但在 printf 语句中 OK 被引号括起来,因此不作宏替换,而作为字符串处理。

  1. 宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名,在宏展开时由预处理程序层层代换。例如:
    #define PI 3.1415926
    #define S PIyy /* PI是已定义的宏名*/
    对语句:
    printf("%f", S);
    在宏代换后变为:
    printf("%f", 3.1415926yy);

  2. 习惯上宏名用大写字母表示,以便于与变量区别。但也允许用小写字母。

  3. 可用宏定义表示数据类型,使书写方便。例如:
    #define UINT unsigned int
    在程序中可用 UINT 作变量说明:
    UINT a, b;
    应注意用宏定义表示数据类型和用 typedef 定义数据说明符的区别。宏定义只是简单的字符串替换,由预处理器来处理;而 typedef 是在编译阶段由编译器处理的,它并不是简单的字符串替换,而给原有的数据类型起一个新的名字,将它作为一种新的数据类型。

请看下面的例子:
#define PIN1 int *
typedef int *PIN2; //也可以写作typedef int (*PIN2);
从形式上看这两者相似, 但在实际使用中却不相同。

下面用 PIN1,PIN2 说明变量时就可以看出它们的区别:
PIN1 a, b;
在宏代换后变成:
int * a, b;
表示 a 是指向整型的指针变量,而 b 是整型变量。然而:
PIN2 a,b;
表示 a、b 都是指向整型的指针变量。因为 PIN2 是一个新的、完整的数据类型。由这个例子可见,宏定义虽然也可表示数据类型, 但毕竟只是简单的字符串替换。在使用时要格外小心,以避出错。
ANSI C 规定了以下几个预定义宏,它们在各个编译器下都可以使用:
__LINE__:表示当前源代码的行号;
__FILE__:表示当前源文件的名称;
__DATE__:表示当前的编译日期;
__TIME__:表示当前的编译时间;
__STDC__:当要求程序严格遵循ANSI C标准时该标识被赋值为1;
__cplusplus:当编写C++程序时该标识符被定义。

预定义宏演示:
1.#include <stdio.h>
2.#include <stdlib.h>
3.
4.int main() {
5. printf(“Date : %s\n”, DATE);
6. printf(“Time : %s\n”, TIME);
7. printf(“File : %s\n”, FILE);
8. printf(“Line : %d\n”, LINE);
9.
10. system(“pause”);
11. return 0;
12.}
VS下的输出结果:
Date : Mar  6 2016
Time : 11:47:15
File : main.c
Line : 8

C-Free 5.0 下的输出结果:
Date : Mar  6 2016
Time : 12:12:59
File : C:\Users\mozhiyan\Desktop\demo.c
Line : 8
< 宏参数的字符串化和宏参数的连接C语言条件编译 >
所有教程
计算机中的数据是以字节(Byte)为单位存储的,每个字节都有不同的地址。现代 CPU 的位数(可以理解为一次能处理的数据的位数)都超过了 8 位(一个字节),PC机、服务器的 CPU 基本都是 64 位的,嵌入式系统或单片机系统仍然在使用 32 位和 16 位的 CPU。

对于一次能处理多个字节的CPU,必然存在着如何安排多个字节的问题,也就是大端和小端模式。以 int 类型的 0x12345678 为例,它占用 4 个字节,如果是小端模式(Little-endian),那么在内存中的分布情况为(假设从地址 0x 4000 开始存放):
内存地址 0x4000 0x4001 0x4002 0x4003
存放内容 0x78 0x56 0x34 0x12

如果是大端模式(Big-endian),那么分布情况正好相反:
内存地址 0x4000 0x4001 0x4002 0x4003
存放内容 0x12 0x34 0x56 0x78

我们的 PC 机上使用的是 X86 结构的 CPU,它是小端模式;51 单片机是大端模式;很多 ARM、DSP 也是小端模式(部分 ARM 处理器还可以由硬件来选择是大端模式还是小端模式)。
这就是加密的关键技术:
通过一次异或运算,生成密文,密文没有可读性,与原文风马牛不相及,这就是加密;
密文再经过一次异或运算,就会还原成原文,这就是解密的过程;
加密和解密需要相同的密钥,如果密钥不对,是无法成功解密的。

上面的加密算法称为对称加密算法,加密和解密使用同一个密钥。

如果加密和解密的密钥不同,则称为非对称加密算法。在非对称算法中,加密的密钥称为公钥,解密的密钥称为私钥,只知道公钥是无法解密的,还必须知道私钥。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: