C语言关键字 [1]

==C语言有32个关键字==

  • auto:定义自动变量,主要是声明变量的生存周期
  • break, continue : break 语句在遇到最内层循环时立即终止。还用于终止 switch 语句。
  • case, switch, default:使用 switch 和 case 语句声明一个switch分支
  • char:用于声明character 类型的变量
  • const:声明常量
  • do…while
  • double: double-precision 浮点数变量类型
  • float:single-precision 浮点数的变量类型
  • if, else:声明if/else 条件判断
  • enum:用于声明枚举类型
  • extern:关键字声明变量或函数在其声明的文件之外具有外部链接。
  • for:C 语言的三种循环之一,for循环
  • goto: 用于将程序的控制权转移到指定的标签
  • int:声明 integer 类型的变量
  • short, long, signed, unsigned:是类型修饰符,它们改变基本数据类型的含义以产生新类型。
    • short int: -32768 to 32767
    • long int: -2147483648 to 214743648
    • signed int: -32768 to 32767
    • unsigned int: 0 to 65535
  • return: 终止函数并返回值
  • sizeof:评估变量或常量的大小
  • register:创建比普通变量快得多的寄存器变量。
  • static:创建一个静态变量。静态变量的值持续到程序结束。
  • struct:用于声明结构体。结构体可以包含不同类型的变量。
  • typedef:用于将类型与标识符显式关联。
  • union:用于将不同类型的变量分组在一个名称下。
  • void:没有任何意义,函数修饰为没有返回值,参数修饰为没有参数
  • volatile:提醒编译器它后面所定义的变量随时都有可能改变

C语言控制语句

==C语言有9种控制语句== (control statements)

  • If..else
  • for
  • while
  • do..while
  • continue
  • break
  • switch
  • goto
  • return

C语言运算符 [2]

==C语言有45种运算符== (operator)

  • 算数运算符 (Arithmetic Operators) :+, -*/%
  • 赋值运算符 (Assignment Operators) :=+=-=*=/=%=
  • 关系运算符 (Relational Operators):==><!=>=<=
  • 逻辑运算符 (Logical Operators):&&||!
  • 位运算符 (Bitwise Operators):&|^~<<>>
  • 逗号运算符 (Comma Operator):链接相关表达式 ,int a, c = 5, d;
  • sizeof运算符(sizeof operator):一元运算符,它返回数据的大小(常量、变量、数组、结构)
  • 杂项运算符():& 取址,* 取指针,?: 二元条件表达式

GCC编译四部曲 [3]

  • 预处理 (Preprocessing):在预处理步骤,将生成一个扩展名为 .i 的文件;使用命令 gcc -E file.c 操作
    • 头文件展开,不检查语法错误,将展开所有头(include)文件(任意)
    • 宏定义替换
    • 删除注释
    • 展开条件编译,根据条件来展开指令
  • 编译 (Compilation) :会生成一个扩展名为 .s 的文件,命令是:gcc -S file.c
    • 检查语法错误
    • 将文件翻译成汇编语言
  • 汇编 (Assembler):将汇编代码转换为纯二进制代码或机器代码(零和一)。此代码也称为目标代码;将生成一个带有 .o 扩展名的文件:gcc -c file.c
  • 链接 (Linker):链接是编译的最后一步。链接器将来自多个模块的所有目标代码合并为一个,如果使用了库也会引用。这个步骤也是包含前三个步骤的。gcc file.o -o hello.exe
    • 接收由汇编步骤生成的 .o 扩展名文件
    • 数据地址回填
    • 数据段合并
    • 库引入

变量

变量 (variables) 是用于存储数据的内存位置名称,可以改变的内容

变量的命名规则

  • 不能以数字开头
  • 由数字、字母,甚至是下划线 (_) 等特殊符号组成
  • 变量名不能是任何关键字
  • 变量名中不能有空格或空白
  • 变量名是==区分大小写的==

变量的数据类型

C 语言中数据类型主要包含以下类型

