专业课知识复习

数据结构
二叉树的定义

数据库

第一范式(1NF)

1NF是对属性的原子性,要求属性具有原子性,不可再分解;

表:字段1、 字段2(字段2.1、字段2.2)、字段3 ……

如学生(学号,姓名,性别,出生年月日),如果认为最后一列还可以再分成(出生年,出生月,出生日),它就不是一范式了,否则就是.

第二范式(2NF)

2NF是对记录的惟一性,要求记录有惟一标识,即实体的惟一性,即不存在部分依赖;

非主属性完全依赖于主关键字

表:学号、课程号、姓名、学分;

这个表明显说明了两个事务:学生信息, 课程信息;由于非主键字段必须依赖主键,这里学分依赖课程号姓名依赖与学号,所以不符合二范式。

正确做法: 学生:Student(学号, 姓名); 课程:Course(课程号, 学分); 选课关系:StudentCourse(学号, 课程号, 成绩)。

不满足第二范式可能存在的问题:

  • 数据冗余:,每条记录都含有相同信息;
  • 删除异常:删除所有学生成绩,就把课程信息全删除了;
  • 插入异常:学生未选课,无法记录进数据库;
  • 更新异常:调整课程学分,所有行都调整。
第三范式(3NF)

3NF是对字段的冗余性,要求任何字段不能由其他字段派生出来,它要求字段没有冗余,即不存在传递依赖;

表: 学号, 姓名, 年龄, 学院名称, 学院电话

因为存在依赖传递: (学号) → (学生)→(所在学院) → (学院电话)

可能会存在问题:

  • 数据冗余:有重复值;
  • 更新异常:有重复的冗余信息,修改时需要同时修改多条记录,否则会出现数据不一致的情况 .

正确做法:

学生:(学号, 姓名, 年龄, 所在学院) 学院:(学院, 电话)

范式化设计和反范式化设计的优缺点

范式化

优点: 可以尽量减小数据冗余, 更新操作快,表体积更小 缺点: 查询需要对多个表进行关联,导致性能降低; 更难进行索引优化

反范式化

优点: 减少表的关联; 更好的进行索引优化 缺点: 存在数据库冗余及数据维护异常; 对数据的修改需要更多成本

C语言
文件操作

FILE *fopen(const char *filename, const char *mode);

fprintf函数 是否会覆盖所有,取决于fopen时是采用什么方式

fopen(file , "r") ; 读方式打开,写会报错!
fopen(file , "w") ; 写方式打开,整个文件会被直接重新写,以前文件的数据全丢失。
fopen( file , "a" ); 追加方式打开,写入数据时,是追加到文件尾,不会影响原文件中的数据
fopen( file ,"r+");
"r+" "a+" "w+" 等方式打开为读写模式,这时,写数据前,要先定位文件指针,如果想改动文件中的内容,则写入的数据长度与要覆盖的数据长度要一致才可以,不然会造成数据覆盖或数据完整性错误!

int fclose(FILE *fp)

  • 若成功执行了关闭操作,放回值为0; 非0,表示关闭时有错误

int fgetc(FILE *fp);

  • 从分配读一个字符,将位置指针指向下一个字符

  • 若读成功,则返回该字符

  • 若读到文件尾或者读取错误,则返回EOF

    #include <stdio.h>
    #include <stdlib.h>
    
    int main() {
        FILE *fp;
        int ch;
        if ((fp = fopen("demo.txt", "r")) == NULL) {
            printf("Failure to open demo.txt! \n");
            exit(0);
        }
    
        while ((ch = fgetc(fp)) != EOF) {
            putchar(ch);
        }
        fclose(fp);
        return 0;
    }
    

int fputc(int c, FILE *fp);

  • 向fp输出字符c
  • 若写入错误,则返回EOF,否则返回c

feof()

  • 函数 feof() 检查是否能达到文件尾,返回非0值,否则返回0值

按块读写数据

size_t fread(void *buffer, size_t size, size_t count, FILE *stream)

  • 从fp所指文件中读取数据块存到 buffer 指向的内存

  • buffer 是存储数据块的内存首地址,如数组的地址

    num = fread(a, sizeof char, n, fp); //n个块,每个块占1字节
    

size_t fwrite(const void *buffer, size_t size, size_t count, FILE *stream)

  • 将buffer指向的内存中的数据块写入fp所指的文件
  • 返回值:实际写入数据块个数,应等于count,除非出现写入错误
位运算

1、补码

在总结按位运算前,有必要先介绍下补码的知识,我们知道当将一个十进制正整数转换为二进制数的时候,只需要通过除2取余的方法即可,但是怎么将一个十进制的负整数转换为二进制数呢?其实,负数是以补码的形式表示:先按正数转换,然后取反加1

要将十进制的-10用二进制表示,先将10用二进制表示:
0000 0000 0000 1010
取反:
1111 1111 1111 0101
加1:
1111 1111 1111 0110
所以,-10的二进制表示就是:1111 1111 1111 0110`

2、按位与(&)

参加运算的两个数,换算为二进制(0、1)后,进行与运算。只有当相应位上的数都是1时,该位才取1,否则该位为0。

将10与-10进行按位与(&)运算:
0000 0000 0000 1010
1111 1111 1111 0110
-----------------------
0000 0000 0000 0010
所以:10 & -10 = 0000 0000 0000 0010`

3、按位或(|)

参加运算的两个数,换算为二进制(0、1)后,进行或运算。只要相应位上存在1,那么该位就取1,均不为1,即为0。

将10与-10进行按位或(|)运算:
0000 0000 0000 1010
1111 1111 1111 0110
-----------------------
1111 1111 1111 1110
所以:10 | -10 = 1111 1111 1111 1110`

4、按位异或(^)

参加运算的两个数,换算为二进制(0、1)后,进行异或运算。只有当相应位上的数字不相同时,该为才取1,若相同,即为0。

将10与-10进行按位异或(^)运算:
0000 0000 0000 1010
1111 1111 1111 0110
-----------------------
1111 1111 1111 1100
所以:10 ^ -10 = 1111 1111 1111 1100`

可以看出,任何数与0异或,结果都是其本身。利用异或还可以实现一个很好的交换算法,用于交换两个数,算法如下:

a = a ^ b;
b = b ^ a;
a = a ^ b;

5、取反(~)

参加运算的两个数,换算为二进制(0、1)后,进行取反运算。每个位上都取相反值,1变成0,0变成1。

对10进行取反(~)运算:
0000 0000 0000 1010
---------------------
1111 1111 1111 0101
所以:~10 = 1111 1111 1111 0101
转义字符
所有的ASCII码都可以用“\”加数字(一般是8进制数字)来表示。而C中定义了一些字母前加"\"来表示常见的那些不能显示的ASCII字符,如\0,\t,\n等,就称为转义字符,因为后面的字符,都不是它本来的ASCII字符意思了。
32~126(共95个)是字符(32是空格),其中48~57为0到9十个阿拉伯数字。
65~90为26个大写英文字母,97~122号为26个小写英文字母
static

注意: 静态局部变量,在函数里定义,就只能在这个函数里使用,同一个文档中的其他函数也是用不了的。

#include <stdio.h>
#include <iostream>
using namespace std;

int a = 5;
void f() {
    static int a = 10;
    a++;
    cout << "static:" << a << endl;
}

int main() {
    cout << a << endl;
    f();
    f();
    f();
    cout << a << endl;
}

//------------------输出:
5
static:11
static:12
static:13
5