程序员的自我提升

1、c++编译器的步骤

1)预处理,命令:gcc -E test.cpp -o test.i 将所有的#define 替换展开宏,处理所有的条件预处理#if #ifndef等 将#include包含的头文件引入,删除所有的// /*等注释,保留#pragma 为编译做准备

2)汇编,命令:gcc -S test.i -o test.s 对预编译文件进行语法分析,语义分析,词法解析,以及优化后生成相应的汇编代码

3)编译,命令:gcc -c test.s -o test.o 将汇编代码转换为机器代码

3)链接,命令:ld 将.o文件链接成可执行文件a.out


2、编译器都做了哪些工作

扫描(词法分析)关键字:int char class等、语法分析 、语义分析、源代码优化、代码生成、目标代码优化,链接时编译器会将函数地址重定向。


3、线程的数据共享

线程共享的数据包括代码段、数据段、进程空间、打开文件。线程私有数据有寄存器、栈。


4、可重入函数的特点

1)不适用任何静态的或者全局变量的非const变量

2)仅依赖调用方提供的参数

3) 函数里面不调用可重入的其它函数


5、c编译器的内存结构

1)text段也成为代码段,保存机器代码;(指令段只可读)

2)data段存放初始化的全局变量以及局部静态变量,(数据段可读写)

3)bss段存放未初始化的全局变量和局部静态变量(默认为0)但是在文件中不占空间,(数据段可读写)


6、套接字编程隐患

1)程序重启,绑定地址(bind)地址不可用,缺少setsockopt(m_sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt));有时候设置了但是重启时还是不可用,有可能是服务进程没有终止,客户端如果connect失败,返回111说明服务器没有开启对应的进程,没有进程在监听客户端所连的端口。

2)套接字read时若返回值为0说明对方已经关闭了连接,同样如果客户端连接断开,服务器继续调用write,则会抛出SIGPIPE信号,返回值为-1,errno=EPIPE,若服务器没有捕获此信号则服务器会中断,因此一定要记得捕获此信号。

3)epoll如果采用了边缘触发(ET)模式倘若客户端发送的数据比如100,但是服务器只读了10个,由于只触发一次通知所有就会阻塞,因此要么就改为水平触发,要么一次性读取完。


7、大数据查询解决方案

1)数据分割:包括水平分割和垂直分割,水平分割就是分表将一个大表的数据拆分为多个表,垂直分割则是将主码和一些常查询的放一张表而将主码和不常查的用于另外一张表。一般不用与查询的长字段比如BLOG等剥离到别的表。

2)分区主流的RDBMS将表水平分割分到不同的文件或者磁盘上,在插入时数据库系统会自己把数据分配到相应的分区中。分区的优点主要是改善检索性能,提供并行能力,分区的方法有范围分partitionby range,可以按时间将数据分为四个季度,也可以按列表分区将表按照某个字段的值分区如将号码按照省份分为pationby list ,也可以按照组合分区:将范围分区和列表分区组合,可以视为以省份和季度为key。

3)参考http://blog.sina.com.cn/s/blog_803d9ba90100xg25.html


8、hash原理以及冲突解决

hash也称散列是将任意长度的消息压缩为指定长度的消息摘要的函数;将消息存储在散列表里用的叫散列法包含除法散列表pos= hashval%hashlen;平方散列:pos=hashval*hashval》28;斐波拉切散列法:pos=hashval*固定值》28

解决冲突方法:1)开放地址法Hi=(H(key)+di) MOD m i=1,2,...,k(k<=m-1),线性探测再散列;2)再哈希当发生冲突时再采用另外的hash函数直到没有冲突为止;3)拉链法,将hash值相同的节点放在同一个单链表中,hashmap采用的就是拉链法;4)建立公共溢出区,指定hash区和data区,data区就是公共溢出区。

应用场景:海量数据的存储计算,统计,查询如:从一百万个ip存量数据中查询是否存在”172.0.0.1";可以采用hash查询而不是遍历这一百万的数据个个去比较。

9、字符数组初始化为0的几种方法

1)char str[10] = ""; 

2)char str[10] = {0};

3) char str[10]; str[0] = '\0'; 

注:字符串一定要记住初始化,否则出现乱码。


10、c语言中0,"0", '\0', '0'的区别

0是整数,"0"是字符串是字符{'0','\0'},'0'是字符数字其ascii码为48,'\0'是字符串结束符其ascii码为0,代表字符NULL。


11、bitmap

当比较复杂点的要求但是对内存要求比较严格,可以用多个bitmap来代替,比如用bitmap_a,bitmap_b来确定一个整数的四种情况,00,01,10,11可以分别代表不同的意思,当赋值时需要同时修改这两个bitmap。同样更复杂的如果需要代表更多种不同含义的则可以继续加bitmap。


12、各种排序算法的平均时间复杂度

快速、归并、堆的复杂度都是O(nlogn),冒泡、选择、插入排序的复杂度则都为O(n*n),最小k值堆的复杂度为O(N*logK),trie的复杂度为(N*L),hash的复杂度为N,各种排序算法都没有可比性,特殊情况下有些排序算法反而更优,视实况而定如已经排好序的则用插入排序,或则堆排序,各个子有序队列则用归并排序。二分查找法的效率比快速排序要高(在查找的时候)。trie一般用于比较大的大数据中,因为其非常耗内存,但是查询和插入的效率比较高,可以去重;数组的特点是随机访问快,但是增加删除比较耗时,链表则刚刚相反,所以折衷为hash_map采用拉链法结合了数组和链表的特性。


