您的位置:首页 > 编程语言 > C语言/C++

浅析C++基础知识

2015-02-06 19:15 399 查看
近期想对C++的面试题目进行一下更加详细的整理。事实上认真思考一下C++程序猿的面试,我们能够发现对程序猿的能力的考察总是万变不离当中,这些基础知识主要分为五部分:一、 C/C++基础知识 二、 C/C++的一些特性,如面向对象,内存管理 三、 基础的数据结构编程的知识。 四、stl的一些基础知识。五、网络编程、多线程的知识、异常处理基础知识

本文试图覆盖C/C++面试的每一个知识点,所以对每一个知识点介绍的并不深入。本文适合已经对一下详细知识有所了解的人,我对每一个点都有粗略的解说,假设想深入理解请阅读相关详细知识。

一、 C/C++的基础知识:包含指针、字符串处理、内存管理。

二、面向对象基础知识:包含多态、继承、封装。 多态的实现方式?多态的优点?

三、基础的数据结构面试:数组、链表、字符串操作、二叉树。这里要说的话就说的非常多了,详细的有使用一些数据结构包含stl list, vector, map, hashmap, set。

四、stl的一些基础知识。

五、网络编程、多线程和异常处理的基础知识。

一、C/C++基础知识:

1. C/C++内存管理:C/C++的内存分为:堆、栈、自由数据区、静态数据区和常量数据区。

栈: 函数运行时,函数内部的局部变量在栈上创建,函数运行结束栈的存储空间被自己主动释放。

堆:就是那些new分配的内存块,须要程序猿调用delete释放掉。

自由数据区:就是那些调用malloc的内存块,须要程序猿调用free释放掉。

全局存储区:全局变量和静态变量被存放到同一块存储区域。

静态存储区:这里存放常量,不同意改动。

堆和栈的差别:a. 管理方式不同:堆须要程序猿手动维护。 b. 栈的存储空间远小于堆。堆差点儿不受限制。 c. 生长方式不同。栈是从高地址空间向低地址空间生长,堆是从低地址空间向高地址空间生长。 d. 效率不同:堆要远快于栈。

new和delete的差别:new和delete是C++的函数会调用,构造函数和析构函数。而malloc和delete仅仅是分配相应的存储空间。

注意问题: 1. 意外处理:内存申请失败处理。2. 内存泄漏:申请的地址空间一定要释放。 3. 野指针:避免指针未赋值使用,一定要初始化。

给出一个图吧,关于内存空间的分配的:



左边的是UNIX/LINUX系统的运行文件,右边是相应进程逻辑地址空间的划分情况。

2. 小端存储和大端存储:

小段:高位存在高地址上;大端:低位存在高地址上。

判定代码:

unsigned short test = 0x1234;

if( *(unsigned char *)&test=0x12 )//

return Big_Edian;//高位在低地址空间 大端存储

3. C++的指针使用:

a. 指针数组 与 数组指针: 注意[]的结合优先级比*要高。那么int *p[3];我们就得到了一个数组,数组的每一个元素是int *的。而int (*p)[3];//我们就得到了一个指针,指向的是一个长度为3的一维数组。

b. 函数指针:比如对函数:int funcA(int a,int b); 我们能够定义一个指针指向这个函数:int (*func)(int,int);
func=funcA;

c. ptr++; 指针ptr的值加上了sizeof(ptr);

4. 运算符优先级:

a. 比較常考的就是 ++和--与*、&、.的优先级对照:注意正确的优先级是:. > ++ > -- > * > &

5. 二维数组的动态申请:

int **a=new int *[10];

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

a[i]=new int[10];

6. extern "C"声明的作用:

C++中引用C语言的函数和变量须要使用extern "C"来进行修饰。由于C++为了支持多态,函数和变量命名的方式和C不同,因此假设一个模块的函数和变量须要被其它模块使用,就须要使用extern "C"来修饰。该修饰制定编译器变量和函数是依照C语言方式进行链接的。

7. 指针和引用的差别,指针和数组的差别,const与define的差别:

指针和引用的差别:引用能够看作是变量的别名,不能够指向其它的地址,不能为空。

指针和数组的差别:指针能够指向其它地址,使用sizeof计算时两者的结果不同。

const与指针的差别:const定义的变量有类型,而define仅仅是编译器的简单替换。

