字节跳动-实习

本文记录了一次2021年字节跳动客户端实习的面试经历,包括一面和二面的题目,如LeetCode题目和基础知识问答。重点讨论了线程池和内存池的概念、优势以及简单实现。面试反馈强调基础知识的重要性,如线程同步、数据结构、网络协议等。此外,还分享了用两个栈或队列实现数据结构的思路。
摘要由CSDN通过智能技术生成

2021-字节跳动-客户端-移动os实习面试


历时半个月 收获满满 经验知识尚存在很多不全面之处 代码框架思维、算法思想也欠缺很多 那就好好总结一下吧!!为明年蓄点儿力!
在牛客学到很多 希望能用自己的实际经历来帮助大家!主要反馈一下实习 一面 二面 的过程 还有一些感悟
offer应该是凉凉哈哈哈哈 毕竟准备时间不充分 还是要刷题 刷题 刷题!!!!!!!!!!!!
另外刷题不在数量 在质量!!!!!用我们多年学数学(高数)的经验去做算法题完全OK!!!!!!!!!
(本人 本科双非 硕士某985 机械电子研一在读 转行自学计算机)


2021 6.02 一面

1、半个小时先做题
Leetcode 61给定一个单链表 输出该链表右移K位的新链表
leetcode 215给定一个数组 输出数组里第k大的数字

注意:面试的时候 一般不允许使用sort 直接排序 要用自己学过的排序算法实现!!.
感受: 这两个题我都还没做到 当时挺慌的。然后就开始用自己熟悉的方法进行实现,最后其实都没做出来,一直在按自己的思路实现。当然了,思路没有太偏。30min到了开始向面试官说自己的实现,准备用什么方法。然后就这样结束了 当时感觉就凉了 我是很愧疚的哈哈哈 面试官很好 。

2、项目 简单介绍一下项目的实现 没深挖!两分钟左右
感受:其实关于项目的准备我是符付出很多经历的,介绍项目的时候我时不时的问一下面试官要不要详细介绍一下程序模块的设计 可是他说简单说一下就好!! 然后我就介绍了一下设计流程 细节没展开 我介绍完 面试官就说问你几个基础知识吧!!!可能是实习要求不高哈哈哈哈哈

3、 基础知识(一些重要的关键字要熟记) 剩余的所有时间都在问基础知识
Voliate
Static 作用域 全局、局部变量
动态链接和静态链接
多态
Auto 和普通的变量初始化方式有什么区别?
指针和引用
大端序和小端序
Const 和#define
C++模板在汇编阶段还是运行阶段?
八股文
三次握手 四次挥手
智能指针的底层实现
子网掩码
简单的sql :查找表student中分数在90-100之间的学生信息

感悟 基础知识的话就得好好准备!!在把握重点的情况下 尽量全面!然后对于自己很熟的知识点 一定要想法进行插入介绍.这就考验你的语言艺术啦哈哈哈 比如:问你https你先问一下面试官 我可以先从http介绍开始 这样你就可以多介绍一些了嘛!但是要在自己对这些知识熟悉的情况下哟 毕竟面试官不一定是想让你完完整整一字不落的答出他的问题。和面试官进行互动和进行知识的迁移 我觉得都是面试的一些技巧哈哈哈 不知道大家意下如何?

一面结束我是准备放弃了!准备去健身去了哈哈哈
晚上九点多 正贴着面膜刷抖音呢!HR小姐姐告诉一面过了 约好二面的时间 吓得我赶紧背着电脑去实验室学习去了!!!!!既然机会就在眼前,为何不争取一把??!!!!!!!!!哈哈哈哈哈


2021.6.08 二面

这次可惜呀!!!!!
1、上来还是做题,时间没规定,面试官一边工作一遍面。

用两个栈实现一个队列。easy题 不过我有点儿记不清了 哎!!!

又按自己的思路写 思路没啥问题 最后调试还是没成功 有点儿慌 ~~~~~

