跳转至

Basis: Data Type and Function

约 511 个字 56 行代码 预计阅读时间 2 分钟

Chapter 1 Basis

常见数据类型

字面量即一个值:

  • 整型:123 表示十进制的 123;0123 表示八进制的 123,亦即十进制的 83;0x123 是十六进制的 123,亦即十进制的 291。

  • 字符型:

    • 一个字符类型相当于一个字节的整型,所以字符类型可以通过整型来表示:char c = 65
    • 引号里的反斜杠 \ 有转义的效果,比如 '\n' 表示一个控制字符,而反斜杠只有通过 \\ 才能表示出来。
    • 反斜杠后边可以接最多三个数字,并且此时使用八进制表示一个字节,且遇见 0~7 之外的数字就会阶数当前字节,比如 '\101' 表示 A,而 '08' 由于 8 超过了八进制的范围,这就是两个字符放在了一个单引号里边,是错误的用法,如果写成字符串,"\08" 就表示两个字符:一个空字符和一个 8
    • \x 后边接在 0-9A-F 内的字符,可以通过十六进制表示一个字符,不过没有长度限制,遇到范围外的字符就结束,比如 \x000041 也是一个字符。

字节分配:char 1byte,short 2byte,int 4byte,long 4byte,long long 8byte, float 4byte,double 8byte,pointer 4/8byte。

运算符优先级

  • 优先级最高的:(Left-to-Right)后缀运算符:后缀形式的递增递减、函数调用、数组下标、访问结构、复合字面量。
  • 优先级略低的:(Right-to-Left)单目运算符:前缀形式的递增递减、单目的正负、逻辑非和按位非、强制类型转换、解引用、取地址、sizeof、对齐。
  • 算数运算:(Left-to-Right)乘除取余、加减、移位——优先级递减。
  • 关系运算:(Left-to-Right)不等关系、相等关系。
  • 位运算:(Left-to-Right)按位与、异或、或。
  • 逻辑运算:(Left-to-Right)逻辑与、逻辑或。
  • 条件运算:(Right-to-Left)三目运算符 ?:
  • 赋值运算:(Right-to-Left)各种赋值,包括复合的赋值运算。
  • 逗号运算符:(Left-to-Right),

% & << 不能用在 double\float 上。

printf() 的转换说明修饰符

const

Chapter 3 函数

3.2 内联函数

Chapter 8:结构和其他数据形式

8.1 结构

8.1.1 结构的定义与基本属性

结构的声明描述了一个结构的组织布局:

1
2
3
4
struct book{
    int index;
    int val;
};

结构体 struct book 描述了由两个整数类型组成的一个结构体,后续程序中可以利用模板 struct book 来声明具有相同数据组织结构的结构变量。

一般使用的结构初始化方式有三种,除了对于每一个结构成员进行值初始化之外,可以直接进行列表初始化和利用初始化器初始化:

struct book bk1 = {231, 1219};
struct book bk2 = {.val = 2023, .index = 1, 23};

指向结构的指针具有指针的优点。指向结构的指针比结构本身更加容易操控、在函数中执行更有效率,并且有可能数据表示数据的结构中包含指向其他结构的指针。结构和数组不同,结构名代表的是这个数据集合,而不是结构变量的地址。

结构允许嵌套,也允许使用匿名结构,还允许定义结构数组,但是结构中的的匿名结构就很恶心。

在两个结构类型相同的情况下,我们允许将一个结构赋值给另外一个结构,这是将每个成员的值都赋给另外一个结构的相应成员。

结构不仅可以作为参数传递,也可以作为返回值返回。与传递结构参数相比,传递结构指针执行起来很快,并且在不同版本的C上都可以执行;缺点是不能保护数据(const 限定符就可以解决了)。将结构作为参数传递的优点是,函数处理的是原始数据的副本,可以保护数据,但是需要拷贝副本,浪费了时间和内存。

8.1.2 结构中的字符数组和字符指针

我们可以在结构之中使用以下两种方式存储字符串:

#define LEN 20
struct names{
    char first[LEN];
    char last[LEN];
}

struct names1{
    char *first1;
    char *last1;
}

如果仅仅是声明数组并且随即初始化,那么两种声明方式是没有区别的。但是如果使用类似于 scanf("%s", &names1.first1); 的方式,第二种声明方式就会出现危险。因为程序并未给 first1 分配存储空间,它对应的是存储在静态存储区的字符串字面量,换言之,结构体 names1 里存储的只是两个字符串的指针,如果仍要实行该操作,因为 scanf() 函数将字符串放在 names1.first1 对应的地址上,但是由于这是没有经过初始化的变量,地址可以是任何值,因此程序会将字符串放在任何地方。简而言之,结构变量中的指针应该只是用来程序中管理那些已经分配或者分配在别的地方的字符串。

如果使用 malloc() 函数来分配内存并且用指针来存储地址,这样处理字符串就会比较合理,当然需要记得使用 free() 释放相关内存。

8.1.3 复合字面量和结构

复合字面量可以用于数组或者结构,如果活只需要一个临时结构值,符合字面量很好用,比如我们可以使用复合字面量创建一个结构赋给另外一个结构或者作为函数的参数。语法是将类型名放在圆括号之中,后面紧跟花括号括起来的初始化列表。比如(某些代码进行了适当的精简):

struct book{
    char title[10];
    char auther[10];
    double value;
}
struct book POMA = (struct book){
    "thistitle",
    "thisman",
    11.11
};

int cal(struct book tmp);
cal((struct book){"thattitle","thatman",22.2});

8.1.4 伸缩型数组成员