8. 怎样判定程序是由C/C++编译器编译出来的?

#ifdef __cplusplus

#else

#endif

9. const的作用,static的作用。

a. const 指定变量不可改动。

b. static 指定变量存在静态数据区,而且仅仅初始化一次。

10. 变量字节对齐:为了加快内存寻址的效率,在C++内存管理时一般选取的是4字节对齐。例如以下变量占用8个字节:

struct Node{

int a,

char b

};

11. const与指针:

char
* const p; //指针不可改

char const *p; //指针指向的对象不可改动

12. 操作符重载:

C/C++
里大多数运算符都能够在 C++ 中被重载。C 的运算符中仅仅有 . 和 ?:(以及sizeof,技术上能够看作一个运算符)不能够被重载。C++
添加了一些自己的运算符,除了 :: 和 .*
外,大多数都能够被重载。

a. 单目操作符:

Point &Point:: operator++();//++a;

Point &Point:: operator++(int);//a++

b. 双目操作符:

Point &Point:: operator+=(Point b)// +=

源码例如以下:

class Point{
public:
int x;
int y;
public:
void Print(){
cout<<"x="<<this->x<<endl;
cout<<"y="<<this->y<<endl;
}
Point(){
x=0;
y=0;
}
Point(const Point &a){
x=a.x;
y=a.y;
}
Point(int x,int y){
this->x=x;
this->y=y;
}
Point& operator+=(Point b);
Point& operator++();
Point& operator++(int);
};
Point& Point::operator+=(Point b){
x += b.x;
y += b.y;
return *this;
}

Point& Point::operator++(){
this->x++;
this->y++;
return *this;
}
Point& Point::operator++(int){
Point a(*this);
this->x++;
this->y++;
return a;
}
int main(){
Point *a=new Point(1,2);
Point *b=new Point(3,4);
Point c(10,20);
*a+=c;
a->Print();
++c;
c.Print();
c++;
c.Print();
return 0;
}

13. 函数调用传值与传引用

传值不会改动传入參数的值,而传引用会改动传入參数的值。

14. volatile与C/C++的四种cast函数:

volatile修饰的变量,编译器不会做优化,每次都是到内存中去读取或者写入改动。C++有四种cast函数:static_cast,dynamic_cast,const_cast,volatile_cast。

15. C++ 类的实例化假设没有參数是不须要括号的,否者就是调用函数而且返回相应的对象,比如例如以下代码就会报错:

struct Test
{
Test(int ) { }
Test() { }
void fun() { }
};

int main(void)
{
Test a(1);
a.fun();
Test b;
b.fun();
return 0;
}

15. C++ 里面的const修饰函数时,标志该函数不会改动this指针的内容。并且const不能修饰非类内部的函数。

[b]16.
const能够重载,这是基于编译器编译时对函数进行重命名实现的。比如f( int a), f(const int a)是两个不同的函数了。


[/b]

17. override(覆盖), overload(重载), polymorphism(多态)

[b]18.
const char* a, char const*, char*const的差别: const char * pa;//一个指向const类型的指针,char * const pc;//const修饰的指针


[/b]

[b][b]19. typename和class的差别:在模版中使用时,class与typename能够替换。在嵌套依赖类型中,能够声明typename后面的字符串为类型名,而不是变量。比如:[/b][/b]



class MyArray //临时还不是非常懂,就先睡觉了。
{
public:
typedef int LengthType;
.....
}

template<class T>
void MyMethod( T myarr )
{
typedef typename T::LengthType LengthType;
LengthType length = myarr.GetLength;
}


二、面向对象和C++特性描写叙述:

1. 面向对象三个基本特征:继承、多态和封装。

2. 多态:多态是面向对象继承和封装的第三个重要特征,它在执行时通过派生类和虚函数来实现,基类和派生类使用相同的函数名,完毕不同的操作和实现相隔离的还有一类接口。多态提高了代码的隐藏细节实现了代码复用。

3. 什么是纯虚函数和纯虚类:有些情况下基类不能对虚函数有效的实现,我们能够把它声明为纯虚函数。此时基类也无法生成对象。

4. 什么是虚函数:虚函数是类内部使用virtual修饰的,訪问权限是public的,而且非静态成员函数。虚函数是为了实现动态连接,定义了虚函数以后对基类和派生类的虚函数以相同形式定义,当程序发现virtual虚函数以后会自己主动的将其作为动态链接来处理。纯虚函数在继承类实现时,须要所有实现。