2、接着又是基础知识了 全程没自我介绍 没问项目 没问数据库 (这些我都是认真准备过的 有一丢丢可惜

C++基础只问了熟悉的数据结构,vector list 数组(对数组的理解有错:插入元素时候不是之后的元素都往后移)
数组和链表的区别
TCP/IP头部 没记住
HTTP HTTPS 加密原理 HTTP完整的传输过程
进程 线程的特点 线程同步 锁 如何实现共享内存 临界区 临界资源 多机分布 多核分布
线程池 问线程的居多
数据库没问
项目没问

总结:分人 灵活 深入 底层
对于一些资料上的名词一定要确认无误 一定要保证自己说的90%正确
里面提到的知识点和名词一定要提前了解一下
以免不知道自己说的是啥 被面试官揪出来问就很尴尬哈哈哈哈

没啥遗憾了 知足常乐?
都是经验嘛 有点儿可惜 毕竟内推的机会不是每年都有的~
明年继续面!!毕业去大厂!!!!​​​​


附 对线程池&内存池的简单总结

一、设计思想:池化资源
面向对象编程中,对象创建和销毁是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是对一些很耗资源的对象创建和销毁。如何利用已有对象来服务就是一个需要解决的关键问题,其实这就是一些”池化资源”技术产生的原因。
比如大家所熟悉的数据库连接池就是遵循这一思想而产生的,下面将介绍的线程池技术同样符合这一思想。

线程池一种管理线程的概念,线程池的好处是可以更方便的管理线程,减少内存消耗
在一个应用程序中,我们需要多次创建销毁线程,而创建销毁线程的过程势必会消耗内存。
为了避免重复的创建线程,线程池的出现可以让线程进行复用可以理解为,当有任务需要执行时,就会向线程池拿一个线程,当任务执行完毕后,不是直接关闭线程,而是将这个线程归还给线程池供其他任务使用;可以类比共享单车的运行机制;


二、线程池的简单实现
通常,线程池都是通过线程池工厂创建,再调用线程池中的方法获取线程,再通过线程去执行任务方法
一般一个简单线程池至少包含下列组成部分。
线程池管理器(ThreadPoolManager):用于创建并管理线程池。
工作线程(WorkThread): 线程池中线程。
任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行。
任务队列:用于存放没有处理的任务。提供一种缓冲机制。
线程池管理器至少有下列功能:创建线程池、销毁线程池、添加新任务

三、线程池的优势
总体来说,线程池有如下的优势:
(1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
(2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行
(3)提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。


内存池:
**内存池(Memory Pool) 是一种内存分配方式。**通常我们习惯直接使用new、malloc 等申请内存,这样做的缺点在于:由于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能。
内存池则是在真正使用内存之前,先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用
当有新的内存需求时,就从内存池中分出一部分内存块, 若内存块不够再继续申请新的内存。这样做的一个显著优点是尽量避免了内存碎片,使得内存分配效率得到提升。


四、线程切换开销大
为什么开销大:
线程切换涉及到用户态和内核态的转换,并且在切换的时候,CPU需要将此线程的所有执行状态保存起来,如线程编号,执行的位置等等 然后再去指向其他的线程。
为什么线程切换会导致用户态和内核态的切换:
因为线程的调度是在内核态运行的 线程的代码是在用户态执行
5、进程 线程的适用场景
进程适用于CPU密集型
线程适用于IO密集型的 需要频繁切换线程的场景.


附 根据实习面试反馈的一些知识点

在这里插入图片描述

Leetcode 61 给定一个单链表 输出该链表右移k位得到的新链表

struct ListNode
{
	int val;
	ListNode* next;
	ListNode(int x):val(x),next(NULL){}
};

// Leetcode 61
//给定一个单链表 输出该链表右移k位的新链表
class Solution
{
public:
	ListNode* rotateRight(ListNode* head, int k)
	{
		if (k == 0 || head == nullptr || head->next == nullptr)
		{
			return head;
		}
		int n = 1;//初始化一个变量n用来表示链表的长度
		ListNode* iter = head;//创建一个指针指向头节点
		while (iter->next != nullptr)
		{
			iter = iter->next;//继续向下遍历
			n++;//将链表遍历完得到链表的长度n
		}
		int add = n - k % n;//核心的一个点【新链表的最后一个节点位原链表的第(n-k%n)个节点】
		if (add == n)//此时n就是k的倍数
		{
			return head;//直接返回原链表
		}
		iter->next = head;//连接成环 为下一步的断开做准备;如果不提前连接成环 一会儿断开后就无法保证完整的链表结构
		while (add--)//将iter指针移动到新链表的最后一个节点
		{
			iter = iter->next;
		}
		ListNode* ret = iter->next;//创建一个新指针指向新链表的头节点
		iter->next = nullptr;//将环断开 得到一新的链表
		return ret;//返回新链表的头节点
	}
};

Leetcode 215 给定一个数组 输出数组里第k大的数字&&面试题 数组中第k大的数字

相比较而言 第一种方法更简单容易理解,但是要熟悉优先队列的一些性质;

解法一:利用优先队列实现小顶堆解法

//构建小顶堆
//关于priority_queue 的参数解释 
//模板声明有三个参数 priority<Type,Container,Functional> 其中 Type为数据类型 Containe为保存数据的容器 Functional为元素比较方式
//默认使用vector进行数据的保存;
//默认按从小到大排列 所以top()返回的是最大值而不是最小值;
//使用了greater< >之后,数据从大到小排列 top()返回的是最小值而不是最大值;

class Solution
{
public:
	int findKthLargest(vector<int>& nums, int k)
	{
		priority_queue<int, vector<int>, greater<int>>heap;//小顶堆实现 队首元素是最小的
		for (int i = 0; i < nums.size(); i++)
		{
			if (i < k)//保证最小堆里只保留k个元素 先将数组中的前k个元素加入队列
			{
				heap.push(nums[i]);//队尾加入
			}
			else if (nums[i] > heap.top())//利用优先队列的性质 保证第k大的数是在堆顶的 这样直接返回堆顶元素就行;
			{
				heap.pop();//当前遍历到的元素大于此时的堆顶元素,就把堆顶元素删除,将当前元素入堆;
				heap.push(nums[i]);//队尾加入
			}
		}
		return heap.top();
	}
};

解法二:构建大顶堆

//构建大顶堆 做k-1次删除操作
//大顶堆 每个节点都大于等于他的两个子节点
//小顶堆 每个节点都小于等于他的子节点
class Solution
{
public:
	void maxHeapify(vector<int>& a, int i, int heapSize)
	{
		int l = i * 2 + 1, r = i * 2 + 2, largest = i;
		//如果父节点的数组下标是i 那么其左孩子是i*2+1 右孩子是i*2+2
		//将当前的根节点的索引下标当作最大值
		if (l<heapSize && a[l]>a[largest])largest = l;
		if (r<heapSize && a[r]>a[largest])largest = r;
		if (largest != i)
		{
			swap(a[i], a[largest]);
			maxHeapify(a, largest, heapSize);
		}
	}

	//构建大顶堆
	void buildMaxHeap(vector<int>& a, int heapSize)
	{
		for (int i = heapSize / 2; i >= 0; --i)
		{
			maxHeapify(a, i, heapSize);
		}
	}

	int  findKthLargest(vector<int>& nums, int k)
	{
		int heapSize = nums.size();//数组的大小就是堆的大小
		buildMaxHeap(nums, heapSize);//构建大顶堆
		for (int i = nums.size() - 1; i >= nums.size() - k + 1; --i)
		{
			swap(nums[0], nums[i]);
			-heapSize;
			maxHeapify(nums, 0, heapSize);
		}
		return nums[0];
	}
};

剑指offer 09 用两个栈实现队列 简单题目


//两个栈实现一个队列
class Myqueue
{
public:
	stack<int>inStack, outStack;//创建一个输入栈 输出栈
	Myqueue(){}
	//将所有的元素加入到输入栈里面;一个元素放入队列的尾部
	void push(int x)
	{
		inStack.push(x);
	}
	//从队列尾部移除元素
	int pop()
	{
		if (outStack.empty())
		{
			while (!inStack.empty())//将输入栈里的所有元素导入到输出栈 直到输入栈为空
			{
				outStack.push(inStack.top());
				inStack.pop();
			}
		}
		//此时输入栈已经没有元素了 开始将输出栈的元素顺次输出就是颠倒之后的元素顺序
		int result = outStack.top();//保存输出栈的栈顶元素 此时栈顶元素就是队列的队首元素
		outStack.pop();
		return result;
	}

	//返回队列首部的元素
	int peek()
	{
	    //this指针指向被调用的成员函数所属的对象
		int res = this->pop();//this指针调用的就是pop成员函数返回的元素
		outStack.push(res);
		return res;
	}

	//输入栈和输出栈都为空 队列为空
	bool empty()
	{
		return inStack.empty() && outStack.empty();
	}

};

分析一下栈和队列的特点:
栈先进后出;队列先进先出;
在这里插入图片描述
在这里插入图片描述


用两个队列实现一个栈

在这里插入图片描述


//栈是一种先进后出的数据结构 元素从顶端入栈 从顶端出栈
//队列是一种先进先出的数据结构 元素从后端入队 从前端出队

class MyStack
{
public:
	queue<int>q1;//存储栈内的元素;
	queue<int>q2;//作为入栈操作的辅助队列;
	MyStack(){}

	void push(int x)
	{
		q2.push(x);
		while (!q1.empty())
		{
			q2.push(q1.front());
			q1.pop();
		}
		//关键操作就是交换二者   将最先入队的元素先暂存到另一个队列中 等有了新元素之后 再把它取回来 实现先进后出的效果
		swap(q1, q2);//一开始q1是空的 交换之后 q1就包含了最新入队的元素;
	}
	//删除队首元素
	int pop()
	{
		int r = q1.front();//交换了q1和q2之后 q1中包含的就是和栈中元素相同顺序的队列了
		q1.pop();
		return r;
	}
	//返回队首元素
	int top()
	{
		int r = q1.front();
		return r;
	}
	//判断队列是否为空
	bool empty()
	{
		return q1.empty();
	}
};


补充this指针

引言
首先,我们都知道类的成员函数可以访问类的数据(限定符只是限定于类外的一些操作,类内的一切对于成员函数来说都是透明的),那么成员函数如何知道哪个对象的数据成员要被操作呢,原因在于每个对象都拥有一个指针:this指针,通过this指针来访问自己的地址
注意
this指针并不是对象的一部分,this指针所占的内存大小是不会反应在sizeof操作符上的。this指针的类型取决于使用this指针的成员函数类型以及对象类型,


一、this指针的概念
定义
在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。
因此,在成员函数内部,它可以用来指向调用对象

this只能在成员函数中使用
成员函数默认第一个参数为T* const register this。
(友元函数,全局函数不是成员函数)

this指针不能在静态函数中使用
静态函数如同静态变量一样,他不属于具体的哪一个对象,静态函数表示了整个类范围意义上的信息,而this指针却实实在在的对应一个对象,所以this指针不能被静态函数使用。

this指针的创建
this指针在成员函数的开始执行前构造的,在成员的执行结束后清除

this指针只有在成员函数中才有定义。
创建一个对象后,不能通过对象使用this指针。也无法知道一个对象的this指针的位置(只有在成员函数里才有this指针的位置)。当然,在成员函数里,你是可以知道this指针的位置的(可以&this获得),也可以直接使用的。


二、this指针的操作
在类的非静态成员函数中返回类对象本身的时候,我们可以使用圆点运算符*,箭头运算符->。

#include<iostream>
using namespace std;
class A{
 private:
  int x;
 public:
  A(){
   x=0;
  }
  void display(){
   cout<<this<<endl;
   cout<<this->x<<endl;
   cout<<x<<endl;
   cout<<(*this).x<<endl;
  }
};
int main(void){
    A a;
    a.display();
}

输出:
0x70fe40
0
0
0

关于this指针的一个经典回答:

当你进入一个房子后,
你可以看见桌子、椅子、地板等,
但是房子你是看不到全貌了。
对于一个类的实例来说,
你可以看到它的成员函数、成员变量,
但是实例本身呢?
this是一个指针,
它时时刻刻指向你这个实例本身。


在C++中成员变量和成员函数是分开存储的
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
那么问题是:这一块代码是如何区分那个对象调用自己的呢?
c++通过提供特殊的对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针

this指针不需要定义,直接使用即可


this指针的用途:

形参和成员变量同名时,可用this指针来区分
*在类的非静态成员函数中返回对象本身,可使用return this


this指针的几个特点:
得出结论 this指针不占用类空间的大小
得出结论 this里面存放的是这个指针指向对象的首地址
得出结论 静态成员函数中是没有this指针的
因为静态成员函数是先于对象存在的 而this指针指向的是对象的首地址
故在静态成员函数里面是无法使用this指针的 因为此时对象还没存在 根本取不到其地址

**代码实例:**
#include<iostream>
using namespace std; 

class Student
{
public:
	int getAge()
	{
		return age;
	}
	Student setAge(int age)//形参也是age
	{
		this->age = age;//当形参名和对象属性名一样时  用this指针加以区分
		cout << "age :===" << age << endl;
		return *this;//函数返回的是它本身

	}

	void test()
	{
		cout << "this指针里面存放的地址是:" << this << endl;

	}

	static void lazy()
	{
		cout<<"I want to sleep "<<endl;
		//cout << this->age << endl;

	}
	//得出结论  静态成员函数中是没有this指针的 
	//因为静态成员函数是先于对象存在的 而this指针指向的是对象的首地址
	//故在静态成员函数里面是无法使用this指针的  因为此时对象还没存在  根本取不到其地址



private:
	int age;//对象属性

};

//
//int main()
//{
//	Student s;
//	s=s.setAge(19);
//	cout << s.getAge() << endl;
//	system("pause");
//	return 0;
//
//}

int main()
{
	Student::lazy();//类直接调用静态成员函数 此时没有对象被实例化


	Student s;
	//对象调用静态成员函数
	s.lazy();

	//s.test();
	//cout << "s 实例对象的地址是:" << &s << endl;// 得出结论 this里面存放的是这个指针指向对象的首地址

	//cout << sizeof(Student) << endl;//输出类所占字节的大小    得出结论 this指针不占用类空间的大小

	system("pause");
	return 0;
}

补充一些基础知识

基础知识(一些重要的关键字要熟记)
Voliate
Static 作用域 全局局部
动态链接和静态链接
多态
Auto 和普通的变量初始化方式有什么区别?
指针和引用
大端序和小端序
Const 和#define
C++模板在汇编阶段还是运行阶段?
八股文
三次握手 四次挥手
智能指针的底层实现
子网掩码
简单的sql


inline关键字
为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了 inline修饰符,表示为内联函数。
只有简单的函数才能使用inline关键字。内联函数不能自己调用自己的递归,内部也不能存在while等。并且,内联函数是否内敛还取决于编译器。内联函数的定义最好放在头文件中,并且,inline只有修饰函数的定义才能是内联的,修饰函数的声明不行。
定义在类中的成员函数是默认内敛的。定义在类外的成员函数只有添加上inline关键字才是内联的。


static关键字的作用(作用域)
全局静态变量:对于全局变量,添加static关键字,使其变为全局静态变量,static修改其连接方式,仅该文件内有效。内存中位于静态存储区,存在于整个程序运行期间,初始化:未经初始化的全局静态变量自动初始化为0(自动对象的值是任意的,除非被显式初始化)作用域:
局部静态变量:对于局部变量,添加static关键字,使其变为局部静态变量,这种变量旨在第指令第一次运行到此处时初始化,离开作用域后,不销毁,再次访问该变量时,值不变。
静态函数:函数的定义和声明在默认情况下都是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。这样不会与其他cpp中的同名函数起冲突,但是,不能在头文件中声明static的全局函数,不在cpp内声明非static的全局函数。
类中静态成员:类中的静态成员在一个类中只有一份实例,供该类的所有对象共享。
类中静态成员函数:与静态成员类似,所有对象共享,可以使用<类名>::<静态成员函数名>(<参数表>)调用。但是静态成员函数的实现中不能直接使用非静态成员,要使用非静态成员可以通过对象来引用。


11. static的作用(C++)
1、隐藏:当同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。
static可以用作函数和变量的前缀,对于函数来讲,static的作用仅限于隐藏.
2、static的第二个作用是保持变量内容的持久:存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。
共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围, 说到底static还是用来隐藏的。虽然这种用法不常见
3、static的第三个作用是默认初始化为0(static变量)
4、C++中的作用
1)不能将静态成员函数定义为虚函数。
2)静态数据成员是静态存储的,所以必须对它进行初始化。(程序员手动初始化,否则编译时一般不会报错,但是在Link时会报错误)
3)静态数据成员在<定义或说明>时前面加关键字static。