利用伸缩型数组成员这一特性声明的结构,起最后一个数组成员具有一些特性:其一,该数组不会立刻存在;其二,使用这个伸缩型数组成员可以编写合适的代码,就好像这个数组确实存在并且具有所需数目的元素一样。但是对这个数组有这以下要求:首先,这个数组成员必须是结构的最后一个成员;其次,结构中至少拥有一个成员;最后,伸缩数组的声明类似于普通数组,只不过其中括号是空的。另外,声明一个具有伸缩型数组成员的结构时,我们不能使用这个数组干任何事,必须先给它分配内存空间后才能以指针形式使用它。比如:

1
2
3
4
5
6
7
8
9
struct flex{
    size_t count;
    double average;
    double array[];
}

struct flex *ptr;
ptr = malloc(sizeof(struct flex) + n * sizeof(double));
ptr->count = n;

使用伸缩型数组成员的结构具有一下要求:第一,我们不能用结构进行赋值或者拷贝,比如*ptr1 = *ptr2,不然这样只能拷贝非伸缩型数组成员外的所有成员,如果非要拷贝,应该使用mencpy()函数;第二,不要按值方式将这种结构传递给函数,应该传递指针;第三,不应该将使用伸缩型数组成员的结构作为数组成员或者另外一个结构的成员。

8.2 联合

联合/Union 是一种数据类型,可以在同一个内存空间之中存储不同的数据类型(但是不是同时)。其典型用法是,设计一种表以存储既无规律,实现也不知道顺序的混合类型。使用联合类型的数组,每个联合都大小相等,每个联合可以存储各种数据类型。

1
2
3
4
5
union hold{
    int digit;
    double bigfl;
    char letter;
};

以上声明的联合可以存储一个 int 类型、一个 double 类型或者一个 char 类型的值。联合只能存储一个值,其占用的内存空间是占用内存最大类型所占用的内存。使用联合的方法如下:

1
2
3
union hold fix;
fix.digit = 1;          //把1存储在fix,占用两个字节
fix.bigfl = 1.1;        //把先前存储的内容抹去,把1.1存储在fix,占用八个字节

8.3 枚举

可以使用枚举类型(enumerated type)声明符号名称表示整型常量。使用关键字 enum 可以声明枚举类型并且指定其可以具有的值(事实上,enum 常量就是 int 类型,所有可以使用 int 类型的地方都可以使用枚举类型)。enum 类型的用法如下:

1
2
3
enum spectrum {red, orange, yellow, green = 100, blue, violet};
enum spectrum color = red;
printf("%d, %d, %d, %d", red, orange, blue, violet);    //输出为0, 1, 101, 102

在使用完枚举声明后,red 等就成了具有名称的常量。第二个语句声明了枚举变量 color,并且拿 red 的值初始化 color。这种不连续的枚举直接按照整数进行处理,无法按照想象中直接遍历枚举内的元素。

8.4 typedef

利用 typedef 可以为某一类型自定义名称。我们之前已经利用 define 进行了自定义名称,但是 define 只是单纯的文本替换,并且相比于 typedef 要更为死板很多。比如:

1
2
3
4
#define STRING char*
typedef char* String;
STRING str1, str2;              //其实是char *str1, str2;只创建了一个字符串和一个字符
String strr1, strr2;            //其实是char *strr1, *strr2;创建了两个字符串

这就明白为什么 define 只不过是单纯的文本替换了。同时,利用 typedef 为结构变量命名的时候,可以省略掉这个结构的标签,比如:

1
2
3
typedef struct{
    int data; int index;
}pair;

其实这是为一个匿名结构命名。typedef 更好的一点是可以为更加复杂的类型命名:比如 typedef char (* FRPTC()) [5];FRPTC 声明为一个函数类型,这个函数返回一个指针,指针指向内含五个char元素的数组。

8.5 函数和指针

Chapter 9 位操作

9.1 进制、位和字节

9.1.1 计数

计算机基于二进制,通过关闭和打开状态的组合来表示信息。C语言利用字节表示存储系统字符集所需的大小,通常一字节/byte 包含八位/bit,计算机界用八位组/octet 特指八位字节。可以从左到右给这八位编码为 7~0 ,编号为 7 的位被称为高阶位,编号为 0 的被称为低阶位。

如何利用二进制表示有符号整数取决于硬件,最通用的做法是利用补码。二进制补码利用一字节的第一位(高阶位)表示数值的正负,如果高阶位是 0,则此时表示非负数,其值与正常情况相同;如果高阶位为 1,则此时表示负数,但是这时负值的量是九位组合 100000000(256 的位组合)减去这个负数的位组合。比如某负数为 10011010,其表示 -12211111111 则表示 -1。这样,我们就可以用一字节来表示从 -128~127 的所有数字。

浮点数分两部分存储:二进制小数和二进制指数。计算机中存储浮点数的时候,要留出若干位存储二进制小数,剩下的位存储指数。二进制小数用有限多个 \(1/2\) 的幂的和近似表示数字(事实上,二进制小数只能精确表示有限多个 \(1/2\) 的幂的和)。一般而言,数字的实际值是由二进制小数乘以 \(2\) 的指定次幂组成。

9.1.2 其他进制数

计算机界常用八进制十六进制计数系统,这些计数系统比十进制更加接近计算机的二进制系统。

  • 八进制/octal 基于八的幂,每个八进制位对应三个二进制位。我们在数字前加上一个 0 来特别表示一个数是八进制。
  • 十六进制/hexadecimal基于十六的幂,每个十六进制位对应四个二进制位。我们在数字前加上 0x 来特别表示一个数是十六进制。十六进制是表示字节的非常好的方式。

9.2 按位运算符

我们常见的按位运算符有:

  • 按位逻辑运算符:~&|^
  • 移位运算符:<<>>

9.3 位字段

9.4 对齐特性