13、从一千万ip中查询某一个ip是否存在,采用的方法有?

trie和hash。查询速度快,不需要从头遍历。时间复杂度为O(1)


14、SUID、SGID的作用

用户有userid,grpid,可以用id查看,同时有euserid,egrpid,Linux内核根据后者去判断该进程的访问权限,在未设置SUID|SGID之前有效用户id为进程的用户id。但是如果设置了SUID位,那么其有效用户则变为了属主的用户权限。

ex:

int main(int argc, char **argv) {
        const char *file = "/data/home/hemb/read.txt";
        int fd = open(file, O_RDWR,0);
        if (fd < 0) {
                perror("open");
                return 0;
        }   
        int nwrite = write(fd,"hello",5);
        if (nwrite < 0) {
                perror("write");
                return 0;
        }   
        printf("ok....\n");
        return 0;

}

属主用户可以利用a.out打开和写文件-rw-r--r--;在未设置SUID之前,其它进程(和属主用户用户id不同的)不可以打开文件但是设置SUID之后就可以打开和写入了和拥有了属主的权限。

$ sudo chown root /usr/local/bin/node
$ sudo chmod +s /usr/local/bin/node

这样任何一个用户都具有root的权限去使用node命令了。

14、inode

每一个文件除了文件名还有一个i节点,可以用ls -i file查看,inode可以看做文件在磁盘的物理地址,而文件名则是逻辑地址。i节点包含的信息有文件连接数、文件大小、属主用户id,组id、文件类型,权限,文件数据区的地址、访问、修改、创建时间等,文件连接数为0时文件才会删除。硬链接会增加文件连接数。但是软连接则不会。删掉软连接不会删除源文件。不可以硬链接目录,可以硬链接文件。软连接的inode指向源文件的inode可以看做是地址的地址。


15、c++父子类构造约束

1. 如果子类没有定义构造方法,则调用父类的无参数的构造方法。

2. 如果子类定义了构造方法,不论是无参数还是带参数,在创建子类的对象的时候,首先执行父类无参数的构造方法,然后执行自己的构造方法。

3. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数,则会调用父类的默认无参构造函数。

4. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类自己提供了无参构造函数,则会调用父类自己的无参构造函数。

5. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类只定义了自己的有参构造函数,则会出错(如果父类只有有参数的构造方法,则子类必须显示调用此带参构造方法)。

6. 如果子类调用父类带参数的构造方法,需要用初始化父类成员对象的方式


16、explicit的作用

在c++中若含有参数构造函数,当其中的类函数也包含构造函数中的参数时,若没有指定explicit则会进行隐式转换。而explicit就是为了防止这种默认转换。


17、操作符重载需要注意,返回值若不是用引用则会用拷贝构造函数生成一个副本然后返回,这样就会调用拷贝构造函数,从而重复调用死循环。

class A {
        public:
                A(int i) {
                        this->num = i;
                }
                A(const A &a) {
                        printf("A()\n");
                        *this = a;
                }
                A operator = (const A &a) {
                        printf("=\n");
                        return *this;
                }
        private:
                int num;
};

18、引用变量和被引用对象的地址是同一个,引用可以看做是一个软连接。

19、const修饰的成员函数不能修改类中的非const成员变量的值。const & :常引用不能被修改。


stl的公共特性

1)拷贝构造函数都是采用浅拷贝,将源结点的值拷贝到目标结点的值,但是二者的地址和对象皆不同。但是swap则是直接将结点之间所维护的值替换如vec维护的是start,finish结点,llist维护的是node节点,deque维护的是start,end结点,与目标结点的值替换。

2) 函数的重载符一般都是用的字典序比较如==,<,>等都是采用的内容对比。

20、vector底层的实现

1)vector底层是采用alloc分配连续的内存template<class T, class Alloc = alloc>,interator 实际是typedef T * interator,所以其实是指针型的。vector采用start,finish,end_of_storage来分别代表内存的起始地址,实际使用的结束地址,实际分配的结束地址,finish=start+n,因此起始finish是指向最后一个元素的后面那个地址,size()返回的是目前实际容量(finish-start)而capacity则是返回的实际分配的容量(end_of_storage-start),max_size()则是size_t(-1)/sizeof(T),能存储的最大个数,默认的map这三个值都为0。

2)begin()、end()、rbegin(),rend()、[]有iterator类型和const_iterator两种类型,rbegin()返回的是finish,rend()返回的是start。但是reverse_iterator 在重载*的时候会先--再取值,所以*rbegin()返回的是最后的一个元素的值。front()、back()有reference和const_referencefront返回的是引用或者常引用,back()则是返回end()-1的引用或者常引用,记住在引用rbegin时需要声明类型为reverse_iterator。

3)vector的析构函数会先对所有的存储元素调用destory:相当于调用析构函数但是没有释放内存,最后再调用deallocate释放所有的分配空间,虽然调用了destory但是如果存储的是对象的指针,并没有释放其内存也没有调用其析构函数,因此如果存储的是对象的指针还需要自己手动去调用delete释放,delete=destroy+deallocate,vector内存是释放是采用一个局部临时变量swap将所有的start,finish全部置为0,然后局部变量则获取原vec的变量再调用析构函数释放空间。