变量类型实际代表名称描述用途
charCharacter代表1bytes(8bit),是以单引号引起的字符通常以单个字母的形式使用X、r等,或 ASCII 字符集。
intInteger自然整数用来存储整数,如 4, 300, 8000 …
floatFloating- Point单精度浮点数表示实数值或小数值(7位小数),例如 20.8, 18.56 …
doubleDouble双精度浮点值比float类型要大 4bytes,允许15位小数
voidVoid表示没有类型。这种数据类型是为了用于修饰没有意义函数或变量,如函数用其修饰标识没有返回值,参数用其修饰表示没有参数。

变量声明与定义

  • 变量的定义 (Declaration):告诉编译器应为变量创建多少存储空间或者在哪里创建存储空间(借助于数据类型)
  • **变量声明 **(Definition):只声明不赋值的变量叫做变量定义, int a

定义与声明的区别:

  • 变量定义会开辟内存空间。变量声明不会开辟内存空间
  • 变量要想使用必须有定义
  • 声明指示编译器存在变量,而定义表示编译器为变量创建的存储位置和存储量

变量的分类

  • 全局变量 (global) :在块或函数之外声明的变量称为全局变量
  • 局部变量 (Local):在块或函数中声明的一种变量
  • 静态变量 (static):使用 static 关键字声明的变量。该变量在各种函数调用之间保留给定值
  • 自动变量 (auto):变量具有自动存储期,程序在进入该变量声明所在的块时变量存在,程序在退出该块时变量消失
  • 外部变量 (extren):能够在多个源文件中共享一个变量 extern int a=10;

变量的数据大小 [6]

C 编程语言有两种基本数据类型:基本与衍生

类型范围大小(以字节为单位)格式化符号
unsigned char0 ~ 2551%c
signed char/char-128 ~ +1271%c
unsigned int0 ~ 655352%u
signed int or int-32,768 ~ +327672%d
unsigned short int0~ 655352%hu
signed short int/short int-32,768 ~ +327672%hd
unsigned long int0 ~ +4,294,967,2954%lu
signed long int/long int-2,147,483,648 ~ 2,147,483,6474%ld
long long int-(2^63) to (2^63)-18%lld
unsigned long long int0 to 18,446,744,073,709,551,6158%llu
float [5]7位精度4%f
double [5]15位精度8%lf

变量类型

C语言中根据变量的声明周期和范围可以被分为两种类型 局部变量和全局变量与静态变量

局部变量

局部变量 (local variables) 被声明在函数内部,只要函数存在,它们就只存在于内存中,直到函数结束,局部变量就会消失!

例如创建一个局部变量 a,a在函数运行时被创建在stack段中,当函数foo() 结束,被释放,故下列代码编译错误。

c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <stdio.h>

void	foo(void)
{
	int	a;

	a = 10;
	printf("Foo function: Variable a = %d\n", a);
} // the variable 'a' ceases to exist in RAM here.

int	main(void)
{
	foo();
	printf("Main: Variable a = %d\n", a);
	// ERROR : main does not know any variable named 'a'!
	return (0);
}

Notes:函数的参数也是局部变量,如果需要外部更改,则通过指针方式传递进去

全局变量

全局变量 (global variables) 是指在函数外部声明的变量;全局变量随函数生命周期结束时消失,因为全局变量被存储在内存结构的data分段中,是属于二进制文件本身的。另外==默认情况下,未被赋值的全局变量会被初始化为 0==。

c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>

int	a; // Global variable initialized to 0 by default

void	foo(void)
{
	a = 42; // Global variable accessible without
		// having been declared in the function
	printf("Foo: a = %d\n", a); // a == 42
}

int	main(void)
{
	printf("Main: a = %d\n", a); // a == 0
	foo();
	printf("Main: a = %d\n", a); // a == 42
	a = 200;
	printf("Main: a = %d\n", a); // a == 200
	return (0);
}

Notes:局部变量的作用域高于全局变量,如果同名会被覆盖

全局变量的作用域

如果想在一个文件中使用另一个文件中定义的全局变量,需要使用关键字 ”extern“ 再次声明。这代表告诉编译器正在声明我们在程序文件的其他地方定义的变量。