静态成员函数与普通函数的区别:
在这里插入图片描述


const与#define的区别
1.define 宏定义的常量不具备类型安全,其定义的变量在预处理阶段进行替换。const定义的常量有明确的类型,编译时有明确的值,程序运行过程中,只有const变量只有一个拷贝。宏定义的的常量消耗的内存比const变量大得多。
2.define定义的常量不能用指针去指向,const可以
3.define可以定义一些简单的函数,const不行
4宏定义的作用范围仅限于当前文件。


volatile的作用和用法
volatile关键词的作用是影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化


C++中四种强制类型转换是:

static_cast, dynamic_cast, const_cast, reinterpret_cast
1、const_cast:用于const变量与非const互转,编译时执行,不是运行时执行。
2、static_cast:用于C++内置类型之间的转换,没有运行时类型的检测来保证转换的安全性。
3、dynamic_cast:用于将一个指向基类的指针转换成指向派生类的指针。如果失败返回空值。只能转换类的指针、引用或空类型的指针。主要用于类的上下行的转换。子类向父类转换是安全的,父类向子类转换比static_cast更安全。
4、reinterpret_cast与C类似的强制类型转换
5、为什么不使用C的强制转换
C的强制转换表面上看起来功能强大什么都能转,但是转化不够明确,不能进行错误检查,容易出错