4)vec的insert和push_back都会判断当前内存使用量和内存实际分配值,若超过了则重新分配内存一般按两倍扩展。insert返回的还是原来position的地址,其它元素往后移一个位置。pop_back:finish前移动,然后调用destory调用析构函数,erase也是将position后面的元素前移一个,finish--然后调用destroy(finish),clear也都是同样调用erase(begin(),end())。

5) resever函数只有在capacity<n时才会重新分配内存;resize(n,k)函数当size()> n时则删除第n+1到末尾;否则后面不够的补k值,若resize(n)则补T()。

6) ++重载符self operator++(int) 等价于a++;调用方法:a.oprator++(0)返回的是当前iterator的值,但是++(*this)其内部的指针还是移动了,这就是a++与++a的区别,stl中++其实就是++a。

7) stl中所有的迭代器默认构造函数都是将其内部遍历初始化为0,或者空。

8)内存的释放,vec的析构函数会先将容器中的结点调用destroy,启动析构函数,然后再释放内存,因此可以采用vector<int>().swap(vecInt);通过临时变量A(),由于没有用其它变量保存,此语句结束后将调用析构方法,将其内存释放。而vecInt则是初始化为默认初始化。

9)vector在erase时,其实是将该地址的后面数据往前移一个位置,然后删除最后一个位置,finish--。

10)容器的push_back都是调用浅拷贝一个对象然后放到容器中,因此比较占内存,如果数据是不变的则可以采用直接拷贝指针,这样可以节省内存空间

11)sort方法采用的是快排,因此需要随机访问,必须是序列容器而非关联容器


20、deque双向队列

1)内存分配为数组+固定大小的内存区可以参考拉链法,初始化为8个大小的数组,为了以后的前后增长会将数组的指针first设置在数组(start和end)的中间位置,这样可以减少内存的重新分配。对应的数组元素指向的是另外一块分配固定大小的内存区首地址。而且这块内存区由iterator保存其first,cur,last以及map值。且start.cur = map的值,end.cur=对应填充后num%size+end的值,这样一开始就从中间插入。

2)deque若从两头插入和删除是最快的可以根据start和end的cur去找到其位置,插入时若内存不够则重新分配内存,但是如果从中间插入则需要往数据少的部分去移动数据。因此慢。

3)deque也可以使用at()或者[]来随机访问。但是效率低于vec。通常用于在头和尾进行删除和增加的操作。虽然内存不是连续分配的,但是其重载了++,当到尾端时会跳到下一个端的初始位置,因此看书去是连续的。

4)resize若n<size(),则从后面截断

5)deque首先是分配map类型为T ** 指向val的指针的指针,开始分配大小为8的指针数组,若以后扩容时则重新分配map以及对应的数组。但是iterator则是向数据量少的那边扩充,因此可能会失效。

20、stack

1)stack是栈,先进后出,其内部维护的是一个deque,只不过deque是双端的,而stack则只用了其end端,所以有stack的pop=deque的pop_back() push=push_back(),top = back();

2)不过stack没有构造函数。


20、queue

1)queue是队列,先进先出,其内部也是维护的是一个queue。

2)front、back调用queue的;push则调用的是push_back();pop 则调用的是pop_front() 队尾追加元素和访问队尾元素, 在队头获取和移除元素

3)priority_queue是一个优先级队列。只提供了top(),push会排序,pop获取优先级最高的。


20、slist

1)slist采用的是一个单链表,其维护的是一个头结点,其数据为data为T(),next=0。数据区的数据可以忽略,纯粹为了做基数。

2)单向链表,反转时调用reverse(node->next);

3)和list很多方法类似但是list是双向环链表,slist则是单向的链表。

20、pair

主要template <class T1, class T2> 其中包含了first和second成员变量。pair()默认构造函数:first(T1()),second(T2());pair(T1,T2)则是将T1,T2分别赋值给first,second。其中的==和<、>都是用字典排序顺序。make_pair(t1,t2)则是调用了pair(t1,t2)。比较简单

21、list

1)list是一个双向链表组成的环,其中含有一个node作为成员变量作为头结点,其数据区为空,初始化时,pre、next都指向自身;而list的begin为node->next,end为node本身。rbegin为end(),rend为begin()。insert(pos,val)生成一个node(val)插入pos前面。push_front()=insert(begin(),val) push_back = insert(end(),val) 。pop_front() = erase(begin()) pop_back() = erase(--begin)

2)==重载:从头结点开始遍历,比较两个结点的val是否相等。>重载:利用字典顺序。

3)list的析构,以begin为起始点,next遍历每个结点,进行析构和释放内存,最后剩下头结点。并且让next和pre指向自身。最后再删除头结点。

4)list的复制,二者从头开始赋值,浅拷贝,直到其中一个到达了结尾,如果左边list长度小于右边的则insert,若大于则erase。erase采用的是[ )左闭右开。

