程序设计理论杂选

Posted by PowderHan on January 7, 2019 已经被偷看过次啦QAQ

Ps: 小声BB:考前会一直不定期更新QAQ
求不挂科!!!

http://sc1.111ttt.cn/2015/1/05/23/98232240282.mp3

SYSU is a good place. You can lose a lot of hair here.

By JZH

强行安利教程


继续安利教程(类型声明疑难(超赞))


Begining


程设理论题杂讲

运算优先级口诀记忆(自创轻喷)

  • 圆方括号,箭头句号
  • 自增自减非反负、针强地址和长度
  • 乘除,加减,再移位
  • 小等大等
  • 等等不等
  • 位与,位异,再位或
  • 逻辑与再逻辑或
  • 三疑 二赋 一真逗

程序编译理论

  • 源程序 .c
    preprocessor 预处理器 -E
  • 添加了宏的源程序 .i
    compiler 编译器 -S
  • 汇编语言 .s
    Assembler 汇编器 -C
  • 可重定向目标程序 .o(bj)
    Linker 链接器
  • 可执行二进制程序
  1. Interpreter 解释器,用于执行解释型语言如Python
  2. byte code 字节码,中间码
  3. machine/native code CPU可直接执行
  • ASCII码
  • Unicode 中文字符编码方式

学术名词英文

  • decimal
    • 十进制
  • binary
    • 二进制 0b
  • octal
    • 八进制 0
  • hexadecimal
    • 十六进制0x
  • Escape sequences
    • 转义字符
  • Macros
  • Pass-by-reference
    • 引用传递
  • Pass-by-value
    • 值传递
  • sequence point
    • 序列点
  • side effects
    • 副作用

变量命名

原则:只能是字母数字和下划线,开头不能是数字

变量名长度

可以是任意长度,但只会处理前面一部分

  • C89: 外部标识符 $<=6$ 内部标识符 $<=31$
  • C99: 外部标识符 $<=31$ 内部标识符 $<=63$

可用字符数

字母 $26×2$ + 数字 $10$ + 下划线 $1$ = $63$

程序内存分布

  • 文本段(code):包含可执行的指令序列
  • 数据段(data):包含已初始化的全局变量与静态变量,并不是只读的
  • BSS段:未初始化的全局变量和静态变量
  • Stack:函数调用与自动变量
  • Heap:动态分配内存的变量

数据类型

  1. 基本类型
    • 数值类型
    • 字符类型 (char)
  2. 构造类型
    • 数组
    • 结构体
    • 共用体
    • 枚举类型
  3. 指针类型

  4. 空类型

typedef用法

  • typedef的使用可以简单理解成正常定义一个变量的方法来完成
    例如typedef struct stu *p
    这里和定义一个指向结构体stu的指针p的方法完全相同,只是需要加上一个typedef,p就变成了这种类型的别名

  • 结构体定义中是不能包含其本身这个结构体变量的,也就是不能进行递归定义
     从另一个角度来说,在定义结构体的具体成员的过程中,这个结构体还是一个unkown type name,所以不能包含这种类型的成员
     但可以定义指向结构体本身类型的指针

  • 在做题的时候一定要注意没有typedef的情况下,一定要注意题目中有没有漏掉struct导致CE

  • typedef typename newname可以出现在typename的定义之前,因为只是起一个标识别名的作用
    同时可以重复将typename1类型别名为newname1类型,也可以将typename1类型起新的别名newname2
    但是已经被定义为typename1类型的别名的newname1不可以再更改变为typename2类型的别名,否则CE

  
ex:  
    typedef int lll;  
    typedef long lll;  
    //CE  
      
    typedef int lll;  
    typedef int lll;  
    //valid
      
    typedef int lll;  
    typedef int iii;  
    //valid  

字符串与字符指针的区别

  • 可以将一个字符串直接赋值给一个字符指针,ex:char *p = "hello"; 这个过程中 双引号做了3件事:
    1.申请了空间(在常量区),存放了字符串
    2.在字符串尾加上了’/0’
    3.返回地址
    所以char *p只是负责接收并且存储这个地址,但是需要注意的是p指向的是一个字符串常量
    如果使用strcpy(s1,s2)来进行字符串的拷贝,而使用p这样的字符串常量的地址作为被粘贴位置s1的话,则会发生段错误
    同时需要注意,如果尝试修改字符串常量的值,不会发生CE,但是会发生段错误
    但字符串常量是可以当作提供赋值内容的s2来使用的 这个应该直观上很好理解

