笔试知识点(3)

哪些函数容易造成缓冲区溢出

C 和 C++ 不能够自动地做边界检查,边界检查的代价是效率。

C语言标准库中的许多字符串处理和IO流读取函数是导致缓冲区溢出的罪魁祸首

strcpy()函数将源字符串复制到缓冲区。没有指定要复制字符的具体数目

确保 strcpy() 不会溢出的另一种方式是,在需要它时就分配空间,确保通过在源字符串上调用 strlen() 来分配足够的空间。

dst = (char *)malloc(strlen(src)); strcpy(dst, src);

strcat()   可以用 strncat()替代

sprintf()、vsprintf()   格式化文本和将其存入缓冲区的通用函数

gets() 从标准输入读入用户输入的一行文本, 用fgets()替代

在循环中 需要进行缓冲区边界检测的:getchar()、fgetc()、getc()、read()

scanf()  可以设置宽度来解决

getenv() 的最大问题是您从来不能假定特殊环境变量是任何特定长度的。

完全二叉树

若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。每一层结点的个数恰好是上一层结点个数的2倍,所以适合顺序储存在数组中,可以反映二叉树节点之间的关系

一棵深度为k且有(2^k) -1 个结点的二叉树称为满二叉树

完全二叉树的特点:叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。需要注意的是,满二叉树肯定是完全二叉树,而完全二叉树不一定是满二叉树

1>如果树为空,则直接返回错

  2>如果树不为空:层序遍历二叉树

  2.1>如果一个结点左右孩子都不为空,则pop该节点,将其左右孩子入队列;

  2.1>如果遇到一个结点,左孩子为空,右孩子不为空,则该树一定不是完全二叉树;

  2.2>如果遇到一个结点,左孩子不为空,右孩子为空;或者左右孩子都为空;则该节点之后的队列中的结点都为叶子节点;该树才是完全二叉树,否则就不是完全二叉树; 

虚函数

C++多态(polymorphism)是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或者称为重写。

最常见的用法就是声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,动态绑定。由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数,所以被成为“虚”函数。

Shape *shape; shape = &rec; shape->area();  这个返回的是shape的area,因为area函数在程序编译期间就设定好了,这就是静态绑定,虚函数需要再父类的area函数前声明visual,这样就是动态绑定

虚函数 是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数

纯虚函数为抽象类 不能生成对象

父类的纯虚函数(方法:virtual ReturnType Function()= 0;)则编译器要求在派生类中必须予以重写以实现多态性

类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。

类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。

class Line {

public:

void setLength( double len );

Line(); // 这是构造函数声明

~Line(); // 这是析构函数声明

};

析构和构造函数不能被继承,返回值不能用来区分重载,可以通过参数个数,参数类型来区分。

数据库事务正确执行所需要素

        要素的缩写:ACID

        原子性

  整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

  一致性

  在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。

  隔离性

  两个事务的执行是互不干扰的,一个事务不可能看到其他事务运行时,中间某一时刻的数据。

  持久性

  在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。

  由于一项操作通常会包含许多子操作,而这些子操作可能会因为硬件的损坏或其他因素产生问题,要正确实现ACID并不容易。ACID建议数据库将所有需要更新 以及修改的资料一次操作完毕,但实际上并不可行。

  目前主要有两种方式实现ACID:第一种是Write ahead logging,也就是日志式的方式。第二种是Shadow paging

SQL Server中使用了WAL(Write-Ahead Logging)技术来保证事务日志的ACID特性。而且大大减少了IO操作。

WAL的核心思想是:在数据写入到数据库之前,先写入到日志.再将日志记录变更到存储器中。

        SQL Server修改数据的步骤

     1.在SQL Server的缓冲区的日志中写入”Begin Tran”记录

     2.在SQL Server的缓冲区的日志页写入要修改的信息

     3.在SQL Server的缓冲区将要修改的数据写入数据页

     4.在SQL Server的缓冲区的日志中写入”Commit”记录

     5.将缓冲区的日志写入日志文件

     6.发送确认信息到客户端(SMSS,ODBC等)

     7.将缓冲区内的页写入到磁盘

     当事务遇到Commit时,仅仅是将缓冲区的所有日志页写入磁盘中的日志文件;而直到Lazy Writer或CheckPoint时,才真正将缓冲区的数据页写入磁盘文件。

Shadow paging:每个page只在日志文件中存一份,无论这个页被修改过多少次。日志文件中,只记录事务开始前page的原始信息,进行恢复时,只需要利用日志文件中的page进行覆盖即可。

查找最小的N个元素时,使用大顶堆