例如下面代码中,main.c 中,使用 extern 关键字声明全局变量,表示我们在其他地方定义了这个变量。并做了 foo() 函数原型的声明。并在 foo.c 文件中,定义了全局变量 afoo() 函数

main.c

c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <stdio.h>

extern int	a; // 在其他文件内定义的全局变量

void foo(void);	// 定义在其他方面的函数,这种写法等同于extern void foo(void);

int	main(void)
{
	printf("Main: a = %d\n", a); // a == 100
	foo();
	printf("Main: a = %d\n", a); // a == 42
	a = 200;
	printf("Main: a = %d\n", a); // a == 200
	return (0);
}

void.c

c
1
2
3
4
5
6
7
8
9
#include <stdio.h>

int	a = 100; // 全局变量的定义

void foo(void)
{
	a = 42;
	printf("Foo: a = %d\n", a); // a == 42
}

输出结果为

c
1
2
3
4
Main: a = 100
Foo: a = 42
Main: a = 42
Main: a = 200

也可以使用头文件来定义,这种方式比上面的更好,示例只是说明全局变量

静态变量

静态变量是指使用关键字 “static" 修饰的变量,静态变量可以分为 静态全局变量静态局部变量 ,静态变量默认是全局的,因为他存储的地方是data区而不是堆,栈中。

静态变量有两点区分与全局变量:

  • 在函数内部定义的静态变量是这个函数的全局变量(第一个结束的括号)
  • 在函数外声明的静态变量仅在这个声明他的文件内有效。

下面代码会编译异常,因为b生命周期存在与for循环中

c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <string.h>
void test()
{
    static int a = 10;
    for(int i=0; i<10; i++)
    {
        static int b = 10;
        a++;
        b++;
    }

    printf("value a is %d\n",a);
    printf("value b is %d\n",b);
}

void main()
{
    test();
}
局部静态变量

局部静态变量不能说是真正的局部变量,因为其存储内存位置与局部变量不同,局部变量存储在堆,栈中,而静态变量存储在data中只是说会被限制在对应的作用域中。

下面代码说明了普通局部变量和静态局部变量的区别,由于存储位置不同,静态局部变量只是被访问限制在作用域中,而不会随函数结束释放掉。

c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>

void foo(void)
{
	int	a = 100;
	static int	b = 100;

	printf("a = %d, b = %d\n", a, b);
	a++;
	b++;
}

int	main(void)
{
	foo();
	foo();
	foo();
	foo();
	foo();
	return (0);
}

输出结果

c
1
2
3
4
5
a = 100, b = 100
a = 100, b = 101
a = 100, b = 102
a = 100, b = 103
a = 100, b = 104
全局静态变量

全局静态变量是声明在函数外面用static修饰的变量,与全局变量不同的是,静态全局变量访问域被限制在声明它们的文件中,无法从程序的另一个文件中访问它。

在全局变量部分,可以通过关键字 ”extern“ 来访问全局变量,如果此时声明一个静态变量 a ,那么通过跨文件的方式这时编译器会提示 ”undefined reference to ‘a’“。通常情况下使用这种场景被用于加速编译。

global VS local VS static

  • 作用域方面不同:局部变量作用域仅在 同一个 {},而静态变量和全局变量在为整个进程
  • 访问作用域不同:全局变量为进程共享,局部变量为函数运行时,静态全局变量为定义它的文件,静态全局变量为 同一个 {}
  • 存储位置不同,局部变量被存储与堆,栈中,而静态变量和全局变量被存储在data中

类型转换

隐式类型转换

隐式类型 (Implicit) 转换也称自动类型转换,这种类型的转换包含如下特点:

  • 编译器自动完成,无需用户干预触发
  • 当表达式中存在多种类型时触发,这是为了保证数据不被丢失
  • 所有的数据类型都将升级为该类型最大值
  • 转换的顺序为:bool -> char -> short int -> int -> unsigned int -> long -> unsigned -> long long -> float -> double -> long double
  • 该转换类型会存在一些问题,如符号消失,数据丢失等。