指针常量与常量指针

顶层const与底层const
指针本身是一个对象,同时也可以指向对象
所以指针本身是不是常量或者指针指向的是不是一个常量是两个相互独立的问题

用名词 顶层const(top-level const)表示指针本身是个常量(适用于任何对象)
顶层const是放在之后,指的是指针本身就是常量,不可以重定向(修改指针本身的值),但是可以通过p修改其指向的值
int *const p = &a;指针本身是常量所以定义时必须初始化

用名词 底层const(low-level const)表示指针所指向的对象是一个常量(与指针和引用等复合类型的基本类型部分有关)
底层const是放在之前,指的是指针所指向的对象是常量,可以重定向,但是不可以通过p修改值
const int* p = &a;指针本身并非常量定义时可以不初始化
当然其等价于int const* p = &a

如果前后都是const,则是顶底互用,不能重定向也不能通过p修改值
const int* const p = &a; 既具有顶层

结构体

  • 一定要注意struct定义结构体的后花括号后是否有分号

  • struct结构体的内存大小具有自动补全的性质
  • 原则1、数据成员对齐规则:结构(struct或联合union)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储)
  • 原则2、结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a里存有struct b,b里有char,int,double等元素,那b应该从8的整数倍开始存储。)
  • 原则3、收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐
     所以我们可以知道如下结构体的内存大小为24B
  • C语言中定义结构体时不能给成员赋初值

  • 重点
    C语言在全局变量处定义一个结构体的时候,如果未初始化,那么并不会进行实例化
    此时的结构体定义作用等于声明,并没有实际分配内存
    所以这个时候可以把结构体变量的定义放在结构体定义的前面
  
ex:
    #include <stdio.h>  
      
    struct stu a;  
    struct stu {  
        int x, y;  
    };  
  
    int main() {  
  
    }  

共用体

  • 共用体所占内存大小是其成员所占内存大小的最大值
    联合变量也可以在定义时直接初始化,但这个初始化只能对第一个成员进行
    如果同时初始化多个成员,也只能用第一个值初始化第一个成员,并且会给出warning

枚举类型

enum DAY {  
    MON=1, TUE, WED, THU, FRI, SAT, SUN  
};  
    1. 枚举型是一个集合,集合中的元素(枚举成员)是一些命名的整型常量,元素之间用逗号,隔开
      2. DAY是一个标识符,可以看成这个集合的名字,是一个可选项,即是可有可无的项。
      3. 第一个枚举成员的默认值为整型的0,后续枚举成员的值在前一个成员上加1。
      4. 可以人为设定枚举成员的值,从而自定义某个范围内的整数。
      5. 枚举型是预处理指令#define的替代。
      6. 类型定义以分号;结束。
      7. 注意枚举类型中最后一个不需要加分号

宏定义

  • 下面代码中对A的宏定义代表将A定义成一个空白字符
    那么在预处理阶段A+1会被替换成 +1,最后得到的输出就是1
   
#include <stdio.h>     

#define A 
  
int main() {  
    printf("%d\n", A + 1);  
    return 0;  
}  
  • 需要注意一下宏定义的一些细节问题
    #define a b是机械化地在预处理阶段将所有的a部分替换成b
    一个很直观的例子就是
    #define a 3+5
    那么a/2等价于3+5/2 应该是 3+2=5

位段

大致概念

  • 位段是 C 语言特有的数据结构, 它允许我们定义一个由位组成的段, 并可为它赋以一个名字

基本形式

   
#include <stdio.h>  


struct name {
    unsigned a : 1;
    unsigned b : 4;
};

int main() {
    printf("%d\n", sizeof(struct name));
}

注意要点

  • 位段成员的类型只能是unsigned int 或者 int类型
  • 长度为n的位段的取值范围为0~($2^n-1$)
  • 一个位段必须存储在同一个存储单元中(字节),不能跨两个存储单元
    如果第一个存储空间不能容纳下一个位段,则该空间不用,而从下一个单元起存放该位段(int的一个存储空间是32位)
  • 可以定义无名位段,表示相应的空间不使用
    也可以定义长度为0的无名位段,表示下一个位段从下一个存储区间开始存放
    位段不可说明维数,也就是不能说明位段数组,例如falg:l[2]
    不可以取位段地址,也意味着不存在位段指针
    位段成员的长度不能大于指定类型固有长度,比如说int的位域长度不能超过32,否则CE
   