if(queue.size()<k){

queue.offer(val);

//小顶堆的堆顶放的是已存在堆中的最下的数,如果新加的值比其最小的堆顶值大,说明堆中不是前k大的数,调整堆。

}else if(queue.size()>=k && queue.peek()<val){

queue.poll();

queue.offer(val);

}

删除:只能删除最顶的,将最后一个节点放在顶 重新排序

添加:添加在末尾,从下向上排序

整型溢出的解决方法

将计算结果减去加数看是否与另一加数相等

检测符号位的变化

进程和线程

进程只能有一个程序,程序可以有多个进程

最短作业优先(SJF)调度算法: CPU执行时间最短的 优先   http://c.biancheng.net/view/1244.html

抢占式:进程有先后到达时间,如果在P2到达的时候,P1没有完成,P1还剩7ms 大于 P2所需要的4ms

非抢占式:P1运行完,运行期间到达了3个进程,这三个进程中,CPU时间最短的,先走

级数求和公式

散列函数--> hash表

若选用H(K)=K%9作为散列函数, K为输入值,H(K)为散列地址

 

联通图 无向图 有向图

任意两点联通的无向图 是连通图:边的数目>=顶点-1

任意两点都有x->y y->x的路径 是有向连通图 : 边>=顶点

任意两点仅有一个方向的路径 是单连通图

无向图:0≤边≤n(n-1)/2

有向图:0≤边≤n(n-1)

字节1B = 8b  1KB = 1024B

进制转换

二进制转10:1101.01(2)=1*20+0*21+1*22+1*23 +0*2-1+1*2-2

10转2: 255/2=127=====余1

                 127/2=63======余1

&:同为1=1 否则0

“|”:有1=1,全0则0

“^”:不相同=1

~:按位取反

sql删除表 用 DROP TABLE table_name

设计模式:代码设计解决方案

适配器模式:原本由于接口不兼容而不能一起工作的那些类可以在一起工作   https://blog.csdn.net/carson_ho/article/details/54910430

Adapter extends Adaptee implements Target,extends是继承,implement是实现接口

适配器模式.png

单例模式:保证一个类只有一个对象

简单工厂模式:外界通过调用工厂类的静态方法,传入不同参数从而创建不同具体产品类的实例,将“类实例化的操作”与“使用对象的操作”分开

工厂方法模式:又称工厂模式、多态工厂模式和虚拟构造器模式,通过定义工厂(父类)=公共接口,而子类则负责生成具体的对象。将类的实例化(具体产品的创建)延迟到工厂类的子类(具体工厂)中完成,即由子类来决定应该实例化(创建)哪一个类。外界通过调用具体工厂类的方法,从而创建不同具体产品类的实例

抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类;抽象工厂中定义了具体工厂的公共接口,具体的工厂负责实现具体的产品实例。如果需要添加新产品,此时就必须去修改抽象工厂的接口,和简单工厂模式一样 违背了开放-封闭原则

策略模式:定义一系列算法,将每个算法封装到具有公共接口的一系列策略类中,从而使它们可以相互替换 & 让算法可在不影响客户端的情况下发生变化

代理模式:给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用,通过引入代理对象的方式来间接访问目标对象

模板方法模式:定义一个模板结构,将具体内容延迟到子类去实现。符合开闭原则

public  abstract class Abstract Class {  
//模板方法,用来控制炒菜的流程 (炒菜的流程是一样的-复用)
//申明为final,不希望子类覆盖这个方法,防止更改流程的执行顺序 
        final void cookProcess(){  
        //第一步:倒油
        this.pourOil();
        //第二步:热油
         this.HeatOil();
        //第三步:倒蔬菜
         this.pourVegetable();
        //第四步:倒调味料
         this.pourSauce();
        //第五步:翻炒
         this.fry();
    }  

//定义结构里哪些方法是所有过程都是一样的可复用的,哪些是需要子类进行实现的

//第一步:倒油是一样的,所以直接实现
void pourOil(){  
        System.out.println("倒油");  
    }  

//第二步:热油是一样的,所以直接实现
    void  HeatOil(){  
        System.out.println("热油");  
    }  

//第三步:倒蔬菜是不一样的(一个下包菜,一个是下菜心)
//所以声明为抽象方法,具体由子类实现 
    abstract void  pourVegetable();

//第四步:倒调味料是不一样的(一个下辣椒,一个是下蒜蓉)
//所以声明为抽象方法,具体由子类实现 
    abstract void  pourSauce();


//第五步:翻炒是一样的,所以直接实现
    void fry();{  
        System.out.println("炒啊炒啊炒到熟啊");  
    }  
}