c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include<stdio.h>
int main()
{
    int x = 10;    // integer x
    char y = 'a';  // character c
  
    // y 被隐式转换为 char类型,a=97
    x = x + y;
     
    // 计算中,存在浮点数值,结果将被转换为float
    float z = x + 1.0;

    
    // 自动转换为long long
    int h = 2147483648;
    // int 到 short int值溢出将为23352减去int大小65536
    short int g = 88888 + x;
    printf("x = %d, z = %f\n", x, z);
    printf("g = %li, h = %lli\n", g, h);
    return 0;
}

显式类型转换

用户定义类型转换的过程称为显式类型转换 (Explicit)

c
1
(type) expression

显示转换示例

c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include<stdio.h>
  
int main()
{
    float x = 1.2;
    // 显示转换一个float为int
    int sum = (int) x + 1;
  
    printf("sum = %d", sum);
  
    return 0;
}

进制转换

十进制

十进制转二进制: 除2反向取余法

十进制转八进制:除8反向取余法

十进制转十六进制:除16反向取余法

例如:16进制转10进制

  • 将十进制数除以 16。将除法视为整数除法
  • 写下余数(十六进制)
  • 将结果再次除以 16。将除法视为整数除法
  • 重复步骤 2 和 3,直到结果为 0
  • 求出的十六进制值是从最后到第一个的余数的数字序列

427的16进制

  • 将数字除以 16,余数(小数部分乘16为余数),最终为1AB

    Decimal To Hexadecimal Conversion

八进制

8进制转10进制:从后向前,8的0次方,8的1次方,8的2次方…按照该顺序乘8的

  • 8进制75转10进制为:$56+5=61$
  • 8进制77655转10进制为:$7(8^4)+7(8^3)+6(8^2)+5(8^1)+5(8^0)=28672+3584+384+40+5=32685$

2进制转8进制:自右向左,每3位一组,按421码转换。高位不足三位补0

  • 1 010 111 010 110 二进制转八进制如下表,最后算出结果为12726

  • 421
    001
    010
    111
    010
    110

十六进制

16进制转10进制:从后向前依次展开,16的0次方,16的1次方,16的2次方…,每位相加,例如:

  • 0x1A = $16 + 10 = 26$
  • 15DE = $1(16^3)+5(16^2)+13(16^1)+14= 4096+1280+208+14=5598$

16进制转二进制:4位一组一次填充。例如 0X1A的二进制,即00011010如下

8421
1010
0001

二进制转16进制:自右向左,每4位一组,按8421码转换。高位不足三位补0

例如 0001 0011 1111的16进制为,如下表 1 3 F(15)

8421
0001
0011
1111

源码反码补码

源码 (*** true form****), 反码 (1‘s complement) [7], 补码 (2‘s complement) [8] 是操作系统中存储和计算数据的一种方式

任何数据都以二进制机器码存储与计算机中。对于的机器码,第一位是用来表示正负值的:0是正数,1是负数。故要表示 -2,对应的机器码是 10000010

机器码不可以直接通过权重展开计算,例如 10000010 为 $1(2^7) + 1(2^1) = 130$ 。因为第一位是1,所以是负数,接下来计算后一位的权重展开为 $-2$

  • 原码:机器码表示的值成为源码:如 43 = 00101011,-43 = 10101011

  • 反码:符号位不变,其余位取反:如 43 = 00101011,-43 = 11010100

  • 补码:符号位不变,counter code then LSB (least significant bit) + 1:如 43 = 00101011,-43 = 11010101

    • 1286432168421
      00101011
      11010100+1(如果需要进位则进一位)
      11010101

Note:二进制 10000000 为 -128

反码, 补码 是为了计算和存储正负数诞生的,正如 C语言的数据结构 中 有符号和没符号表示的数值位置不一样。

Reference

[1] keywords c language

[2] Arithmetic Operators

[3] four stages of compilation c

[4] offline installation visual studio

[5] difference float double

[6] data type in c

[7] 1‘s complement

[8] 2‘s complement