静态链接和动态链接区别
静态链接和动态链接两者最大的区别就在于链接的时机不一样,静态链接是在形成可执行程序前,而动态链接的进行则是在程序执行时
静态链接的过程就已经把要链接的内容已经链接到了生成的可执行文件中,就算你在去把静态库删除也不会影响可执行程序的执行;而动态链接这个过程却没有把内容链接进去,而是在执行的过程中,再去找要链接的内容,生成的可执行文件中并没有要链接的内容,所以当你删除动态库时,可执行程序就不能运行。

就不设置环境变量的情况下来说
动态库一般都会存在/usr/lib/ 目录下;而静态库可以在任何目录下,只要你第一次链接的时候,用绝对路径去链接就行了,之后再删除,是不会影响你的生成的执行文件的。
如若可以设置环境变量的话
动态库和静态库可以放置到你想放的任何地方,只是动态库需要设置环境变量,而静态库链接的时候需要绝对路径。
但一般来说,动态库都会放在放在/usr/lib,应该大家都习惯了,这样也方便寻找,而当链接动态库的时候默认的路径就是/usr/lib。
各自的优缺点:
1、静态链接库执行速度比动态链接库快。(执行过程不需要找链接的内容)
2、动态链接库更节省内存。(未写入要链接的内容)


子网掩码相关
1、子网掩码的概念及作用
①、子网掩码(Subnet Mask)又叫网络掩码、地址掩码,必须结合IP地址一起对应使用。
②、只有通过子网掩码,才能表明一台主机所在的子网与其他子网的关系,使网络正常工作。
③、子网掩码和IP地址做“与”运算,分离出IP地址中的网络地址和主机地址,用于判断该IP地址是在本地网络上,还是在远程网络网上。
④、子网掩码还用于将网络进一步划分为若干子网,以避免主机过多而拥堵或过少而IP浪费。
2、子网掩码的组成
①、同IP地址一样,子网掩码是由长度为32位二进制数组成的一个地址。
②、子网掩码32位与IP地址32位相对应,IP地址如果某位是网络地址,则子网掩码为1,否则为0。
③、举个栗子:如:11111111.11111111.11111111.00000000
3、子网掩码的表示方法
①、点分十进制表示法
二进制转换十进制,每8位用点号隔开
例如:子网掩码二进制11111111.11111111.11111111.00000000,表示为255.255.255.0