如public virtual function f()=0;

注:下列函数不能被声明为虚函数:普通函数(非成员函数);静态成员函数;内联成员函数;构造函数;友元函数。

5. 执行时绑定和虚函数表:

动态绑定:绑定的对象是动态类型,其特性依赖于对象的动态特性。

虚函数表: C++的多态是通过动态绑定和虚函数表来实现的。这是虚函数的地址表,存储着函数在内存的地址。一般类的对象实例地址就是虚函数表。有虚函数覆盖时,虚函数的地址存储的是被覆盖的子类的函数的地址。

事实上知道虚函数表了,我们就能够通过操作指针的方式訪问一些额外的数据(当然假设直接写成代码会编译不通过的)。对于基类指针指向派生类时:我们能够通过内存訪问派生类未覆盖基类的函数,訪问基类的非public函数。

6. 空类的大小不是零:由于为了确保两个实例在内存中的地址空间不同。

例如以下,A的大小为1,B由于有虚函数表的大小为4.

class A{};

class B:public virtual A{};

多重继承的大小也是1,例如以下:

class Father1{}; class Father2{};

class Child:Father1, Father2{};

7. 深拷贝与浅拷贝:

C++会有一个默认的拷贝构造函数,将某类的变量所有赋值到新的类中。但是这样的情况有两个例外:(1). 类的默认构造函数做了一些额外的事情,比方使用静态数据统计类被初始化的次数(此时浅拷贝也无法解决这个问题,须要额外的改动拷贝构造函数)。 (2). 类内部有动态成员:此时发生拷贝时就须要对类内部的动态成员又一次申请内存空间,然后将动态变量的值拷贝过来。

8. 仅仅要在基类中使用了virtualkeyword,那么派生类中不管是否加入virtual,都能够实现多态。

9. 虚拟析构函数:在使用基类指针指向派生类时,假设未定义虚拟析构函数那么派生类的析构函数不会被调用,动态数据也不会被释放。

构造函数和析构函数的调用顺序为:先基类构造函数,再派生类构造函数。析构时,先派生类析构函数,再基类析构函数。

10. public,protected, private权限:privated:类成员函数、友元函数。 protected:派生类、类成员函数、友元函数,public全部

11. 可重入函数(多线程安全),即该函数能够被中断,就意味着它除了自己栈上的变量以外不依赖不论什么环境。

12. 操作系统内存管理有堆式管理、页式管理、段式管理和段页式管理。堆式管理把内存分为一大块一大块的,所需程序片段不在主村时就分配一块主存程序,把程序片段load入主存。页式管理:把主页分为一页一页的,每一页的空间都比块小非常多。段式管理:把主存分为一段一段的,每一段的空间比一页一页的空间小非常多。段页式管理。

三、基础的数据结构编程知识:

 事实上我理解作为面试来说,通常会面时应聘者的基础知识,假设对于过于复杂的图程序,动态规划一般不太适合。而对于数组、链表、二叉树、字符串处理的考察,既能考察出应聘者的基础知识、代码风格、命名书写规范,又能考察出应聘者的代码是否包括bug。而比較深入的一些题目一般仅仅作为脑筋急转弯或者给出思路,博主在此提醒各位,面试时准确性更加重要,假设一时想不出来,能够首先给出一个效率较低的实现,然后再一步步优化。以下分析一下对于数组、链表、二叉树和字符串处理的常考题目:
1. 数组:一般到了数组就会考察排序(稳定性、平均、最好、最差时间复杂度/内存使用、不同情况下那种最快)、二分的特性。
 2. 链表:链表的插入,逆序,推断链表相交,找到链表交点,链表的删除。
3. 二叉树:二叉树的深度、前序/中序/后序遍历、二叉树的叶子数目、二叉树的节点数目、二叉树的层次遍历。
4. 字符串的处理:atoi,itoa,字符串替换、字符串的查找。再次特意提醒一下,字符串处理设计指针的处理所以指针的内存訪问控制,须要使用很多其它的注意,比方參数为空,内存申请失败,返回值控制,指针加减。

1. 数组:
1). 排序:各种排序算法的对照方下图所看到的。