5)uniq是判断相邻的两个结点若相同则删除前着。因此只有排序好的list才能用uniq

 6) list的reverse采用的是遍历节点每次将节点插入begin,因此可以逆序。template <class T, class Alloc> template <class Predicate>  void list<T, Alloc>::remove_if(Predicate pred) 这里慢慢体会,包括多个template。 

20、set、mutiset、map、mutimap

1)底层都是以红黑树为基础,维护一个红黑树,map、set不可以重复kye,如果重复插入key则返回该key的地址已经插入标识位pair<iterator,bool>,而mutiset、mutimap则直接插入,并且直接返回iterator。

2)其中map的[]操作 则是根据pair<key,val> val 的 keyofval()方法获取key,然后根据kye去查找,若找到相同的key值则视为已经找到并且返回该iterator和false。否则新插入一条val=<key,T()>的数据。返回iteraot.first.second的引用

3)其中set和map其实都差不多,只是set是以单个class作为val 而map则是以pair<key,val>作为val。

4)map和set是红黑树,新增和删除时只会影响当前节点,会调整树,但是不会重新分配内存,因此在erase时需要记住下一个节点即erase(it++)

5)由于插入时是按照红黑树的大小比对来的,因此在插入结构里面需要重载<操作符


20、hash_set、hash_mutiset、hash_map、hash_mutimap

1)内部采用的拉链数据结构(hashtable),由vector生成n个桶,然后根据hash(key)%n去存放位置,和duque有点类似,但是duque则是采用固定大小的连续地址。而hashset则是采用非固定大小的单链表。

2)同样重载了++操作,当前位置为第n个桶的末尾时,移到第n+1个桶的第一个头结点。

3)hashset、hash_map不可以允许相同key的值,而hash_mutiset hash_mutimap则可以。

4)和map以及mutimap类似对于[]操作,如果存在key则返回改结点的迭代器,否则进行新增。

5)mutilmap不可以采用[]方法,因为其key可以重复。

6)hase_set在添加新元素时是采用在头结点那里新增的,pparry[]的值是随时跟新的。

7)由于hash_set是为了避免同一个key出现,因此在key的结构体里面需要重载==操作符

8)

21、类的析构函数在栈空间内当生命周期结束时会自动调用其析构函数,而堆空间内则需要程序员手动去调用delete才会调用,程序结束时未释放的内存有擦做系统自己去释放。


22、友元函数&友元类

定义在类中,friend+[class::]函数,其实现可以在类内也可以在类外,另外友元函数的定义是为其它类访问本类的私有变量的接口,因此跟类本身没有关系,可以放public和private处。一般用于类运算符的重载和两个类其中一个类需要访问另一个类的成员。


23、二叉树的定义

1)二叉树只有一个根节点,另外度数为0的叫叶子节点,其它的叫分支结点。

2)完全二叉树:从左到右排满,最后一层可以不满。

3)满二叉树:只有度数为2和0的完全二叉树。


24、makefile

可以自动化编译,类似于shell脚本,可以按照你自定义的规则去编译。

1)makefile通过include来引用外部文件 #注释(类似shell)include命令会去检查文件是否存在,不存在则编译终止,可以用-include来强制执行,通用include也可以引用外部环境变量如include $(mk)通过export mk=xx来指定文件。

2)make的命令为make 选项 目标 若make不带目标则 makefile是以第一个: 作为其终极目标,而且只执行这个目录下依赖的东西,下一个伪目标或者没有在依赖里的其它目标不会被执行,因此需要编译成可执行文件、静态、动态库的需要将其放到最前面,clean不成文的规定放末尾。

3)makefile中可以用通配符*如*.o ,通配符用于变量中不可以展开需要objs= $(wildcard *.o)由关键字wildcard指出。objs = $(patsubst %.cpp,%.o, $(wildcard *.cpp)) 调用了patsubst和wildcard两个makefile函数。

4)环境变量MAKEFILES对所有当前系统下的make都通用等价于makefile中新添加了一行 include $(MAKEFILES),但是如果其指定的文件不存在,make不理会继续执行,与内部引用的区别就在此。

5)文件搜索,makefile中的有些源文件或者.o文件可能不在当前目录下,因此需要通过环境变量VPATH=src:../dir去指定中间用冒号分割。默认会先从当前目录优先搜索。

当然也可以在makefile文件中利用关键字vpath pattern dir pattern可以用%.h 匹配其中%是0,或者多。

6)伪目标:.PHONY被声明为伪目标的目标不会根据隐含规则去创建这个目标,而且伪目标是可以不需要生成对应的目标。一般用于作为一个make 目标的命令执行。伪目标可以依赖其它的目标。

7)objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@

其中$<表示为所有依赖目标foo.c bar.c而$@ 则是foo.o bar.o

files = foo.elc bar.o lose.o 

$(filter %.o,$(files)): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
emacs -f batch-byte-compile $<

fillter函数的调用。

8)makefile中的shell命令,如果想要出错了也继续执行忽略错误则用-rm -rf 也可以加选项i make -i有些命令需要写在一行并且用;隔开,比如cd dir1;pwd

9)嵌套make,有些工程需要跳到不同的目录下去进行make则需要用到 make -s(silent) 可以去掉很多冗余的信息

subsys:

cd dir1 && make

或者 make -C dir1

10)makefile中变量的使用变量申明时不需要加$,但是引用时采用$但是如果是循环则需要$$第一个$取其下标,第二个才是真正取其值。

