您的位置:首页 > 职场人生

面试常见题(非算法)

2015-09-15 11:33 525 查看

linux和os:

netstat tcpdump ipcs ipcrm (如果这四个命令没听说过或者不能熟练使用,基本上可以回家,通过的概率较小 ^_^ ,这四个命令的熟练掌握程度基本上能体现面试者实际开发和调试程序的经验)

cpu 内存 硬盘 等等与系统性能调试相关的命令必须熟练掌握,设置修改权限 tcp网络状态查看 各进程状态 抓包相关等相关命令 必须熟练掌握

awk sed需掌握

共享内存的使用实现原理(必考必问,然后共享内存段被映射进进程空间之后,存在于进程空间的什么位置?共享内存段最大限制是多少?)

c++进程内存空间分布(注意各部分的内存地址谁高谁低,注意栈从高道低分配,堆从低到高分配)

ELF是什么?其大小与程序中全局变量的是否初始化有什么关系(注意.bss段)

使用过哪些进程间通讯机制,并详细说明(重点)

makefile编写,虽然比较基础,但是会被问到

gdb调试相关的经验,会被问到

如何定位内存泄露?

动态链接和静态链接的区别

32位系统一个进程最多多少堆内存

多线程和多进程的区别(重点 面试官最最关心的一个问题,必须从cpu调度,上下文切换,数据共享,多核cup利用率,资源占用,等等各方面回答,然后有一个问题必须会被问到:哪些东西是一个线程私有的?答案中必须包含寄存器,否则悲催)

写一个c程序辨别系统是64位 or 32位

写一个c程序辨别系统是大端or小端字节序

信号:列出常见的信号,信号怎么处理?

i++是否原子操作?并解释为什么???????

说出你所知道的各类linux系统的各类同步机制(重点),什么是死锁?如何避免死锁(每个技术面试官必问)

列举说明linux系统的各类异步机制

exit() _exit()的区别?

如何实现守护进程?

linux的内存管理机制是什么?

linux的任务调度机制是什么?

标准库函数和系统调用的区别?

补充一个坑爹坑爹坑爹坑爹的问题:系统如何将一个信号通知到进程?(这一题哥没有答出来)

c语言

1、宏定义和展开(必须精通)

简单宏定义:#define N 100

类型重命名: #define BOOL int

控制条件编译: #define DEBUG

预定义宏:

LINE 被编译的文件的行数

FILE 被编译的文件的名字

DATE 编译的日期(格式”Mmm dd yyyy”)

TIME 编译的时间(格式”hh:mm:ss”)

STDC 如果编译器接受标准C,那么值为1

使用带参数的宏替代实际的函数的优点:

1) 、 程序可能会稍微快些。一个函数调用在执行时通常会有些额外开销——存储上下文信息、复制参数的值等。而一个宏的调用则没有这些运行开销。

2) 、 宏会更“通用”。与函数的参数不同,宏的参数没有类型。因此,只要预处理后的程序依然是合法的,宏可以接受任何类型的参数。例如,我们可以使用MAX宏从两个数中选出较大的一个,数的类型可以是int,long int,float,double等等。

带参数的宏的一些缺点:

1) 、 编译后的代码通常会变大。每一处宏调用都会导致插入宏的替换列表,由此导致程序的源代码增加(因此编译后的代码变大)。

2) 、宏参数没有类型检查。当一个函数被调用时,编译器会检查每一个参数来确认它们是否是正确的类型。如果不是,或者将参数转换成正确的类型,或者由编译器产生一个出错信息。预处理器不会检查宏参数的类型,也不会进行类型转换。

3) 、无法用一个指针来指向一个宏。如在17.7节中将看到的,C语言允许指针指向函数。这一概念在特定的编程条件下非常有用。宏会在预处理过程中被删除,所以不存在类似的“指向宏的指针”。因此,宏不能用于处理这些情况。

4) 、宏可能会不止一次地计算它的参数。函数对它的参数只会计算一次,而宏可能会计算两次甚至更多次。如果参数有副作用,多次计算参数的值可能会产生意外的结果。