struct packed_data {  
    unsigned a:2;
    unsigned b:4;
    unsigned :0; /* a、 b存储在同一个单元, c、 d另存在一个单元 */
    unsigned c:3;
    unsigned :3; /* 这三位空间不用 */
    unsigned d:2;
} data;

switch的用法

  
    switch (a) {
        case 1:
            printf("QAT");
        case 5:
            printf("TAT");
        default:
            printf("QWQ");
    }
  • switch的大致形式如上,default并不是必须的
    switch中的表达式只能为整型
  • 其中case后面所跟的值必须是整形常量,而不能使用变量(当然字符类型也是一种特殊的整型)
  • 同时注意到case的判断只是一个入口作用,程序会找到对应的入口进去,一直往下执行,直到结束或者遇到break,所以上述代码如果a==1那么最后会输出QAQTATQWQA

C++引用

常引用const T &与引用T &是不同的类型
T &类型的引用或者T类型的变量可以用来初始化const T &类型的引用
const T &类型的常引用和const T类型的常变量则不能用来初始化T &类型的引用,除非进行强制类型转换

LINUX常见指令

编译         gcc hello.c -o hello
执行         ./hello
返回上级目录     cd ..
跳转下级目录     cd Desktop
查看目录内容     ls
先运行a程序,再运行b程序              ./a && ./b
运行a程序并将其输出作为b程序的输入运行b程序    ./a | ./b(管道机制)

杂讲

  • float类型的值用printf(“%d”)打印,得到的是垃圾值
  • int类型用printf(“%f”)打印,得到的是0.000000(注意保留几位小数)
  • int类型如果用scanf(“%f”)输入,再按printf(“%d”)输出得到的是垃圾值,如果再按printf(“%f”)输出得到的是0.000000
  • float类型如果使用scanf(“%d”)输入,再按照printf(“%d”)输出得到的是0.000000,如果再按printf(“%d”)输出得到的是1

  • unsigned int x = -5 使用printf(“%d”)打印,得到的是-5

  • sizeof()具有截断作用,一般情况下不会计算其中的值

  • 函数的返回类型可以不写,只会warning,默认为int

  • 指针大小由机器字长决定,32位机为32位,64位机为64位

  • ++a + ++a 一定要有空格,但仍然是未定义行为

  • getchar()的返回值是int

  • printf("%d %d",i,j,k);
    参数多于控制符号时,只给warning,但正常输出前两个

  • printf("%d %d %d",i,j);
    控制符号多给参数时,只给warning,最后输出的是垃圾值

  • scanf("%*d",&a);
    %后加了*的部分会被忽略,会进行读取,但是读取后不会赋值给参数变量
    例如scanf("%*d %d",&a,&b);
    如果输入3 5,这个时候3会被读取但是不会被赋值给a,然后5会正常被赋值给b 最后的效果是a没有被输入赋值 b被赋值为5

  • printf("%*d",a);可以使用后面的形参代替,实现动态格式输出出
     如下面的例子中 最后等价于printf(“%10.3s”,s);
  
ex:
    char *s = "myworld";
    int i = 3;  
    printf("%10.*s",i,s);  
  • int ungetc(int ch, FILE* stream)
    将字符ch退回到指定流中

  • exit(0) 是正常退出并且返回0值
    abort() 强行异常退出,不会进行一些常规的消除操作等,返回值非0

  • []优先级高于*

  
int **a[10] - int **[10] a  
int (**a)[10] - int [10] **a  
int *(*a)[10] - int *[10] *a  
  • C语言中有个很小的容易被忽略的细节a[i] 等价于 i[a]

  • C语言中可以将一个 int 类型强行赋值给 int* 指针
    C++则禁止了这种行为

  • 函数签名(signature)大致 就是把函数名字去掉以后剩下的东西(返回值、参数、调用方式等)
    用于表示区别函数

  • C使用static、extern等变量类型声明时,允许省略后面的type类型,只给warning,默认为int
  • C++中则必须加上type类型名,不然会error

  • static int x;
  • 不初始化也是随机值
  