②、CIDR斜线记法
IP地址/n
例1:192.168.1.100/24,其子网掩码表示为255.255.255.0,二进制表示为11111111.11111111.11111111.00000000
例2:172.16.198.12/20,其子网掩码表示为255.255.240.0,二进制表示为11111111.11111111.11110000.00000000
不难发现,例1中共有24个1,例2中共有20个1,所以n是这么来的。运营商ISP常用这样的方法给客户分配IP地址。
4、为什么要使用子网掩码?
前面说道,子网掩码可以分离出IP地址中的网络地址和主机地址,那为什么要分离呢?因为两台主机要通信,首先要判断是否处于同一网段,即网络地址是否相同。如果相同,那么可以把数据包直接发送到目标主机,否则就需要路由网关将数据包转发送到目的地。
5、子网掩码的分类
①、缺省子网掩码
也叫默认子网掩码,即未划分子网,对应的网络号的位都置 1 ,主机号都置 0 。
未做子网划分的IP地址:网络号+主机号
A类网络缺省子网掩码: 255.0.0.0,用CIDR表示为/8
B类网络缺省子网掩码: 255.255.0.0,用CIDR表示为/16
C类网络缺省子网掩码: 255.255.255.0,用CIDR表示为/24
②、自定义子网掩码
将一个网络划分子网后,把原本的主机号位置的一部分给了子网号,余下的才是给了子网的主机号。其形式如下:

做子网划分后的IP地址:网络号+子网号+子网主机号
6、子网掩码和IP地址的关系
子网掩码是用来判断任意两台主机的IP地址是否属于同一网络的依据,就是拿双方主机的IP地址和自己主机的子网掩码做与运算,如结果为同一网络,就可以直接通信。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr.liang呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值