#运算符

将一个宏的参数转换为字符串字面量(字符串字面量(string literal)是指双引号引住的一系列字符,双引号中可以没有字符,可以只有一个字符,也可以有很多个字符)

【实例】

#define PRINT_INT(x) printf(#x " = %d\n", x)
//x之前的#运算符通知预处理器根据PRINT_INT的参数创建一个字符串字面量。因此,调用

PRINT_INT(i/j);
//会变为
printf("i/j" " = %d\n", i/j);
//在C语言中相邻的字符串字面量会被合并,因此上边的语句等价于:
printf("i/j = %d\n", i/j);


#define STR(x) #x

int main(int argc char** argv)
{
printf("%s\n", STR(It's a long string)); //注意:并没有双引号,输出 It's a long str
return 0;
}


##运算符

“##”被称为 连接符(concatenator),它是一种预处理运算符, 用来把两个语言符号(Token)组合成单个语言符号

【实例】

#define MK_ID(n) i##n
int MK_ID(1), MK_ID(2), MK_ID(3);
//预处理后声明变为:
int i1, i2, i3;


#define GENERIC_MAX (type)           \
type type##_max(type x,  type y)     \
{                                    \
return x > y ? x :y;             \
}
GENERIC_MAX(float)
//展开为
float float_max(float x, float y) { return x > y ? x :y; }


宏的通用属性:

1) 、宏的替换列表可以包含对另一个宏的调用。

2) 、预处理器只会替换完整的记号,而不会替换记号的片断。因此,预处理器会忽略嵌在标识符名、字符常量、字符串字面量之中的宏名。

3) 、一个宏定义的作用范围通常到出现这个宏的文件末尾。由于宏是由预处理器处理的,他们不遵从通常的范围规则。一个定义在函数中的宏并不是仅在函数内起作用,而是作用到文件末尾。

4) 、宏不可以被定义两遍,除非新的定义与旧的定义是一样的。小的间隔上的差异是允许的,但是宏的替换列表(和参数,如果有的话)中的记号都必须一致。

5) 、宏可以使用#undef指令“取消定义”。#undef指令有如下形式:

#undef N

会删除宏N当前的定义。(如果N没有被定义成一个宏,#undef指令没有任何作用。)#undef指令的一个用途是取消一个宏的现有定义,以便于重新给出新的定义。

参考:C语言中的宏定义

2、位操作(必须精通)

参考:C 位运算符 及有用位运算

3、指针操作和计算(必须精通)

4、内存分配(必须精通)

5、字节对齐

满足以下两点个:

1) 结构体每个成员相对于结构体首地址的偏移量都是本成员大小的整数倍,如不满足则加上填充字节;

2) 结构体的总大小为结构体最宽的基本类型成员大小的整数倍,如不满足则在最后加上填充字;

struct A {
int a;
char b;
short c;
};


分析sizeof(A):

首先第一个 int 为 4 。----

其次 char 为 1,char 成员相对于首地址的偏移量为4, 4是char 本身大小 1 的整数倍,所以可以接着安置。-----

然后 short 为2,short 成员相对于首地址的偏移量为5,5不是short 本身大小 2 的整数倍,所以在填充到6。-----X--

最后计算总大小,int 4 char 1 填充 1 short 2 = 8,8是最宽int 4 的整数倍。所以后面不再填充。

参考:字节对齐

6、sizeof必考

sizeof 计算字符串长度

char str1[20] = "0123456789";
char str2[] = "0123456789";

int a = sizeof(str1); //20  计算str1所占的内存空间,以字节为单位
int b = sizeof(str2); //11, 注意要还要加上 '\0'

int c = strlen(str1); //10, 注意 strlen 参数只能是char*, 且必须以'\0'结尾
int d = strlen(str2); //10


2、8、类空间大小

空类:1字节

基类对象的存储空间大小为:

非static数据成员的大小 + 4 个字节(虚函数的存储空间)

派生类对象的存储空间大小为:

