2018-06-05 20:44:24 +08:00
|
|
|
|
# 2.1 基础语法
|
|
|
|
|
|
|
|
|
|
## 2.1.1 关键字
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
|
|
|
|
以下单词或字符在C语言中有特殊含义,称作关键字:
|
|
|
|
|
|
2018-06-05 20:44:24 +08:00
|
|
|
|
* include
|
|
|
|
|
* define
|
|
|
|
|
* ifdef
|
|
|
|
|
* ifndef
|
|
|
|
|
* endif
|
|
|
|
|
* extern
|
|
|
|
|
* typedef
|
|
|
|
|
* static
|
|
|
|
|
* const
|
|
|
|
|
* struct
|
|
|
|
|
* union
|
|
|
|
|
* void
|
|
|
|
|
* signed
|
|
|
|
|
* unsigned
|
|
|
|
|
* char
|
|
|
|
|
* short
|
|
|
|
|
* int
|
|
|
|
|
* long
|
|
|
|
|
* float
|
|
|
|
|
* double
|
|
|
|
|
* if
|
|
|
|
|
* else
|
|
|
|
|
* for
|
|
|
|
|
* do
|
|
|
|
|
* while
|
|
|
|
|
* break
|
|
|
|
|
* continue
|
|
|
|
|
* goto
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
以上关键字的作用将在后续章节讲解。
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-06-05 20:44:24 +08:00
|
|
|
|
## 2.1.2 特殊符号
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
|
|
|
|
C语言中常会用到以下符号:
|
|
|
|
|
|
2018-06-05 20:44:24 +08:00
|
|
|
|
* 赋值运算:=、+=、-=、*=、/=、%=、&=、|=
|
|
|
|
|
* 算数运算:+、-、*、/、%、++、--
|
|
|
|
|
* 比较运算符:==、>=、<=、!=、>、<
|
|
|
|
|
* 逻辑运算:&&、||、!
|
|
|
|
|
* 位运算:&、|、~、^、>>、<<
|
|
|
|
|
* 指针运算:*、&
|
|
|
|
|
* 其他:;、#、{、}、[、]、0x、0b、//、/\*、\*/
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
以上符号中的运算符将在学习数据类型之后进行说明。
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-06-05 20:44:24 +08:00
|
|
|
|
## 2.1.3 注释
|
2018-03-17 00:28:57 +08:00
|
|
|
|
|
|
|
|
|
注释是一些说明性的文字,他并不影响程序的逻辑、执行和运算。仅仅是帮助编程开发人员更好阅读和理解代码。
|
2018-07-11 00:27:43 +08:00
|
|
|
|
|
|
|
|
|
C语言有行注释和块注释,下面是几个行注释:
|
2018-03-17 00:28:57 +08:00
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
// 这是一个行注释。
|
|
|
|
|
// This is a line comment.
|
|
|
|
|
```
|
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
下面是一个块注释:
|
2018-03-17 00:28:57 +08:00
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
/*
|
|
|
|
|
* @author: lion chen cy187lion@sina.com
|
|
|
|
|
* @brief: 这是一个Doxygen风格的块注释。
|
|
|
|
|
*/
|
|
|
|
|
```
|
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
C语言的编码和注释有很多种风格,你会发现每种风格有各自的优缺点。你可以尝试多种风格,但成熟的软件系统会采用统一的风格,这样的要求经常由《编码规范》来约定。本教程主要使用 **Doxygen** 风格的注释。
|
2018-06-05 20:44:24 +08:00
|
|
|
|
|
|
|
|
|
## 2.1.4 字面常量
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
常量可以理解为固定不变的量,是与变量相对的概念。常量一经申明变不允许再发生改变。以下均是常量:
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-06-05 20:44:24 +08:00
|
|
|
|
* 16、0xFA90、0b1010
|
|
|
|
|
* 25.1
|
|
|
|
|
* "Have Fun!"、'x'、"15996699996"、"2\*3.1415926535898\*r"
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
常量的不变性是指如下赋值语句都是错误的:
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-06-05 20:44:24 +08:00
|
|
|
|
* "var"="Have Fun!"
|
|
|
|
|
* "var"=1024
|
|
|
|
|
* 16=0x16
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
而下列写法是可以编译通过的:
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-06-05 20:44:24 +08:00
|
|
|
|
* "var"=="Have Fun!"
|
|
|
|
|
* "var"!=1024
|
|
|
|
|
* 16<0x16
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
因为以上并非赋值语句,而是比较语句。通过英文双引号表达的是字符串变量,他们可以是一串字符。使用英文单引号表达的是字符常量,他只可以包含一个字符。而类似于16、25.1这种的是数字量。数字量分为整数和浮点数,他们有多种方式去表达方式。
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-06-05 20:44:24 +08:00
|
|
|
|
## 2.1.5 整数的表达方式
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
除了常用的十进制方式以外,在C语言甚至其他语言中,还经常用到二进制和十六进制数。
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-06-05 20:44:24 +08:00
|
|
|
|
* 0b 开头的表达二进制数,仅使用0、1表达
|
|
|
|
|
* 0x 开头的表达十六进制数,除0~9外,使用A~F来表达10~15
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
二进制、十进制、十六进制间通过 8421BCD 码进行转换。
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-06-05 20:44:24 +08:00
|
|
|
|
* 二进制:0b 1010
|
|
|
|
|
* 十进制:1\*2^3+0\*2^2+1\*2^1+0\*2^0
|
|
|
|
|
* 也就是:1\*8+0\*4+1\*2+0\*1
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
且每 4 位二进制数可表示一位十六进制数:
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-06-05 20:44:24 +08:00
|
|
|
|
* 二进制:0b 1010 0101
|
|
|
|
|
* 十六进制:0xA5
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-06-05 20:44:24 +08:00
|
|
|
|
## 2.1.6 变量
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
在C语言中,可以使用字符来表示数字量或字符串等。就好像数学里用x表示一些数那样。这样的量,可以被反复修改,被称作变量。变量只可以使用英文字符或下划线“_”开头,可包含0~9的数字,不可包含其他字符。以下是一些变量:
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-06-05 20:44:24 +08:00
|
|
|
|
* int aint
|
|
|
|
|
* unsigned long _along
|
|
|
|
|
* double flt0, flt1, flt2
|
|
|
|
|
* void* pointer
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
|
|
|
|
变量需要被定义才能够使用,在定义变量的时候,在变量前面的用于描述变量的C关键字表示了变量的类型和作用范围。
|
2018-07-11 00:27:43 +08:00
|
|
|
|
|
2018-03-16 23:51:20 +08:00
|
|
|
|
例如 static 表示变量是静态的,而 short 表示了有符号的32位整数。
|
2018-07-11 00:27:43 +08:00
|
|
|
|
|
|
|
|
|
所谓有符号、无符号,即变量所表示的数是否包含负数。
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-06-05 20:44:24 +08:00
|
|
|
|
* unsigned char: 0~255
|
|
|
|
|
* signed char:-128~127
|
|
|
|
|
* char = signed char
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-07-21 22:28:38 +08:00
|
|
|
|
char是8位数,可表示2^8=256个数,无符号的char从0开始,取256个整数,最大是255。当表示有符号数时,则用128个数表示-1~-128,另一半表示0~127。
|
2018-07-11 00:27:43 +08:00
|
|
|
|
|
|
|
|
|
其他类型的位数为:
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-06-05 20:44:24 +08:00
|
|
|
|
* short 16位
|
|
|
|
|
* int 32位
|
|
|
|
|
* long 与总线位宽有关,不低于32位
|
|
|
|
|
* long long 64位
|
|
|
|
|
* float 32位浮点数,非常不精确
|
|
|
|
|
* double 64位浮点数,精确
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
另一种变量是如下定义的:
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-03-17 00:28:57 +08:00
|
|
|
|
```cpp
|
|
|
|
|
int* pointer;
|
|
|
|
|
```
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-03-21 23:37:47 +08:00
|
|
|
|
这种变量被称作指针,或指针变量,后续会进行详细说明,并且我们会不断的提到它。
|
2018-07-11 00:27:43 +08:00
|
|
|
|
|
|
|
|
|
之前我们说变量的值可以被反复修改,也就是可以这样做:
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-03-17 00:28:57 +08:00
|
|
|
|
```cpp
|
|
|
|
|
int aint;
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-03-17 00:28:57 +08:00
|
|
|
|
aint = -56; // 初始化赋值。
|
|
|
|
|
aint += 3;
|
|
|
|
|
aint ++
|
|
|
|
|
```
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
这种做法叫做变量赋值。有时候我们需要在定义变量的时候就为变量赋值,可以这样做:
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-03-17 00:28:57 +08:00
|
|
|
|
```cpp
|
|
|
|
|
double aflt = 3.14;
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
变量的第一次赋值被称作初始化。注意,如果一个变量没有经过初始化就被使用了,那是很危险的,尤其是没有初始化的指针变量。
|
2018-07-11 00:27:43 +08:00
|
|
|
|
|
|
|
|
|
通常,在定义一个变量的同时,我们就声明了他,但是这个变量通常只在当前源文件中可见,而一个软件项目会包含很多源文件。如果我们想在另一个源文件中使用这个变量,就需要声明他。例如,在 a.c 这个源文件中定义和初始化变量:
|
2018-03-17 00:28:57 +08:00
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
/**
|
|
|
|
|
* @file: a.c
|
|
|
|
|
*/
|
|
|
|
|
short ashort=16;
|
|
|
|
|
```
|
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
然后我们在 b.c 文件中声明并使用他:
|
2018-03-17 00:28:57 +08:00
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
/**
|
|
|
|
|
* @file: b.c
|
|
|
|
|
*/
|
|
|
|
|
extern short ashort;
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-03-17 00:28:57 +08:00
|
|
|
|
ashort++;
|
|
|
|
|
```
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
经常会遇到需要将一种类型变量赋值给另一种变量的情况,在赋值的过程中,将发生类型转换。如果将位数少的变量赋值给位数多的变量,这个转换过程将会很自然。
|
2018-03-21 23:37:47 +08:00
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
char a = 25;
|
|
|
|
|
long b = a; // b=25.
|
|
|
|
|
```
|
2018-06-05 20:44:24 +08:00
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
但如果反过来,将位数多的变量赋值给位数少的,将会截取低位的部分数据进行赋值。
|
2018-03-21 23:37:47 +08:00
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
short a = 0xAA55;
|
|
|
|
|
char b = a; // b=0x55.
|
|
|
|
|
```
|
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
事实上,在进行类型转换时,如果按照上面的写法,将会产生编译警告。正确的做法是显式的明确指出这里要进行类型转换。这叫做类型强制转换。
|
2018-03-21 23:37:47 +08:00
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
short a = 0xAA55;
|
|
|
|
|
char b = (char)a; // 强制转换成char型.
|
|
|
|
|
```
|
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
在进行上述类型转换时,并没有改变a的类型,只是赋值了一份a的值,然后进行扩展或裁剪,再将结果赋值给b。而a本身的值和类型并未发生任何变化。
|
2018-03-21 23:37:47 +08:00
|
|
|
|
|
2018-06-05 20:44:24 +08:00
|
|
|
|
## 2.1.7 符号常量
|
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
有时候我们希望用一个符号来代替某个字面常量,这看起来很像一个不允许改变值的变量,我们用 const 关键字来修饰它,使之成为符号常量。例如:
|
2018-03-19 21:37:23 +08:00
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
const long cnvar0=666;
|
|
|
|
|
const char cnvar1='C';
|
|
|
|
|
```
|
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
一个符号常量只能在定义时被初始化,并且不能够被再次赋值。例如下面做法是错的:
|
2018-03-19 21:37:23 +08:00
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
const int cnint = 256;
|
|
|
|
|
cnint++;
|
|
|
|
|
```
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-06-05 20:44:24 +08:00
|
|
|
|
## 2.1.8 运算符
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
c语言运算符主要包括赋值运算、算数运算、逻辑运算、位运算、比较运算等。运算符主要涉及到优先级,结合性以及前加加和后加加的问题。例如:
|
2018-03-19 21:37:23 +08:00
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
int val=5, mask=0b0100;
|
|
|
|
|
printf("val=%d.\n", val++); // val=5.
|
|
|
|
|
printf("val=%d.\n", ++val); // val=7.
|
|
|
|
|
|
|
|
|
|
if(val<=10 && val>=5)
|
|
|
|
|
{
|
|
|
|
|
printf("True\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val &= mask; // val=4.
|
|
|
|
|
```
|
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
TODO: 有一个非常常用的特殊运算符,即sizeof运算符。
|
2018-03-20 11:11:43 +08:00
|
|
|
|
|
2018-06-05 20:44:24 +08:00
|
|
|
|
## 2.1.9 宏
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
通过使用宏,可以指导c编译器做一些特别的工作。例如使用define关键字来做一些替代的工作:
|
2018-06-05 20:44:24 +08:00
|
|
|
|
|
2018-03-19 21:37:23 +08:00
|
|
|
|
```cpp
|
|
|
|
|
#define BASE_ADDR (0x25)
|
|
|
|
|
#define REG1_OFFSET (0x01)
|
|
|
|
|
#define REG2_OFFSET (0x02)
|
|
|
|
|
|
2018-03-21 23:37:47 +08:00
|
|
|
|
printf("Register 1 addr=0x%x.\n", BASE_ADDR+REG1_OFFSET); // addr=0x25+0x01
|
|
|
|
|
printf("Register 2 addr=0x%x.\n", BASE_ADDR+REG2_OFFSET); // addr=0x25+0x02
|
|
|
|
|
```
|
2018-06-05 20:44:24 +08:00
|
|
|
|
|
2018-03-21 23:37:47 +08:00
|
|
|
|
这称作宏替换。宏替换可以提高程序的可移植性,例如上述例子中,只要修改 #define BASE_ADDR (0x25) 一处便可以修改所有寄存器的地址。
|
2018-07-11 00:27:43 +08:00
|
|
|
|
|
|
|
|
|
注意,定义宏的时候一般不加分号,这是因为在进行宏展开时,会将宏名替换为后边的全部,如果有多余符号存在,则会产生如下问题:
|
2018-03-21 23:37:47 +08:00
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
#define BASE_ADDR 0x25;
|
|
|
|
|
#define REG1_OFFSET 0x01
|
|
|
|
|
#define REG1_ADDR (BASE_ADDR+REG1_OFFSET) // 替换后 REG1_ADDR 为 0x25;+0x01,而这不是一条有效的c语言语句.
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
括号也在宏定义中担当着重要角色,我们看下面的例子:
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
#define BASE_ADDR 0x25
|
|
|
|
|
#define REG1_OFFSET 0x01
|
|
|
|
|
#define REG1_ADDR_0 (BASE_ADDR+REG1_OFFSET)
|
|
|
|
|
#define REG1_ADDR_1 BASE_ADDR+REG1_OFFSET
|
|
|
|
|
|
|
|
|
|
int a = REG1_ADDR_0*2; // a=(0x25+0x01)*2=76
|
|
|
|
|
int b = REG1_ADDR_1*2; // b=0x25+0x01*2=39
|
|
|
|
|
```
|
|
|
|
|
|
2018-07-10 22:32:00 +08:00
|
|
|
|
另外,我们很少使用小写英文字母作为宏的名称。
|
|
|
|
|
|
|
|
|
|
很多参考中将宏理解为编译期间的文本替换,编译器会将宏展开,这与程序执行期间发生的事情有本质差别。
|
2018-03-21 23:37:47 +08:00
|
|
|
|
|
2018-06-05 20:44:24 +08:00
|
|
|
|
## 2.1.10 typedef
|
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
typedef也是一个能够有效提高程序可维护性的关键字。它允许你声明一个自定义的类型。例如:
|
2018-03-21 23:37:47 +08:00
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
typedef unsigned short U16
|
|
|
|
|
|
|
|
|
|
U16 a = 0x55AA; // 等于 unsigned short a.
|
|
|
|
|
```
|
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
需要注意的是,typedef与宏替换很像,很容易将二者混淆。关键的区别在于,宏替换仅仅是简单的字面替换,而typedef仅用于声明某个类型。比如:
|
2018-03-21 23:37:47 +08:00
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
#define MY_STRUCT struct A{...}
|
|
|
|
|
|
|
|
|
|
MY_STRUCT x;
|
|
|
|
|
MY_STRUCT y;
|
|
|
|
|
```
|
2018-06-05 20:44:24 +08:00
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
这样的代码会产生奇异,由于宏的字面替换,最终声明了两个一样的结构体类型,并分别用它们去定义x和y。编译器很难区分x和y的类型,它们看起来相同,却又不同。这样的问题我们用typedef来解决:
|
2018-03-21 23:37:47 +08:00
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
typedef struct A{} MY_STRUCT;
|
|
|
|
|
|
|
|
|
|
MY_STRUCT x;
|
|
|
|
|
MY_STRUCT y;
|
2018-03-19 21:37:23 +08:00
|
|
|
|
```
|
|
|
|
|
|
2018-03-21 23:37:47 +08:00
|
|
|
|
由于typedef不会产生字面替换,仅仅是声明了新的类型,因此,此处与定义两个普通变量没有差别,并且不存在上述的问题。
|
2018-07-11 00:27:43 +08:00
|
|
|
|
|
|
|
|
|
关于结构体这一复合类型,将在后续章节详细说明。
|
2018-03-21 23:37:47 +08:00
|
|
|
|
|
2018-06-05 20:44:24 +08:00
|
|
|
|
## 2.1.11 指针
|
2018-03-21 23:37:47 +08:00
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
前文提及的变量,无论是哪种类型的,在程序运行起来之后,都会占用一定的内存空间。所占用具体空间的大小,与其类型有关。而每个变量所在的位置,便是内存地址。
|
|
|
|
|
|
|
|
|
|
我们可以通过变量名来获取变量,此外,也可以通过变量地址来获取变量。这是通过指针操作来实现的:
|
2018-03-21 23:37:47 +08:00
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
// 假设系统是从低位开始寻址.
|
|
|
|
|
unsigned int a = 0x11223344;
|
|
|
|
|
char* pa = (char*)&a; // 通过 & 符号取变量 a 的地址. 指针 pa 的值便是变量 a 的首地址.
|
|
|
|
|
|
|
|
|
|
printf("%d.\n", *pa); // 0x44.
|
|
|
|
|
pa++;
|
|
|
|
|
|
|
|
|
|
printf("%d.\n", *pa); // 0x33.
|
|
|
|
|
pa++;
|
|
|
|
|
|
|
|
|
|
printf("%d.\n", *pa); // 0x22.
|
|
|
|
|
pa++;
|
|
|
|
|
|
|
|
|
|
printf("%d.\n", *pa); // 0x11.
|
|
|
|
|
pa++;
|
|
|
|
|
```
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-03-21 23:37:47 +08:00
|
|
|
|
在变量类型后面加\*号,便是定义指针类型变量。根据类型的不同,指针的类型也不同,有long\*,short\*等。
|
2018-07-11 00:27:43 +08:00
|
|
|
|
|
2018-03-21 23:37:47 +08:00
|
|
|
|
可以通过\&符号来取地址,由于指针代表了变量地址,因此,&取出的地址可以赋值给指针变量。
|
2018-07-11 00:27:43 +08:00
|
|
|
|
|
2018-03-21 23:37:47 +08:00
|
|
|
|
我们说指针变量,意味着指针本身也是一个变量,是变量就有存储空间和存储地址。存储空间便是类型的长度,指针代表了内存地址,因此其长度总是与内存总线宽度一致。如32位机则指针变量长度为4,64位机为8。
|
2018-07-11 00:27:43 +08:00
|
|
|
|
|
|
|
|
|
指针也有存储地址,意味着可以通过另一个指针来索引指针变量,另一个指针便成为了二级指针。
|
2018-03-16 23:51:20 +08:00
|
|
|
|
|
2018-06-05 20:44:24 +08:00
|
|
|
|
## 2.1.12 语句
|
2018-03-22 11:05:23 +08:00
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
只有词法没有句法就无法构成完整的语言。因此要学习c语言的语句。
|
|
|
|
|
|
|
|
|
|
与自然语言的一个区别在于,c语言通常以英文分号作为一句话的结束。
|
2018-03-22 11:05:23 +08:00
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
int a; // 这句话说完了.
|
|
|
|
|
a = 16; // 这句话也说完了.
|
|
|
|
|
a++ // 这句话没说完...
|
|
|
|
|
```
|
|
|
|
|
|
2018-03-22 11:27:04 +08:00
|
|
|
|
c语言有赋值语句,判断语句,条件语句,循环语句,分支语句等。这些名称与语句的功能相对应,因此很好区别。
|
2018-07-11 00:27:43 +08:00
|
|
|
|
|
2018-03-22 11:27:04 +08:00
|
|
|
|
我们会把一些语句用大括号包裹起来,形成语句块。
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
{
|
|
|
|
|
int a, b, c;
|
|
|
|
|
a = 60;
|
|
|
|
|
b = 3;
|
|
|
|
|
c = a*b;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
语句块非常有用,经常出现在条件语句,循环语句或者分支语句的后面。之后讲到函数时,你会注意到,函数体本身就是一个语句块。
|
|
|
|
|
|
2018-06-05 20:44:24 +08:00
|
|
|
|
## 2.1.13 c/c++文件
|
2018-03-22 17:46:56 +08:00
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
c/c++ 语言程序被写入到文件中,这些文件有特殊的扩展名,C 文件扩展名是“.c”和“.h”;C++ 文件扩展名为“.cpp”和“.h”。
|
|
|
|
|
|
|
|
|
|
其中 .h 文件称作头文件,其余都是源码文件。源文件是我们编写程序实体的主要文件,变量和函数的定义都在源文件中。
|
|
|
|
|
|
|
|
|
|
项目很容易产生多个源文件,所以也常会出现在源文件 a.c 中需要访问 b.c 中的变量或者函数的情况。
|
|
|
|
|
|
|
|
|
|
这时候通过以下方法来实现。
|
2018-03-22 18:06:16 +08:00
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
#include <b.h>
|
2018-03-22 18:41:52 +08:00
|
|
|
|
/**
|
|
|
|
|
* 或者 #include "b.h"
|
|
|
|
|
* 主要差别是搜索头文件的路径不同。
|
|
|
|
|
* 可以指定头文件的绝对或相对路径如下:
|
|
|
|
|
* #include "/path/b.h"
|
|
|
|
|
*/
|
2018-03-22 18:06:16 +08:00
|
|
|
|
```
|
|
|
|
|
|
2018-07-11 00:27:43 +08:00
|
|
|
|
其中,b.h 包含了 b.c 中可以被外界访问到的变量或函数等资源的声明。
|
|
|
|
|
|
|
|
|
|
这里体现出了定义和声明的区别。定义一个变量或者函数,会为它分配实际的存储空间,还要实现函数的具体功能。而声明只是在告诉大家:“嘿,这里有个变量 a,那里有个函数 void fun1(void)!”。
|
|
|
|
|
|
2018-03-22 18:06:16 +08:00
|
|
|
|
所以,声明不会分配存储空间,也不需要实现函数的具体功能。
|
2018-07-11 00:27:43 +08:00
|
|
|
|
|
|
|
|
|
头文件中包含了很多的声明,define 或者 typedef,它告诉外界存在这些资源。如果你想在源码中使用这些资源,你只要 include 这个头文件即可。
|
|
|
|
|
|
|
|
|
|
一旦包含了某个头文件,便可以使用这个头文件中声明的内容。需要注意的是,头文件不但可以被源文件包含,被头文件包含,还可以被多次包含,这就有可能使得这个头文件被重复包含。重复包含是不允许的,可以使用 ifdef 等宏避免。
|
2018-03-22 18:41:52 +08:00
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
/**
|
|
|
|
|
* @file: a.h
|
|
|
|
|
*/
|
|
|
|
|
#ifndef A_H
|
|
|
|
|
#define A_H
|
|
|
|
|
|
|
|
|
|
extern int a;
|
|
|
|
|
void func1(int x);
|
|
|
|
|
|
|
|
|
|
#endif // A_H
|
|
|
|
|
```
|