建造者模式:隐藏创建对象的建造过程 & 细节,使得用户在不知对象的建造过程 & 细节的情况下,就可直接创建复杂的对象

  • 指挥者(Director)直接和客户(Client)进行需求沟通;
  • 沟通后指挥者将客户创建产品的需求划分为各个部件的建造请求(Builder);
  • 将各个部件的建造请求委派到具体的建造者(ConcreteBuilder);
  • 各个具体建造者负责进行产品部件的构建;
  • 最终构建成具体产品(Product)。

外观模式:定义了一个高层、统一的接口,外部通过这个统一的接口对子系统中的一群接口进行访问
 

树的遍历:先,中,后序都是深度遍历

笛卡尔积的基数 

D1={张三,李四,王五}, D2={男,女}, D3={北京,西安}, D1xD2xD3的基数是 3*2*2 = 12

fork() ,exec函数, Vfork() 

fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程。

fork()函数用于从一个已经存在的进程内创建一个新的进程,新的进程称为“子进程”,相应地称创建子进程的进程为“父进程”。使用fork()函数得到的子进程是父进程的复制品,子进程完全复制了父进程的资源,包括进程上下文、代码区、数据区、堆区、栈区、内存信息、打开文件的文件描述符、信号处理函数、进程优先级、进程组号、当前工作目录、根目录、资源限制和控制终端等信息,而子进程与父进程的区别有进程号、资源使用情况和计时器等

    pid_t pid;

    pid = fork();

   1)在父进程中,fork返回新创建子进程的进程ID;
    2)在子进程中,fork返回0;if(pid==0)
    3)如果出现错误,fork返回一个负值;
   父子进程的运行先后顺序是完全随机的(取决于系统的调度)

vfork()函数功能与fork()函数功能类似不过更加彻底:内核不再给子进程创建虚拟空间,直接让子进程共享父进程的虚拟空间当父子进程中有更改相应段的行为发生时,再为子进程相应的段创建虚拟空间并分配物理空间。在vfork()函数创建子进程后父进程会阻塞,保证子进程先行运行

vfork()函数创建的子进程会与父进程(在调用exec函数族函数或exit()函数前)共用地址空间,此时子进程如果使用变量则会直接修改父进程的变量值。因此,vfork()函数创建的子进程可能会对父进程产生干扰。另外,如果子进程未调用exec函数族函数或exit()函数,则父子进程会出现死锁现象

举个例子,vfork()函数创建了一个“儿子”暂时“霸占”“老爹”的房产,此时需要委屈老爹一下,让老爹歇息(阻塞)。当儿子买房了(执行exec函数族函数)或者儿子死了(执行exit()退出),就相当于分家了,此时老爹得到自己的房产。如果子进程需要依赖父进程的进一步动作,则会产生死锁

fork是独立的两个进程 父有子的ID,Vfork一旦子创建,父就堵塞,子分享父的资源

exec函数族提供了让进程运行另一个程序的方法。exec函数族内的函数可以根据指定的文件名或目录名找到可执行程序,并加载新的可执行程序,替换掉旧的代码区、数据区、堆区、栈区与其他系统资源。这里的可执行程序既可以是二进制文件,也可以是脚本文件。在执行exec函数族函数后,除了该进程的进程号PID,其他内容都被替换了。

TL控制数据传输的时延

TTL的作用是限制IP数据包在计算机网络中的存在的时间。TTL的最大值是255,TTL的一个推荐值是64。

虽然TTL从字面上翻译,是可以存活的时间,但实际上TTL是IP数据包在计算机网络中可以转发的最大跳数。TTL字段由IP数据包的发送者设置,在IP数据包从源到目的的整个转发路径上,每经过一个路由器,路由器都会修改这个TTL字段值,具体的做法是把该TTL的值减1,然后再将IP包转发出去。如果在IP包到达目的IP之前,TTL减少为0,路由器将会丢弃收到的TTL=0的IP包并向IP包的发送者发送 ICMP time exceeded消息。

常成员函数:只能读取同一类中的数据成员的值

int GetX() const; //声明常成员函数

int Point::GetY() const //定义常成员函数

{

return yVal;

}

1.常成员函数不能更新对象的数据成员

2.当一个对象被声明为常对象,则不能通过该对象调用该类中的非const成员函数

const Set s;

s.AddElem(10); // 非法: AddElem不是常量成员函数

try catch throw 栈的解旋

异常处理的执行过程  https://blog.csdn.net/ShenHang_/article/details/103805053


(1)程序流程到达 try 块,然后执行 try 块内的程序块,如果没有引起异常,那么跟在 try 块后的 catch 子句都不执行,程序从最后一个 catch 子句后面的语句继续执行下去;

(2)抛出异常的时候,将暂停当前函数的执行,开始查找匹配的 catch 子句;

(3)首先检查 throw 是否在 try 内部,如果是,检查 catch 子句,看是否其中之一与抛出对象相匹配,如果找到匹配的 catch,就处理异常;如果找不到,就退出当前函数并释放局部对象,然后继续在主调函数中查找;

(4)如果找到匹配的 catch,就处理异常,如果找不到,则退出主调函数,然后继续在调用更高一级的主调函数中查找;

(5)沿着嵌套函数调用链继续向上,直到为异常找到一个 catch 子句,只要找到能够处理异常的 catch 子句,就进入该 catch 子句,并在它的处理程序中继续执行,当 catch 结束时,跳转到该 try 块的最后一个 catch 子句之后的语句继续执行;

(6)如果始终未找到与该被抛异常匹配的 catch 子句,最终 main 函数会结束执行,则运行库函数terminate将被自动调用,terminate函数的默认功能是终止程序,其缺省功能是调用abort终止程序。

异常被抛出后,从进入try块(与截获异常的catch子句相对应的那个try块)起,到异常被抛出前这期间在栈上构造(且尚未析构)的所有对象都会被自动析构,析构的顺序与构造的顺序相反,这一过程称为解旋

//如果main函数不做异常处理:将会发生错误:
 
// 假设我们我们要做 x/y ,y就不能为0 
void divide(int x, int y)      //抛异常
{
    if (y == 0)  // y为0 就是 异常
    {
        throw x;  //抛出 int类型 异常 (简单)  ,如果是一个类就变得复杂了
    }
 
    cout << "divide结果:" << x/y<< endl;
}
 
void myDivide(int x, int y)
{
    try
    {
        divide(x, y);
    }
    catch (...)
    {
        cout << "我接受了 divide的异常 但是我没有处理 我向上抛出" << endl;
        throw ;    //接收了异常之后继续往外抛
    }
}
 
 
void main22()
{
    myDivide(100, 0);      //main函数不做异常处理,将会发生错误。
    
    cout<<"hello..."<<endl;
    system("pause");
    return ;
}

register volatile restrict

register 这个关键字请求编译器尽可能的将变量存在CPU内部寄存器中,而不是通过内存寻址访问,以提高效率

register 暗示编译程序相应的变量将被频繁地使用,如果可能的话,应将其保存在CPU的寄存器中,以加快其存储速度

由于寄存器变量储存在寄存器而非内存中,所以无法获取寄存器变量的地址

所以以下代码会报错   %p 输出地址

register int i = 0;
printf("i = %d, &i = %p\n", i, &i);

关于指针

#include <stdio.h>
#include <string.h>
 
void main(){
    int i=10;
    printf("i value is: %d\n",&i); //1.1 输出结果: i value is: 1706007260
    printf("i addr is: %p\n",&i); //1.2 输出结果:i addr is: 0x7fff65af9adc
    int *pp=0;
    pp=&i;
    printf("pp value: %d\n",pp); //2.1 输出结果:pp value: 1706007260
    printf("pp addr: %p\n",pp); //2.2 输出结果:pp addr: 0x7fff65af9adc
    printf("pp x value: %x\n",*pp); //2.3 输出结果:pp x value: 0x7fff65af9adc
}

volatile 告诉编译器该被变量除了可被程序修改外,还可能被其他代理、线程修改。因此,当使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,而不使用寄存器中的缓存的值。

volatile int x = 0;

int a = x;

int b = x;

在执行b的赋值中,x必须再次从内存中读取

restrict 指针指向的内存不能被别的指针引用 

 

int ar[10];

int * restrict restar=(int *)malloc(10*sizeof(int));

int *par=ar;

//这里说明restar是访问由malloc()分配的内存的唯一且初始的方式。par就不是了。

for(n=0;n<10;n++)

{

par[n]+=5;

restar[n]+=5;

ar[n]*=2;

par[n]+=3;

restar[n]+=3;

}

因为restar是访问分配的内存的唯一且初始的方式,那么编译器可以将上述对restar的操作进行优化:

restar[n]+=8;

而par并不是访问数组ar的唯一方式,因此并不能进行下面的优化:

par[n]+=8;

因为在par[n]+=3前,ar[n]*=2进行了改变。使用了关键字restrict,编译器就可以放心地进行优化了。

volatile 和 restrict 方便编译器的优化

getDeclaredMethod 不能获取父类方法

Java反射机制中的 getDeclaredMethod:获取当前类的所有声明的方法,包括public、protected和private修饰的方法。需要注意的是,这些方法一定是在当前类中声明的,从父类中继承的不算,实现接口的方法由于有声明所以包括在内。

String classname = "com.Person";
        //寻找名称的类文件,加载进内存 产生class对象
        Class cl = Class.forName(classname);

        //获取一个Person对象
        System.out.println("获取一个Person对象:");
        Object obj=cl.newInstance();
        System.out.println();
 
 
        //1.获取 公有 有参方法 public void demo2.Person.public_show()
        Method Person_public_show=cl.getMethod("public_show",String.class,int.class);
        System.out.println("获取执行 public void demo2.Person.public_show(java.lang.String,int) :");
        Person_public_show.invoke(obj,"神奇的我",12);  
        // invoke方法中传入的是上一步获取到的方法的实例对象和方法传入的实参

getMethod:获取当前类和父类的所有public的方法。这里的父类,指的是继承层次中的所有父类。比如说,A继承B,B继承C,那么B和C都属于A的父类。

finally

finally作为异常处理的一部分,它只能用在try/catch语句中,并且附带一个语句块,表示这段语句最终一定会被执行(不管有没有抛出异常),经常被用在需要释放资源的情况下

Connection conn = null;
Statement stmt = null;
try {
  conn = DriverManager.getConnection(url,userName,password);
  stmt = conn.createStatement;
  String sql = "sql";//需要执行的sql
  stmt.executeUpdate(sql);
  stmt.close();
  conn.close();
  } catch (SQLException e) {
    e.printStackTrace();
  }finally{
  if(stmt! = NULL){
    stmt.close();
  }
  if(conn! = NULL){
    conn.close();
  }
  }

出错,catch之后,return之前,关闭数据库

如果线程执行try-catch 被杀死或者中断,finally不会执行

如果在try catch中执行 System.exit (0), finally不会执行

接口等抽象类 不能被static修饰

jvm方法区

  • 堆——堆是所有线程共享的,主要用来存储对象。其中,堆可分为:年轻代和老年代两块区域。使用NewRatio参数来设定比例。对于年轻代,一个Eden区和两个Suvivor区,使用参数SuvivorRatio来设定大小;
  • Java虚拟机栈/本地方法栈——线程私有的,主要存放局部变量表,操作数栈,动态链接和方法出口等;
  • 程序计数器——同样是线程私有的,记录当前线程的行号指示器,为线程的切换提供保障;
  • 方法区——线程共享的,主要存储类信息、常量池、静态变量、JIT编译后的代码等数据。方法区理论上来说是堆的逻辑组成部分;
  • 运行时常量池——是方法区的一部分,用于存放编译期生成的各种字面量和符号引用;

nums=[1,2,3,4,5]  表示[3,4]  --> num[2:-2]  左边从0开始数,右边从-1开始数

某些情况下,编译器会调整特定代码的执行顺序来提高代码的执行效率

最简DFA  1、无死状态(不能到达终态或无法到达的状态)2、两两不是等价状态 3. 无多余状态

内存溢出指申请的内存空间不足

内存泄漏指申请内存空间后无法释放造成内存堆积

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,关于数据结构笔试知识点,我可以为您提供一些基础的内容。数据结构是计算机科学中的一个重要分支,它研究的是数据的组织、存储和管理方式。在笔试中,常见的数据结构知识点包括以下几个方面: 1. 数组:数组是一种线性数据结构,它可以存储一组相同类型的数据。在笔试中,常见的数组操作包括遍历、查找、插入、删除等。 2. 链表:链表也是一种线性数据结构,它由一系列节点组成,每个节点包含一个数据元素和一个指向下一个节点的指针。在笔试中,常见的链表操作包括遍历、查找、插入、删除等。 3. 栈和队列:栈和队列都是一种特殊的线性数据结构。栈是一种后进先出(LIFO)的数据结构,而队列是一种先进先出(FIFO)的数据结构。在笔试中,常见的栈和队列操作包括入栈、出栈、入队、出队等。 4. 树:树是一种非线性数据结构,它由一组节点和一组边组成。每个节点包含一个数据元素和若干个指向子节点的指针。在笔试中,常见的树操作包括遍历、查找、插入、删除等。 5. 图:图也是一种非线性数据结构,它由一组节点和一组边组成。每个节点包含一个数据元素和若干个指向其他节点的指针。在笔试中,常见的图操作包括遍历、查找、插入、删除等。 6. 排序和查找算法:排序和查找算法是数据结构中的重要内容。常见的排序算法包括冒泡排序、选择排序、插入排序、快速排序、归并排序等;常见的查找算法包括线性查找、二分查找、哈希查找等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值