基类存储空间 + 派生类特有的非static数据成员的存储空间

虚继承的情况下,派生类对象的存储空间大小为:

基类的存储空间 + 派生类特有的非static数据成员的存储空间 + 4字节(每一个类的虚函数存储空间。)

3、sizeof 计算联合体大小(同样需要字节对齐)

uniton u2
{
char a[13];
int b;
};
sizeof(u2) == 16;
//最大成员为13,但要按int的对齐方式(也就是要能整除sizeof(int) = 4,b必须放到离首地址偏移量为16的地方)


7、各类库函数必须非常熟练的实现

8、哪些库函数属于高危函数,为什么?(strcpy等等)

gets 最危险 使用 fgets(buf, size, stdin)。这几乎总是一个大问题!

strcpy 很危险 改为使用 strncpy。

参考:C语言中不安全的函数以及解决方案

c++

一个String类的完整实现

(注意:赋值构造,operator=是关键)

class MyString
{
public:
//构造函数
MyString();
MyString(const MyString&);
MyString(const char*);
MyString(const size_t,const char);
//析构函数
~MyString();
//属性
size_t length();//字符串的长度
bool empty();//字符串是否为空
const char* c_str();//返回C风格字符串的指针
//读写操作符
friend ostream& operator<< (ostream&,const MyString&);
friend istream& operator>> (istream&,MyString&);
//‘+’操作符
friend MyString operator+(const MyString&,const MyString&);
//比较操作符
friend bool operator==(const MyString&,const MyString&);
friend bool operator!=(const MyString&,const MyString&);
friend bool operator<(const MyString&,const MyString&);
friend bool operator<=(const MyString&,const MyString&);
friend bool operator>(const MyString&,const MyString&);
friend bool operator>=(const MyString&,const MyString&);
//下标操作符
char& operator[] (const size_t);
const char& operator[] (const size_t)const;
//赋值操作符
MyString& operator=(const MyString&);
//'+='操作符
MyString& operator+=(const MyString&);
//子串操作
MyString substr(size_t,size_t);
//添加操作
MyString& append(const MyString&);
//插入操作
MyString& insert(size_t,const MyString&);
//替换操作
MyString& assign(const MyString&,size_t,size_t);
//删除操作
MyString& erase(size_t,size_t);
private:
size_t strLength;
char* p_str;
};


虚函数的作用和实现原理

(必问必考,实现原理必须很熟)

虚函数的作用:

可以让成员函数操作一般化,用基类的指针指向不同的派生类的对象时, 基类指针调用其虚成员函数,则会调用其真正指向对象的成员函数,而不是基类中定义的成员函数(只要派生类改写了该成员函数)。

实现原理:

每个类用了一个虚表每个类的对象用了一个虚指针

class A
{
public:
virtual void f();
virtual void g();
private:
int a;
};

class B : public A
{
public:
void g();
private:
int b;
};


编译器为A类准备了一个虚表vtableA,内容如下:



B因为继承了A,所以编译器也为B准备了一个虚表vtableB,内容如下:



当有B bB;的时候,编译器分配空间时,除了A的int a,B的成员int b;以外,还分配了一个虚指针vptr,指向B的虚表vtableB,bB的布局如下:



当如下语句的时候:

A *pa = &bB;


pa的结构就是A的布局(就是说用pa只能访问的到bB对象的前两项,访问不到第三项int b)

那 么pa->g()中,编译器知道的是,g是一个声明为virtual的成员函数,而且其入口地址放在表格(无论是vtalbeA表还是 vtalbeB表)的第2项,那么编译器编译这条语句的时候就如是转换:call *(pa->vptr)[1](C语言的数组索引从0开始哈~)。

这一项放的是B::g()的入口地址,则就实现了多态。(注意bB的vptr指向的是B的虚表vtableB)



sizeof一个类求大小(注意成员变量,函数,虚函数,继承等等对大小的影响)

指针和引用的区别

★ 相同点:

1. 都是地址的概念;

指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。

★ 区别:

1. 指针是一个实体,而引用仅是个别名;

2. 引用使用时无需解引用(*),指针需要解引用;

3. 引用只能在定义时被初始化一次,之后不可变;指针可变; 引用“从一而终” ^_^

4. 引用没有 const(int & const ref = value; 错误),指针有 const,const 的指针不可变;

5. 引用不能为空,指针可以为空;引用被创建的同时必须被初始化

6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;

typeid(T) == typeid(T&) 恒为真,sizeof(T) == sizeof(T&) 恒为真,但是当引用作为成员时,其占用空间与指针相同(没找到标准的规定)。

7. 指针和引用的自增(++)运算意义不一样;

8. 不能有NULL 引用,引用必须与合法的存储单元关联(指针则可以是NULL)

string& rs;// 错误,引用必须被初始化
int const & ref = value;
const int & ref = value;
int  & const ref = value;  //错误


多重类构造和析构的顺序

stl各容器的实现原理

STL共有六大组件

1、容器。2、算法。3、迭代器。4、仿函数。6、适配器。

容器

序列式容器:

vector-数组,元素不够时再重新分配内存,拷贝原来数组的元素到新分配的数组中。

list-(环状)双向链表

slist-单向链表。

deque-

双向队列是一种双向开口的连续线性空间,可以高效的在头尾两端插入和删除元素

分配中央控制器map(并非map容器),map记录着一系列的固定长度的数组的地址.记住这个map仅仅保存的是数组的地址,真正的数据在数组中存放着.deque先从map中央的位置(因为双向队列,前后都可以插入元素)找到一个数组地址,向该数组中放入数据,数组不够时继续在map中找空闲的数组来存数据。当map也不够时重新分配内存当作新的map,把原来map中的内容copy的新map中。所以使用deque的复杂度要大于vector,尽量使用vector。



stack-基于deque。

queue-基于deque。

heap-完全二叉树,使用最大堆排序,以数组(vector)的形式存放。

priority_queue-基于heap。

关联式容器:

set,map,multiset,multimap-

基于红黑树(RB-tree),一种加上了额外平衡条件的二叉搜索树。

hash table-

散列表。将待存数据的key经过映射函数变成一个数组(一般是vector)的索引,例如:数据的key%数组的大小=数组的索引(一般文本通过算法也可以转换为数字),然后将数据当作此索引的数组元素。有些数据的key经过算法的转换可能是同一个数组的索引值(碰撞问题,可以用线性探测,二次探测来解决),STL是用开链的方法来解决的,每一个数组的元素维护一个list,他把相同索引值的数据存入一个list,这样当list比较短时执行删除,插入,搜索等算法比较快。

hash_map,hash_set,hash_multiset,hash_multimap

基于hash table。

算法

,,组成。要使用 STL中的算法函数必须包含头文件,对于数值算法须包含,中则定义了一些模板类,用来声明函数对象。

1、查找容器元素find

vector<int>::iterator result;
result = find( v1.begin(), v1.end(), num_to_find );


2、条件查找容器元素find_if

bool divby5(int x)  //传入的是迭代器区间[first, last)的值
{
return x%5 ? 0:1;  //能被5除尽返回1,则是在迭代器区间中找能除5除尽的数
}
vector<int>::iterator ilocation;
ilocation = find_if(v.begin(),v.end(),divby5);


利用返回布尔值的谓词判断pred,检查迭代器区间[first,last)(闭开区间)上的每一个元素,

如果迭代器i满足pred(*i)==true,表示找到元素并返回迭代值i(找到的第一个符合条件的元素);

未找到元素,返回末位置last。

3、统计等于某值的容器元素个数count

list<int> l;
count(l.begin(),l.end(),value)


4、条件统计count_if

count_if(l.begin(),l.end(),pred)


5、子序列搜索search

search算法函数在一个序列中搜索与另一序列匹配的子序列

vector<int>::iterator ilocation;
ilocation=search(v1.begin(),v1.end(),v2.begin(),v2.end());
if(ilocation!=v1.end()) //搜索到了