ex:  
struct point {  
    int index;  
    double x, y;  
};  
  • 常量在定义的时候必须初始化,否则CE

  • 注意,数组名只在一定程度上等价于指针,但仍有区别
    例如如下代码中p的值虽然与q相等,但sizeof(q)得到的是指针的大小,而sizeof(p)得到的是整个数组的内存大小
    需要注意的是,char* p = “QAQAQAQAQAQAQAQAQAQ”,这个时候的sizeof(p)仍然是指针的大小8

  
ex:  
#include <stdio.h>  
  
int main() {  
    int p[] = {1, 2, 3, 4};  
    int *q = p;  
    printf("%d %d\n", sizeof(p), sizeof(q));  
}  
  • 对数组名取地址相关问题 ex:int a[5];
    其实a和 &a结果都是数组的首地址,但他们的类型是不一样
    a表示&a[0],也即对数组首元素取地址,a+1表示首地址+sizeof(元素类型)
    &a虽然值为数组首元素地址,但类型为:类型 (*)[数组元素个数],所以&a+1大小为:首地址+sizeof(a)
    简单地说,a是一个指向数组首元素的指针,而&a可以某种程度上理解为一个指向整个数组的指针(但实质上仍是首元素的地址)

  • 形参的类型默认是auto,所以不能再给形参加上作用存储类型的声明
    例如void work(auto int a);会报错
    auto,extern,static都不可以,但是可以使用register

  • 对不同类型的指针作大小判断的时候,是直接比较内存地址的大小,但会给出warning.
    将A类型的变量地址赋值给指向b类型的指针时,会给出warning,但不会CE
    两个指针之间未定义加乘除运算以及按位与异或等运算,但是有减法运算

  • printf("C programing %s\n","Class %s\n","WOW");
    以上输出只会将第二个字符串嵌套到第一个中,而不会将第三个嵌套到第二个中
    同时会给出warning,提示说多给了一个字符串参数,也就是忽略了第三个字符串

  • char *str = "abc""def";
    以上赋值方法是可行的,会将两个字符串拼接在一起成为一个字符串并返回字符串的首地址给str

  • C语言中可以通过一个非底层指针来改变一个const常量的值,只会给出warning但仍可编译通过
    C++中则禁止了这种行为

  • 对负数取模的时候只需要记住*任何情况下都满足a=(a/b)*b+a%b**即可 例如3%(-2) 先算出3/(-2)==-1,那么3%(-2) == 3-(-1)(-2) == 1```
    注意一下取模运算只能出现在整数中,而不能涉及到浮点数
  • 而对于除法,不管正负,算出值后去掉小数部分即可

  • 有符号类型变量与无符号类型变量进行运算时,会自动转换为无符号类型比较
     所以一个无符号的正整数和一个有符号的负整数比大小,最后会是无符号的复整数大
    当然需要注意的是,浮点数的第一位固定是符号位,所以不存在无符号的浮点数

  • 关于C语言序列点和副作用

  • 要注意没有&&=||=这样的符合运算符

  • return语句能作为选择表达语句的表达式,也不能通过任何方式利用它的返回值

  • 注意+ -等运算符号先算左右哪边是取决于编译器实现的,而非一定从左往右算

  • float f = y + x /= x / y;会CE报错
    因为/=的优先级很低在+之后,那么上式等价于float f = (y + x) /= (x / y)
    y + x并非左值,所以会CE报错

  • 注意%= /= *=的结合性是从右向左
    所以int z = x /= y %= 2;先算的是y%=2得到0,然后发生除0错误

  • 做题时候一定要注意if或者for以及do...while等语句后有没有分号

  • 使用register声明变量不一定能够真的将变量存在寄存器中
    不可以对register变量进行取地址操作了

  • C语言预处理可以有编译器的特性
    条件编译(Conditional Compilation)使C可以在不同平台上编译出可执行文件

  • 以下三条语句,第一条会给出warning,第二条无误,第三条会发生段错误
  
    int **a = &a;  
    int **b = &*b;  
    int **c = **c;  
  • int a[2][3] = {1, 2, 3, , 4, 5};不可以这样空开一个元素进行初始化
    函数用多维数组作参数时,只能省略最高维的维数,别的低维不能省略,否则编译器无法识别判断

$Ending$






The total number of English words:1506
The total number of Chinese words:9255
欢迎点击下方的知乎图标关注我的知乎QAQ
毕生梦想-成为知乎大V
蟹蟹~