排序法平均时间最差情形稳定度额外空间备注
冒泡O(n2)O(n2)稳定O(1)n小时较好
交换O(n2)O(n2)不稳定O(1)n小时较好
选择O(n2)O(n2)不稳定O(1)n小时较好
插入O(n2)O(n2)稳定O(1)大部分已排序时较好
基数O(logRB)O(logRB)稳定O(n)B是真数(0-9),

R是基数(个十百)

ShellO(nlogn)O(ns) 1<s<2不稳定O(1)s是所选分组
高速O(nlogn)O(n2)不稳定O(nlogn)n大时较好
归并O(nlogn)O(nlogn)稳定O(1)n大时较好
O(nlogn)O(nlogn)不稳定O(1)n大时较好

2) 详细的每种排序算法的实现:
a) 高速排序实现:

int partition(int a[],int low,int high){
int data=a[low],tem;
int i=low+1,j=low+1;

while(j<=high){
if(a[j]>data)
j++;
else{
tem=a[i];
a[i]=a[j];
a[j]=tem;
i++;
j++;
}
}
a[low]=a[i-1];
a[i-1]=data;
return i-1;

}

void qsort1(int a[],int low,int high){
if(low>=high)
return;
int mid=partition(a,low,high);
if(mid-1>low)
qsort1(a,low,mid-1);
if(high>mid+1)
qsort1(a,mid+1,high);

}


b) 堆排序实现:

void HeapModify(int a[], int i, int length){
int j=-1;
if( i*2+1 < length ){
if( a[i*2+1]>a[i]){
j=i*2+1;
}
}
if( i*2 +2 < length){
if( a[i*2 +2] > a[i] && a[2*i+2] > a[2*i+1]){
j=i*2+2;
}
}

if( j> 0){
int tem;
tem=a[i];
a[i]=a[j];
a[j]=tem;
HeapModify(a,j,length);
}
}
void Heap_sort(int a[],int length){
int i,tem;
for(i=length/2;i>=0;i--){
HeapModify(a,i,length);
}

for(i=0;i<length-1;i++){
tem=a[0];
a[0]=a[length-1-i];
a[length-1-i]=tem;
HeapModify(a,0,length-i-1);
}
}


c) 最大子数组求和:
int Max_Sum(const int a[],const int length_a){
int length,i,max1=0;
int *b=new int[10];

max1=b[0]=a[0];
for(i=1;i<length_a;i++){
b[i]=max(b[i-1]+a[i],a[i]);
if(b[i]>max1){
max1=b[i];
}
}
return max1;
}


3) 字符串处理程序:
a). atoi实现:
int atoi1(char *str){
assert(str!=NULL);
int result=0,pos=1;
if(*str=='-'){
pos=-1;
str++;
}
while(*str!='\0'){
assert(*str<='9'&&*str>='0');
result=result*10+*str-'0';
str++;
}
return result*pos;

}
b). itoa实现:
char *itoa1(int num,int k){
char data[]="0123456789ABCDEF";
bool pos=true;
int count=1,tem;
int num1=num;
if(num<0){
num=-num;
pos=false;
count++;
}
while(num>0){
num=num/k;
count=count+1;
}
char *result=new char[count];
char *presult=result,*pstr;
if(pos==false){
*presult++='-';
}
pstr=presult;

while(num1>0){
*presult++=data[num1%k];
num1=num1/k;
}
*presult--='\0';
while(pstr<presult){
tem=*pstr;
*pstr=*presult;
*presult=tem;
*presult--;
*pstr++;
}

return result;
}
c). 将字符串空格替换为目的串的实现:

char * Replace_Str(char * str, char * target){
assert(str!=NULL&&target!=NULL);
char *pstr=str;
int count=0;
while(*pstr!='\0'){
if(*pstr==' '){
count++;
}
pstr++;
}
char * result=new char[sizeof(str)+count*strlen(target)];
char *presult=result;
for(pstr=str;pstr!='\0';pstr++){
if(*pstr!=' '){
*presult=*pstr;
}
else{
char *ptarget=target;
while(*ptarget!='\0'){
*presult++=*ptarget++;
}
}
}
*presult='\0';
return result;
}
d). strcpy实现:

void strcpy1(char * source, char * dest){
assert(source!=NULL&&dest!=NULL);
char * psource=source, *pdest=dest;
while(*psource!='\0'){
*pdest++=*psource++;
}
*pdest='\0';
}
e). 查找目的串第一次出现位置(最快的肯定是KMP O(M+N)),或者查找目的串在源串中的出现次数:
<span style="font-size:14px;">int Find_Str(const char* source,const char* target){ //查找目的串在源串中出现的位置,最弱的方法
assert(source!=NULL&&target!=NULL);

int i,j,i1;
int len_source=strlen(source),len_target=strlen(target);

for(i=0;i<len_source;i++){
i1=i;
for(j=0;source[i1]==target[j]&&j<len_target;i1++,j++);
if(j==len_target)
return i;
}
return -1;
}</span>
f). memcopy实现:

void *memcpy1(void * dest, const void * source, size_t num){//注意须要考虑内存重叠的部分,特别的dest在source数据区间内部时候的处理
assert(dest!=NULL&&source!=NULL);
int i;
byte *psource=(byte *) source;
byte *pdest=(byte *) dest;

if(pdest>psource&&pdest<psource+num){
for(i=num-1;i>=0;i--)
*(pdest+i)=*(psource+i);
}else{
for(i=0;i<=num;i++)
*(pdest+i)=*(psource+i);
}

return dest;
}


3) 链表处理:

a). 链表相交判定:

b). 链表逆序:

c). 两个有序链表拼接成为一个有序链表:

4) 二叉树处理:

a). 二叉树的前序/后序/中序遍历:

a. 二叉树前序/非递归:

void Pre_Traverse_Recur(NODE *root){//前序递归
if(root==NULL)
return;
cout<<root->data;
Pre_Traverse_Recur(root->left);
Pre_Traverse_Recur(root->right);
}
void Pre_Traverse(NODE *root){<span style="font-family: Arial, Helvetica, sans-serif;">//前序非递归</span>
stack<NODE*> stack1;
NODE *p=root;

while(p!=NULL||!stack1.empty()){
while(p!=NULL){
cout<<p->data;
stack1.push(p);
p=p->left;
}
if(!stack1.empty()){
p=stack1.top();
stack1.pop();
p=p->right;
}
}
}
b. 二叉树中序/非递归:

void Inorder_Traverse_Recur(NODE *root){//中序递归
if(root==NULL)
return;
Pre_Traverse_Recur(root->left);
cout<<root->data;
Pre_Traverse_Recur(root->right);
}
void Inorder_Traverse(NODE *root){<span style="font-family: Arial, Helvetica, sans-serif;">//中序非递归</span>
stack <NODE*> stack1;
NODE *p=root;
while(p!=NULL||!stack1.empty()){
while(p!=NULL){
stack1.push(p);
p=p->left;
}
if(!stack1.empty()){
p=stack1.top();
cout<<p->data;
stack1.pop();
p=p->right;
}
}
}
c. 二叉树后序/非递归:

void Postorder_Traverse_Recur(NODE *root){//后序递归
if(root==NULL)
return;
Postorder_Traverse_Recur(root->left);
Postorder_Traverse_Recur(root->right);
cout<<root->data;
}

#include <hash_map>
using namespace std;
using namespace __gnu_cxx;
void Postorder_Traverse(NODE *root){//非递归后序遍历
    stack <NODE *> stack1;
    hash_map <int,int> hashmap1;

    assert(root!=NULL);
    NODE *p=root;

    while(p!=NULL){
        stack1.push(p);
        p=p->left;
        hashmap1[stack1.size()]=0;
    }

    while(!stack1.empty()){
        p=stack1.top();

        while(p->right&&hashmap1[stack1.size()]==0){
            hashmap1[stack1.size()]=1;
            p=p->right;
            while(p!=NULL){
                stack1.push(p);
                hashmap1[stack1.size()]=0;
                p=p->left;
            }
            p=stack1.top();
        }
        p=stack1.top();
        cout<<p->data;
        stack1.pop();
    }
}

b). 二叉树的深度/节点数目/叶子数目:

c). 二叉树的两个节点的最短距离,最低公共祖先:
d). 全然二叉树判定:

e). 二叉树镜像:

5) 其它:

a). B/B+树:

四、STL基础知识:
1. STL:标准模板库,它由算法、迭代器、容器组成。

2. STL容器简单介绍,STL容器大致能够分为三类:
1). 序列容器:vector, list, deque, string

2). 关联容器:map, set, hashmap, multiset, hash_set

3). 其它杂项:stack, queue, valarray, bitset
3. list使用:
list<int> lst1;
list<int>::iterator itor;
lst1.push_front(1);
lst1.push_back(2);
int i=lst1.front();
list1.size();
list1.empty();
4. stack使用:

stack<int> stack1;
stack1.push(10);
int i=stack1.top();
stack1.pop();
stack1.size();
stack1.empty();
5. vector使用:

vector<int> v;
vector<int> ::iterator itor;
for(int i=0;i<10;i++){
v.push_back(i);
}
itor=v.begin();
v.insert(itor,55);
v.erase(itor);

for(itor=v.begin();itor!=v.end();itor++){
cout<<*itor<<endl;
}
vector<int>::iterator findit = find(v.begin(),v.end(),123);
if(findit==v.end())
cout<<"Not exist!!"<<endl;
6. Map/HashMap使用:

map <int,int> map1;
map1[5]=5;
map1.insert(pair<int,int>(7,7));
map1.insert(pair<int,int>(1,1));
map <int,int> ::iterator itor;

for(itor=map1.begin();itor!=map1.end();itor++)
cout<<itor->first<<itor->second<<endl;
itor=map1.find(5);
if(itor!=map1.end())
cout<<"Exists"<<endl;
map1.size();
map1.empty();




五、多线程和网络编程:

1. 原子锁 自旋锁 信号量 相互排斥锁:
自旋锁专门为多处理器并发而引入的一种锁,在内核中大量应用于中断处理等部分。最多仅仅能被一个内核任务持有,假设一个内核任务试图请求一个已经被占用的自旋锁,那么这个任务就会一直进行忙循环。
信号量:用在多线程、多进程同步,假设线程完毕了某个操作就会通过信号量告诉别的线程,别的线程在进行某些动作。进程假设检測到信号量被占用,就会被发送到等待序列。信号量能够不是为了资源竞争使用,比方控制流程。
相互排斥锁:用在多线程同步,如竞争资源訪问控制。
2. C++多线程和多进程:
3. TCP三次握手过程:



4. socket编程基础:
server端 socket, bind, listen, accept. client端
socket, connect , send/receive。
5. 短连接和长连接:

长连接时,client端向server端发起连接,server接受连接,两方建立连接。假设client/server完毕一次读写以后,他们之间的连接并未主动关闭,兴许操作能够继续使用这个连接。

6. 多线程编程基础:

多线程之间能够通过共享内存的方式同步数据,或者使用TCP。多线程间同步能够使用信号量、相互排斥锁、临界区、事件。

7. TCP与UDP的差别:

TCP是面向连接的,可靠的字节流服务。两方先建立TCP连接,然后才数据传输。TCP支持超时重发,丢弃反复数据,校检数据和流量控制。ftp

UDP是非面向连接的,不提供可靠性。它仅仅是把应用程序传给IP层的数据报先发送出去,并不能保证它们可以达到目的地。没有超市重发等机制,故而传输速度非常快。ping

TCP报文头的结构:源port、目的port、序列号、确认序列号、首部长度、窗体大小、紧急指针、选项、数据。UDP数据包显然没有上述的非常多数据。

8. OSI7层模型:



9. C++实现单例模式:
class Singleton{//太令我挣扎了。。。
private:
static Singleton *_instance;
Singleton(){
}
public:
static Singleton * Instance(){
if(0==_instance)
_instance=new Singleton;
return _instance;
}
static Singleton * Get_Instance(){
return _instance;
}
};

Singleton* Singleton::_instance = 0;
int main(){
Singleton *sg = Singleton::Instance();
return 0;
}

10. C++异常处理:

參考文献:
1. 进程内存空间: http://www.cnblogs.com/biyeymyhjob/archive/2012/07/14/2591714.html 2. 原子操作、信号量、自旋锁、相互排斥锁: http://blog.163.com/hbu_lijian/blog/static/126129153201261722410353/ 3. 二叉树面试题目:http://www.cnblogs.com/dolphin0520/archive/2011/08/25/2153720.html
4. 多线程同步方法: http://blog.sina.com.cn/s/blog_6288219501011189.html
5. OSI 7层模型: http://blog.sina.com.cn/s/blog_6f83fdb40101fchk.html


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