【实例】

// test343.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;

int main(int argc, char* argv[])
{
string v1,v2;
cin>>v1>>v2;

string::iterator ilocation;
ilocation = search(v1.begin(),v1.end(),v2.begin(),v2.end());

if(ilocation!=v1.end())
cout<<"v2的元素包含在v1中,起始元素为"<<"v1["<<ilocation-v1.begin()<<']'<<endl;
else
cout<<"v2的元素不包含在v1中"<<endl;

return 0;
}


二、变异算法

是一组能够修改容器元素数据的模板函数。

1、copy(v.begin(),v.end(),l.begin());将v中的元素复制到l中。

2、替换replace

replace算法将指定元素值替换为新值。

3、随机生成n个元素generate

函数原型:generate_n(v.begin(),5,rand);向从v.begin开始的后面5个位置随机填写数据。

4堆排序sort_heap

使用:

make_heap(v.begin(),v.end());

sort_heap(v.begin(),v.end());

排序sort

函数原型:sort(v.begin(),v.end());

/article/4950112.html

extern c 的作用

(必须将编译器的函数名修饰的机制解答的很透彻)

能够正确实现C++代码调用其他C语言代码。加上extern “C”后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。

由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般之包括函数名。

这个功能十分有用处,因为在C++出现以前,很多代码都是C语言写的,而且很底层的库也是C语言写的,为了更好的支持原来的C代码和已经写好的C语言库,需要在C++中尽可能的支持C,而extern “C”就是其中的一个策略。

这个功能主要用在下面的情况:

1、C++代码调用C语言代码

2、在C++的头文件中使用

3、在多个人协同开发时,可能有的人比较擅长C语言,而有的人擅长C++,这样的情况下也会有用到

【C/C++编译修饰函数机制】

由于C、C++编译器对函数的编译处理是不完全相同的,尤其对于C++来说,支持函数的重载,编译后的函数一般是以函数名和形参类型来命名的。

例如函数void fun(int, int),

C++语言编译后的可能是_fun_int_int

而C语言没有类似的重载机制,一般是利用函数名来指明编译后的函数名的,对应上面的函数可能会是_fun这样的名字。

#ifndef __INCvxWorksh /*防止该头文件被重复引用*/
#define __INCvxWorksh
#ifdef __cplusplus  //告诉编译器,如果是cpp文件,这部分代码按C语言的格式进行编译,而不是C++的
extern "C"{
#endif

/*…*/

#ifdef __cplusplus
}
#endif

#endif /*end of __INCvxWorksh*/


volatile的作用

(必须将cpu的寄存器缓存机制回答的很透彻)

以volatile修饰的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。

volatile int i=10;
int a = i;
...
// 其他代码,并未明确告诉编译器,对 i 进行过操作
int b = i;  //从新从i的位置读取


优化做法是,由于编译器发现两次从 i读数据的代码之间的代码没有对 i 进行过操作,它会自动把上次读的数据放在 b 中。而不是重新从 i 里面读。

volatile用在如下的几个地方:

1) 中断服务程序中修改的供其它程序检测的变量需要加volatile;

2) 多任务环境下各任务间共享的标志应该加volatile; 防止优化编译器把变量从内存装入CPU寄存器中,如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量

3) 存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;

static const的用法

non-const static静态成员变量不能在类的内部初始化

ISO C++ forbids in-class initialization of non-const static member

const成员变量也不能在类定义处初始化,只能通过构造函数初始化列表进行,并且必须有构造函数。

non-static data member initializers only available with -std=c++11

c++11允许const成员变量在类定义处初始化

class B
{
const int c_b = 9;  //c++11才能通过
};


网络编程:

db:

mysql,会考sql语言,服务器数据库大规模数据怎么设计,db各种性能指标

最后:补充一个最最重要,最最坑爹,最最有难度的一个题目:一个每秒百万级访问量的互联网服务器,每个访问都有数据计算和I/O操作,如果让你设计,你怎么设计?

这里写链接内容
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: