0%

c语言核心

c 程序由一系列文本文件组成,以文件为单位,经过预处理、编译链接生成可执行程序,由系统调用执行

c 语言中的实体

c 语言有如下实体:对象、函数、标签( struct 、 union 或枚举)、结构体或联合体成员、枚举常量、 typedef 类型别名、标号名、宏名、宏形参名。

实体是由声明所引入的,使其与名字对应起来,并定义了其属性。为一个实体定义其使用所需的所有性质的声明是一个定义。

什么是对象?
一个对象是执行环境数据存储的一个区域,其内容可以表示值(值是对象的内容转译为特定类型时的含义)。
简单说,就是程序执行时,除了代码段,其他堆栈段、bss 段、数据段里面的都是对象

所以,以下实体都不是对象:值,引用,函数,枚举项

原因是以上这些实体只是在编译阶段使用,说白了就是写给编译器看的,函数直接生成进代码段,值会以二进制嵌入到代码中

如何判断?
对象类型:所有不是函数类型的类型

标识符声明和定义

声明是向程序中引入一个名字
定义是足以使用该名字所标识的实体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 对于对象,分配存储的声明为定义
extern int a; // 声明a,未分配存储
int a; // 定义a

// 对于函数,包含函数体的声明为定义
void b(); // 声明函数b
void b(){;}; // 定义函数b

// 对于结构体,包含结构体成员列表的为定义
struct c; // 声明结构体c
struct c {int c;}; // 定义结构体c

// 所有的typedef 都是定义,定义的名称属于通常命名空间
typedef int A[]; // A 是 int[]
A a = {1, 2}; // a 的类型是 int[2]
typedef struct { double hi, lo; } range;
range z, *zp;

include 预处理指令,会将包含的头文件插入到当前行
所以,头文件如果定义了全局变量,如果重复包含此头文件,会出现重定义,可使用#pragma once 或者宏定义避免一个头文件被多次包含
但,即使每个编译单元编译通过,链接时也会出现重复定义,原因是全局变量有外部链接
所以头文件可以放所有声明和内部链接的定义

标识符查找和命名空间

c 语言有 4 个独立的命名空间:
标号命名空间:跟在 goto 后的标识符,在标号命名空间查找
标签命名空间:跟在 struct、union、enum 后的标识符,在标签命名空间查找
类型成员命名空间:跟在成员访问运算符后的标识符,在类型成员命名空间查找
通常命名空间:函数名称,对象名,以 typedef 声明的标识符,枚举常量,所有其他标识符

4 个作用域:
文件作用域:任何在代码块之外声明的标识符都具有文件作用域
块作用域:任何位于一对花括号声明的标识符具有块作用域
函数作用域:只适用于函数内部的标号
函数原型作用域:只适用于在函数原型中声明的参数

1
2
3
4
5
6
7
8
9
10
struct A {
int a; // 类型成员命名空间/文件作用域
enum { INT, FLOAT, STRING} B; // B属于标签命名空间文件作用域,INT属于通常命名空间,文件作用域
struct C {
char c;
};
}
// ABCD 都属于标签命名空间,文件作用域
union D d; // ok
void INT(); // false 通常命名空间文件作用域已经有INT标识符

当 c 程序遇到标识符时,会在当前作用域查找定位引入该标识符,

对象类型

每个对象有如下属性:
标识符属性

作用域:标识符在某一范围内可见
命名空间:同一作用域同名标识符可属于不同命名空间,指代不同对象
链接:跨作用域指代同一对象的能力
存储期:用于限定对象的生存期

链接分为:
无链接:只能在其所在的作用域使用,函数形参和所有非 extern 的块作用域对象
内部链接:当前翻译单元的任何作用域都可使用,所有 static 的函数和对象
外部链接:在其他翻译单元可以使用的标识符,文件作用域下的非 static 函数和对象、extern 对象

存储类指定符:指定对象和函数的存储期和链接
auto - 自动存储期与无链接
register - 自动存储期与无链接;不能取这种对象的地址
static - 静态存储期与内部链接(除非在块作用域)
extern - 静态存储期与外部链接(除非已声明带内部链接)
_Thread_local - 线程存储期

若不提供存储类指定符,则默认为:
对所有函数为 extern
对在文件作用域的对象为 extern
对在块作用域的对象为 auto

结论:默认情况下
文件作用域定义得对象和函数:都是外部链接

类型

对象、函数和表达式拥有称为类型的属性,它确定存储于对象或表达式求值所得的二进制值的转译方式。
c 语言类型分为:

  1. void 类型
  2. 基本类型:char、int、long、double、float 等
  3. 派生类型:数组、结构体、联合体、函数、指针、原子类型
  4. 枚举类型

表达式

表达式是运算符及其运算数的序列,它指定一个运算
表达式求值可得到一个结果,分为:左值、右值和函数指代器

某些类型的常量值可常量或字面量,直接嵌入程序中

  1. 整数常量
  2. 浮点常量
  3. 字符常量
  4. 字符串字面量:
  5. 复合字面量:结构体、联合体或数组的无名对象,( type ) { initializer-list }
1
2
3
int *p = (int[]){2, 4}; // 创建一个无名的 int[2] 类型静态存储数组
// 初始数组为值 {2, 4}
// 创建指向数组首元素的指针 p

声明和定义

声明是引入一个标识符到程序中,并指定其属性
格式:specifiers-and-qualifiers declarators-and-initializers ;
specifiers-and-qualifiers:空白符分割,类型指定符,类型限定符,存储类指定符,对齐指定符,函数指定符
declarators-and-initializers:逗号分割,声明器和初始化器
声明器格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1. 标识符:identifier
int i; // i是标识符

2. 指针声明器格式:* qualifiers(可选) declarator
int *p; // p是指向int类型的指针
int (*p)(int); // 函数指针
int (*p)[5]; //数组指针

3. 数组声明器格式:
noptr-declarator [ static(可选) qualifiers(可选) expression ]
noptr-declarator [ qualifiers(可选) * ]
int n = 1;
int a[5]; // 常量大小的数组
int b[n]; // 变长度数组(VLA)
int c[]; // 位置大小数组

4. noptr-declarator ( parameters-or-identifiers ):函数声明器
int max(int a, int b);

5. ( declarator ):任何可放入括号中的声明器,引入指向数组或指向函数指针时要求这么做

初始化器格式

1
2
1. = expression
2. = { initializer-list }

三种显示初始化器:
标量类型初始化:包括整型类型,浮点类型,指针类型等
数组初始化:2 种形式

1
2
3
4
5
6
7
8
9
1. = string_literal 字符串字面量,初始化字符数组
int c[] = "abc";
2. = { expression , ... } 被初始化数组成员列表
int y[5] = {1,2,3}; // y 拥有类型 int[5] 并保有 1,2,3,0,0
int y[4][3] = { // 4 个 3 个 int 的数组的数组( 4*3 矩阵)
{ 1 }, // 0 行初始化到 {1, 0, 0}
{ 0, 1 }, // 1 行初始化到 {0, 1, 0}
{ [2]=1 }, // 2 行初始化到 {0, 0, 1}
}; // 3 行初始化到 {0, 0, 0}

结构体及联合体类型初始化:

1
2
3
4
5
6
7
8
9
// . member 形式的单独成员指代器,和 [ index ] 形式的数组指代器。

// 初始化 w (二个结构体的数组)为
// { { {1,0,0}, 0}, { {2,0,0}, 0} }
struct {int a[3], b;} w[] = {[0].a = {1}, [1].a[0] = 2};

union { int x; char c[4]; }
u = {1}, // 令 u.x 活跃,拥有值 1
u2 = { .c={'\1'} }; // 令 u2.c 活跃,拥有值 {'\1','\0','\0','\0'}