11)key:=$(val) 其中(:=)代表只取val当前的值,就算后面val变化也不会变。但是如果是kye=$(val)如果在取key的值时,val已经变化了,那么key也会变,可以理解为是引用

12)makefile有默认的隐含规则,当然也可以被重写,当重写时有单后缀%.o:%.c :xxx其右边可以有依赖的文件,同时也支持老式的双后缀.0.c:但是右边不能有依赖文件。因此一般用单后缀,同时为了makefile识别后缀可以采用.SUFFIX这个伪目标。

13)makefile含有隐含使用规则如AR= ar以及ARFLAGS=cv CPP=$(CC) -E CXX=g++.

14)makefile实践:gcc编译成EXE文件或者静态,尤其是静态文件,动态文件都是以.o文件为基础的,因此在makefile中写ar libtest.a objs 只需要加objs不能加-I -L等参数了,而编译EXE文件时如果有些文件还没有编译成.o文件,可以通过-I -L去找对应的文件。另外如果是某一目录下的文件可以直接用变量val=bin/xx。


25、c编译器的内部链接和外部链接

1)c或者c++的源文件在链接时有些变量或者类的定义在不同的源文件中同名但是不冲突叫内部链接,冲突则叫外部链接。类的声明、结构体、枚举等都是内部链接

2)一般外部链接比较少,只有在全局的非静态类以及类的非内联函数以及类的静态变量会冲突。外链函数只允许其中的一个定义在其它类中,因此可以保证二义性。

3)申明不会分配空间所以申明是可以重复的,但是定义则不行,类里面的函数或者变量都是声明,只有定义的时候才会分配空间,但是类中的声明变量不能重复,因为在类定义时就会冲突。

4)内部链接的如果需要影响其它文件则需要加入到头文件中。同样最好不要讲外链函数或者变量引入头文件中,可以采用inline或者static 内联化。

5)自由函数是指非友元函数已经类函数


26、内联函数和宏的区别?
内联函数和宏的区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。你可以象调用函数一样来调用内联函数,而不必担心会产生于处理宏的一些问题。内联函数与带参数的宏定义进行下比较,它们的代码效率是一样,但是内联欢函数要优于宏定义,因为内联函数遵循的类型和作用域规则,它与一般函数更相近,在一些编译器中,一旦关上内联扩展,将与一般函数一样进行调用,比较方便。


27、优先级与运算符扩展以及位运算

1)unsigned char b = ~x>>4+1 这里>>的优先级比+低,抑或比>>的优先级要低;另外会将x先扩展为int型所以取反时高位变成了1

2)unsigend int i = 0xfffffff3。char *p=(char*)&i。拆分为unsigend int *q= &i;p=(char*)q。所以p指向的地址还是i的结构printf("%x“,*p),还是从i的地址开始取。

3)x&y + ((x^y)>>1) 是相同的减半,不同的减半,因此是求平均值。

4)a与b交换的两种方法a=a+b;b=a-b;a=a-b; a=a^b;b=a^b;a=a^b。这里两个因子抑或得到的值再抑或其中一个因子可以得到另外一个因子。

5)求一个结构体中某一个变量距离结构体的地址偏移量:(size_t)&((STU*)0->item),将0强制转为STU结构体指针,然后取其变量的地址即为偏移量。

6)字节对齐可以用 #para pack(n)来指定字节对齐数,结构体中静态变量所占的空间为0,。

7)空类所占的空间为1.

28、宏,函数、内联函数的对比

1)与宏相比:宏是在预定义的时候展开,二内联函数是真正的函数在类中定义或者inline的函数申明定义。宏主要是存在二义性对于复杂的运算不适合。

2)与普通函数相比:内联函数效率更高,不需要中断调用,可以和宏一样类似的将代码展开。不需要跳转。一般适合代码少的,如果比较多编译器会放弃。

29、数组与指针

int arr[4] 中arr与&arr的值(地址)都是同一个,不同在于arr+1与&arr+1,前者是以int类型为单位偏移,后者则是以arr[4]作为单位。

30、c++异常类

c++由关键字throw作为异常的抛出,throw()代表不抛出任何异常,throw(A,B)代表只抛出异常类A或者B 不加throw则代表可以抛出任何异常。若不符合上述规则则直接core。c++抛出的异常如果在当前函数内未捕获则会让调用该函数的函数来处理,若未处理则一层层往上抛,直到main,否则报core。异常类可以用catch(...)代表捕获任何类型的异常。

异常类主要是为了捕获异常信息和打印异常信息代码参考:

TException::TException(int code,const char* fmt,...)
{
    va_list     ap;
    va_start(ap,fmt);
    vsnprintf(m_msg,sizeof(m_msg),fmt,ap);
    va_end(ap);
    m_code = code;
}

void TException::print()
{
    printf("Exception!!Code[%d],Msg[%s]\n",m_code,m_msg);
}

31、

网络编程,tcp有缓冲区,因为tcp有超时重发,需要将进程字节发送的内存拷贝到内核套接字的缓冲区中,以免超时重发,SND_BUFF是tcp发送的缓冲区,当不能容纳时则阻塞,tcp发送为字节编序,重复删除,还有流量控制,

服务端若不调用bind绑定监听套接字地址和端口则内核自动为其绑定,但是如果想查看绑定的地址需要用getsockname;netstat -a可以查看tcp那些端口在监听,状态图。

tcp的time_wait状态是为了保证客户端万一发送ACK失败,重新发送。

tcp服务器中如果fork子进程需要close(listenfd)父进程需要close(clifd),其中close只有其计数器变为0时才会发送FIN信号,发送FIN,则read返回0。如果想要发送FIN需要用shut。父进程和子进程都需要记得关闭不必要的句柄以免资源浪费,另外子进程结束时,若父进程没有捕获sigchld则会变成僵尸进程,需要捕获并且用waitpid轮询,因为信号是不排队的,因此信号处理函数一般只处理一次。

慢系统调用是指那些connect,read等阻塞的系统调用如果中间信号捕获,那么该系统返回报错并且错误为EINTR,因此需要考虑轮询。但是connect比较特殊,重新调用时会报错。需要关闭改套接字重新socket获取套接字。

connect失败有大概3中,第一种在同一个网段但是主机不存在,超时,第二种服务端满了或者未有开启则返回RST(复位),第三种不在同一个网段而且无法访问则主机不可达,connect是阻塞的,可以设置SO_LINGER,若服务器不能接收则客户端向服务器发送RST

参数SO_REUSEADD是指当服务器端口忙并且处于TIME_WAIT时可以重用端口,当服务器由于这种情况终止需要立即重启时有用,但是如果非TIME_WAIT状态则会报错,TIME_WAIT存在的意义,实现全双工关闭,允许旧的重复分节消失,

close与shutdown的区别:close对连接数-1,并且为0时才发送FIN,close关闭套接字不能读也不能写,而shutdown则可以选择,另外会发送FIN

当向一个已经主动关闭的服务端发送数据时会返回RST到套接字,如果继续调用write写会发送SIGPIPE,此信号默认会终止程序

多宿主机是指的同一个主机上有多个网络接口(网卡)上面有多个ip,当服务器采用统配符地址时,则可以对应多个ip的服务 

控制connect超时时间的三种方式,第一种设置alarm时钟控制,第二种设置connect为非阻塞,并且采用select,当connect返回-1时才采用select,当连接好但是对方没有发送数据时是可写,如果有数据则是可读可写,如果发生错误则也是可读可写,所以判断可以写时,并根据getsockopt获取erro值,判断是否错误值,另外则是设置setsockopt的SO_SNDTIME来控制

32、linux内核

linux主要分为用户空间和内核空间(用户态和内核态),用户空间是指每个进程分配的空间,进程通过系统调用访问内核空间,用户不可以直接访问内核空间,每个进程都是虚拟地址空间,而实际物理地址称为页,虚拟地址到物理地址的映射称为页表,因此父子进程同一个变量的虚拟地址是可以相同的,但是实际物理地址不同。在unix系统中万物皆文件:everything is file,系统同一时刻有多少进程在同时运行决定于cpu的个数,进程之间的切换叫内核切换,进程之间执行时长优先级叫内核调度,进程切换时,会先保存改进程当前执行信息,待激活时再恢复,fork与exec:fork是拷贝一份虚拟地址空间给另外一个进程,但是exec则是将另外一个程序搬到现有的虚拟空间,并移走当前的虚拟空间内容,因此进程id没有变化。

vfork:只会拷贝页表,但是不会申请实际的物理地址,相当于与父进程共享实际的内存地址,在子进程调用exec或者退出时,父进程阻塞,设计初衷就是为了避免资源的浪费,因此在调用比如exec的函数时比较合适。但是fork采用写时复制,如果在子进程中如果直接调用exec,vfork没有优势可言,差不多一个意思,只是vfork可以保证子进程先执行。

33、数据库优化

1)从表的设计出发

1、在设计时可以考虑适当的范式,减少数据冗余,但是也不是绝对的,适当的数据冗余可以减少表与表之间的联合查询,如name和code。

2、在经常查询的字段上建立索引

3、分区(按天来分区,按季度分区)按性别分区

4、分表(水平拆分如将月表修改为日表,垂直拆分:将一张大表里多个字段拆成少个字段)

5、字段类型的选择主键用自增还是GUID,以及预判每个字段类型的大小,可以节省空间

2)从编码出发

1、绑定变量进行软解析

2、进行批量更新或者插入

3、优化sql尽量避免全表扫描

4、编写存储过程

3)从硬件角度

1、分配更多的内存和购买更好的设备

2、搭建集群进行读写分离

3、缓存、集群、读写分离。

4、cpu、内存、磁盘、网络等硬件设备

34、svcbase的容灾机制

每隔一周期定时刷新数据到磁盘全量(镜像)然后将增量文件删除(RDB)。另外处理的文件进行增量保存,万一内存数据库挂了,则从全量+增量恢复(AOF)。

当内存挂掉时,可以从全量的文件镜像到内存再刷新增量文件。每一个源文件对应一个增量文件。所以增量文件还是比较多的。而且从内存镜像到文件则是直接写,但是从文件load到内存包括后面的增量文件处理则是用updateItem处理:因为该函数保证了内存主键的唯一性,即不存在则更新,存在则update,所以前后是不矛盾的

35、redis集群

redis集群需要多台主节点,而且每个主节点有多个从节点,主节点主要进行写,从节点主要进行读,主节点通过主从复制与从节点达到数据一致性,主节点之间通过分配hash slot哈希槽,对数据进行分片,如果主节点挂了,则从节点替代主节点,或者从多个从节点中选择一个作为主节点,可以增加主节点或者删除主节点这样会设计哈希槽的重新分片。从节点负责做数据库持久化,减轻主节点的压力,主从节点的主从复制机制:首先建立连接,客户端向服务器发送sync命令,如果有其它子进程还在进行主从复制则先等待,否则fork一个进程来镜像,服务端将当前的全量镜像文件发送给客户端,然后在客户端将文件复制到内存时,服务端的增量文件则是通过发布命令到客户端进行增量更新。

各个主节点之间是可以相互通信的,因此可以当发送给某一个主节点时,会根据哈希槽去分配到其它主节点上存储数据

redis的负载均衡

可以用haproxy做负载均衡器,提供一个虚拟IP,进行负载均衡,主要的负载均衡配置有轮询、随机、

36、线程同步方式

互斥量(mutex)、读写锁(rdlock)、条件变量(cond),而多进程的同步方式有信号量,P、V

37、linux的组成

1、内核包括内存管理,文件系统管理,进程管理,信号管理,网络管理

2、shell,用户与内核交互的接口

3、文件系统

4、应用程序

38、linux系统调用和库函数的区别

linux系统调用是可以理解为内核的api,面对的对象是硬件,而库函数则为应用开发的api,面对的对象是应用,可以理解为库函数是在系统调用之上封装了一层,这样做的目的主要是可以移植只要编译器相同则库函数可以通用,另外就是为了提高效率,比如缓存,fwrite和write的区别。所以用time命令常常可以看到用户时间和系统时间,区别就在这里

39、检查另外一个系统的主机或者端口是否运行

telnet ip 【port】

40、面向对象的思想

封装:隐藏实现细节,继承:类的重用,多态:接口的重用。

c++类默认自带的四种方法:构造,析构,赋值(A & operator=(const A&)),拷贝构造:A(const A&a)(函数参数的值传递、赋值返回的非引用)。B b类的构造,先调用基类的构造然后调用子类的构造,类的析构,先调用子类的析构再调用父类的析构,A *p = new B()非多态析构函数,只调用父类的析构,否则调用子类的析构(先析构子类再父类)

stl中是set如果需要存储对象,那么该对象需要实现operator<这样它才能根据红黑树来判断储存位置,以及是否重复了。以及sort方法只能针对序列容器,当然list自己实现了sort方法:list.sort();

容器包括序列容器(可以随机访问的例如数组的string、deque、vector)和关联容器(map、set)。

41、c里面的voliate

此词代表是易变的,就是随时可能会被修改的,因此希望编译器不需要优化,直接从内存中读取而不是寄存器中,主要场景如嵌入式编程时,中断服务修改了改变量,但是如果没有用voliate修饰,则其还是从寄存器中读取数据,这样就等于中断程序不起作用了。还有多线程计数器累加的时候也是同样的,

42、drop、truncate和delete的区别

delete 后面可以加where条件,数据可以回滚,只清除数据,表结构保留,drop是ddl表结构和数据全部删除,truncate ddl语言不可以回滚,同时表数据清理,表结构保留。

43、数据库语言

ddl:数据库定义语言:create,truncate、drop、alter 数据库操作语言:dml:update,insert,delete,数据库查询语言dql:select,sql函数 case when 1=1 then “yes” else “no" end | case "1" when "1" then "yes" else "no" end sql函数oracle函数(round、trunc、decode(值,值1,返回值1,值2,返回值2.。。默认值)select xxx from tb where  group by having order by

44、sql语句

普通子查询和关联子查询(子查询根据父查询里面的值作为条件,先遍历父查询)如exits则必须是关联子查询 

45、表的子查询可以用join,create tb as select * from tb1 where 1=2,可以建立一个和原来表的结构相同的空表,insert into tb select * from tb2|select * into tb2 from tb1 where可以复制一个表。

46、悲观锁和乐观锁的优缺点

悲观锁包括行级锁、页级锁、读写锁等通过排它锁来保证数据的一致性,和避免更新丢失,但是缺点也比较明显,就是由于长时间锁效率低下,如火车票的出售如果采用这种可能就是会等半天,而乐观锁的优点则是并发效率高,到最后提交的时候根据版本去比较,锁的粒度比较小,但是会出现更新丢失,比如数据已经被更新了(火车票被别人抢了)你就白等了。

47、事物的隔离级别

脏读:一个事物读取到了另外一个数据未有提交的数据,等其回滚时,脏读(如有人给你打了钱,你发了货物,结果别人回滚了你却发货了)

不可重复读:一个事物中读取两次但是其中有另外一个事物进行了更新,导致读取不一致,不可重复读。

幻读:事物读取一个记录的次数两次,但是中间插入了一条导致两次读的不一致。

一般的数据库会隔离脏读:不允许脏读,即等事物提交之后再读。

48、索引

主键和外键会默认建立索引,非经常查询字段如果建立索引反而影响效率

49、视图

虚表是实表的映射,可以隐藏某些列,类似于函数的接口,是一个中间集,简化查询。

50、ip是无状态、无连接、不可靠的服务(简单、高效),需要上层tcp去保证可靠性(乱序和丢失)。tcp是面向连接、可靠的字节流,因此不适用多播和广播。

而udp则是面向无连接的,而且是数据报(和字节流的区别在于数据报的读写次数是相同的,因此如果recvfrom程序缓存区比报文的大小还小则会出现截断的情况,而字节流由于存在缓存所以可以不同)。tcp接受缓存区会对报文段进行重新排序放入缓存区中。

tcp的可靠性表现在:发送应答、和超时重传、乱序重排、重复丢弃。tcp是全双工的,所以关闭一方,只是代表完成了数据的发送,而数据的接受还没有完成。可以继续接受对方的数据。tcp同时会超时重连。time_wait期间,端口和ip被占用,不可以重连,让下一个连接无法接受上一个连接的数据,除非设置了setopt(so_resuseaddr)这样的话,timewait期间可以重连

复位信号rst的三种情况1、客户端重启连接2、主机端口没有开3、主机处于time_wait4、对一个服务端关闭了,客户端半连接,继续往服务端发送信息

51、select和epoll的区别

1、epoll对FD没有限制2、select对就绪事件需要轮询检测而epoll则是采用回调直接将就绪事件句柄放入了事件队列中,时间复杂度为O(1),会随着句柄的增加而线性下降3、epoll可以采用ET边缘触发模式更高效,而select只能采用LT模式。4、事件通知时select需要从内核拷贝到用户态,而epoll则是采用mmap共享的不需要拷贝,效率更高

52、往一个读端关闭的管道或者socket写数据会sigpipe信号,默认结束进程。

52、spark

Hadoop分片存储,mapreduce过程split->mapping->shuffring->reduce,每次讲中间迭代结果存储在磁盘中,

Hadoop三大应用:批处理(报表),交互式查询(sql),实时计算(汇总)

spark的底层存储层为:hdfs或者hbase,资源调度层:yarn,内核层:sparkcore,应用层:sparksql,sparkstream等,spark将中间迭代结果放在内存,内存不够时才会写入磁盘,支持java、Python、r、Scala语言,

53、分布式

对硬件的要求比较低,性能和可靠性比较低可以采用刀片机;可以进行横向可扩展,分布式不存在单点失效(多台其它机器备份数据3份)分布式系统之间减少通信开销(网络传输)分布式做成无状态。c(一致性)a(读和写都能返回结果,非阻塞)p(集群之间节点不能通信了是否还能正常提供服务)理论,分布式的本质:多个组件分布在不同的机器上,通过网络协同工作完成。分布式面临的问题有:集群中备份数据与主数据一致。可用性,分区容错性

54、linux

应用软件、操作系统、硬件组成计算机,VMware是虚拟主机,但是它没有操作系统是一台裸机,需要安装centos等才能算有操作系统

55、hdfs

shell操作:bin/hdfs dfs  -ls 【-R】 hdfs://webpath:port/(绝对路径)或者直接用/ 或者用hdfs dfs -ls x(当前路径)

bin/hdfs dfs -mkdir  【-p】 /newdir  创建目录,或者-mkdir -p /dir1/dir2/dir3

bin/hdfs dfs -put localpath hdfspath 在hdfs文件中保存了3份数据   hdfs dfs -get hdfspath localpath

hdfs dfs -text hdfspath,hdfs dfs -rm -rf hdfspath。

/etc/sysconfig/network设置主机名,然后通过hosts去配置主机和ip

hafs不适用存储小文件,一次写入多次读取,处理非机构化数据,中心化(namenode存储元数据在内存中block和datanode的映射关系、datanode存储在):复杂度小。

56、hbase的入库方式有两种:第一种由mapreduce在hdfs上进行批量入库,第二种从本地文件进行批量入库。

57、惊群现象:就一个食物抛出,其它东西对其进行竞争,比如一个目录有多个进程进行读取,就会存在惊群现象,解决方法:对输入目录进行加锁,每个子进程读取的时候加锁,读取完了将其扫描到的文件放到自己的子目录单独处理,或者部署多套系统(每一个输入目录对每一个下下游的输出目录),解决目录争夺的问题。另外tcp并发服务器中主进程中如果每个子进程都进行绑定和监听的话,当客户端进行connect时就会出现惊群现象,因此采取的措施是:主进程进行监听,子进程进行处理

58、网关和路由器的区别

网关是两个不同网络之间(通过子网掩码)如果需要进行访问,在没有路由器的情况下需要先给某台机器的网关(只有一个,而且是该网络下的一个ip)然后转发给目的主机上的网关,而路由器则是通过路由表寻找源和目的主机之间最优的路径。路由器中可以配置网关。而且路由器不是附属在某一台主机上的。因此一个路由器可以配置不同的网关。路由表中也有一个默认的网关default:当所有都没有匹配时走该网关。route add --default gw ip另外一个主机上可以由多个网卡,可以通过ifconfig eth0 ip netmask添加ip;通过route add -net ip netmask gw ip来配置访问改网段下的ip通过网卡。同一台机器上的不同网卡可以配置不同的ip但是同一网卡上的ip需要是在同一个网段。




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值