TCP
TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议
它能够提供以下服务:
- 可靠传输
通过序列号、确认应答、重传机制等确保数据完整、准确地从发送端传输到接收端。
三次握手:
- 点对点
- 全双工
- 面向字节流
- 流量控制:通过滑动窗口机制,接收端可以控制发送端的发送速率,防止接收端来不及处理导致数据丢失。
- 拥塞控制:当网络出现拥堵时,TCP能够减少其数据传输速率,避免造成更严重的网络拥塞。
- 数据顺序控制:保证数据按照发送顺序到达接收端。
- 对比:
不能提供的服务:
- 时延和带宽保证
- 多播和广播:TCP是基于点对点的通信模式,不支持多播(一点对多点)和广播(一点对全点)通信。
HTTP
- 使用TCP传输
TCP是一个传输协议,而HTTP全称是超文本传输协议,是在传输时使用TCP协议,HTTP是在应用层进行握手,即服务器上的运行的进程之间的通信 - HTTP不保留状态,或者说无状态stateless
- 消息格式
- 发送:方法(GET POST)+URL+协议
- 相应:协议+状态码+状态词
- 响应状态码:
- 404 Not Found: 表示客户端能够与服务器通信,但服务器找不到请求的资源。
- 200 OK:请求成功,服务器提供了请求的网页
- 5XX - 服务器错误
字符数组初始化方法
- 直接赋值
编译器会自动计算数组str的大小,包括字符串的结尾标识符\0。
char str[] = "Hello, World!";
- 指定大小赋值
数组的大小必须足够大,以存储所有字符和末尾的\0。
char str[14] = "Hello, World!";
- 逐字符填充
逐字符初始化时,必须包括字符串的结束符\0。
如果你指定的大小大于实际初始化的字符数(包括必需的终止符\0),剩余的部分将自动用\0填充。
char str[] = {'H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd', '!', '\0'};
- 二维数组也要注意/0
char str[4][6] = {"hello", "world"};
unsigned int 范围
int 在大多数系统上占用 4 个字节(32 位),其取值范围是从 -2147483648 到 2147483647
unsigned int 在大多数系统上占用 4 个字节(32 位),其取值范围为 0 到 4294967295。
冒泡排序
冒泡排序的基本思想是,重复地遍历待排序序列,每次比较相邻的两个元素,如果它们的顺序错误,则交换它们。通过每次遍历,将最大的元素“冒泡”到最后。
def bubble_sort(arr):
n = len(arr)
for i in range(n):
for j in range(0, n-i-1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
return arr
选择排序
选择排序的基本思想是,每次从待排序的元素中选择最小的元素,放置到已排序序列的末尾。
def selection_sort(arr):
n = len(arr)
for i in range(n):
min_index = i
for j in range(i+1, n):
if arr[j] < arr[min_index]:
min_index = j
arr[i], arr[min_index] = arr[min_index], arr[i]
return arr
补码
补码是一种用于表示有符号整数的编码方式,常用于计算机系统中。在补码表示法中,正整数的二进制表示与无符号整数相同,而负整数的表示稍有不同。
具体来说,对于一个 n 位的补码表示:
对于正整数,其补码与其原码(即二进制表示)相同。
对于负整数,先将其绝对值表示成二进制形式,然后取反(每一位取反),最后加 1。
在一个字节(8bit)的补码中,负数的表示范围是 -128 到 -1,而正数的表示范围是 0 到 127。
参数传递和内存分配
分析运行下面的Test函数会有什么样的结果,为什么?
void GetMemory1(char*p)
{
p = (char*)malloc(100);
}
void Testl(void)
{
char*str =NULL;
GetMemory1(str);
strcpy(str,"hello world");
printf(str);
}
GetMemory() 函数中的指针 p 是按值传递的,而不是按引用传递。在 GetMemory() 函数内部分配了内存,并将其地址存储在 p 中,但这个地址只是 GetMemory() 函数内部的局部变量,它并不会改变 Test() 函数中指针 str 的值。因此,Test() 函数中的 str 指针仍然是 NULL,没有指向任何有效的内存位置。
未定义行为导致程序崩溃,因为试图写入未分配的内存地址。
char *GetMemory2(void)
{
char p[] = "helloworld";
return p;
}
void Test2(void)
{
char *str =NULL;
str= GetMemory2();
printf(str);
}
输出乱码。
GetMemory() 函数中的局部数组 p 是在栈上分配的,它的生命周期仅限于 GetMemory() 函数的执行过程。当 GetMemory() 函数返回时,p 的内存将被释放,但 Test() 函数中的 str 指针仍然指向已释放的内存地址,这会导致未定义行为,可能会输出垃圾值,程序崩溃或其他意外行为。
void GetMemory(char** p, int num)
{
*p = (char*)malloc(num);
}
void Test(void)
{
char* str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
正确打印了hello。利用二级指针做函数形参,成功把str变成分配的大小为100字节连续空间的的起始地址。但是没有用free进行内存释放,造成内存泄漏。
void Test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
虽然调用了 free(str) 释放了内存,但并没有将 str 指针置为 NULL。因此,在 if (str != NULL) 中,虽然 str 已经指向了一片已经释放的内存,但由于 str 本身并不是 NULL,所以条件成立,进入了 if 分支。所以正常进行strcpy(str, “world”);,最后打印world。对已经释放的内存进行操作,也可能导致未定义行为。
函数的继承派生、虚函数
这段代码的输出结果是什么
class Ca
{
public:
Ca() {strcpy(m_info,"fa");printf("Ca”);}
virtual void f() {printf(m info);}
void f1(){printf("fa1”);}
void f2(int i=3){ this->f();printf("%d”,i);}
char m info[10];
};
class Cb:public Ca
{
public:
Cb (){printf("cb”);}
void f(){printf("fb”);}
void f1(){printf("fb1”);}
void f2(int i=5){ this->f();printf("%d”,i);}
};
int main()
{
Ca* p=NULL;
Ca a;
Cb b;
p = &a;
p->f();
p->f1();
p->f2();
p = &b;
p->f();
p->f1();
p->f2();
}
**通过基类指针只能访问派生类的成员变量,但是不能访问派生类的成员函数。**为了消除这种尴尬,让基类指针能够访问派生类的成员函数,C++ 增加了虚函数(Virtual Function)。有了虚函数,基类指针指向基类对象时就使用基类的成员(包括成员函数和成员变量),指向派生类对象时就使用派生类的成员。换句话说,基类指针可以按照基类的方式来做事,也可以按照派生类的方式来做事,它有多种形态,或者说有多种表现方式,我们将这种现象称为多态(Polymorphism)。
Ca
Ca
Cb
fa
fa1
fa3
fb
fa1
fb3
模拟题
两个字符串 start 和 target,长度均为n。每个字符串 仅 由字符’L’、‘R’ 和’_‘组成,其中:字符"L’ 和’R’ 表示片段,其中片段"L’只有在其左侧直接存在一个 空位 时才能向 左 移动,而片段’R’ 只有在其右侧直接存在一个 空位 时才能向 右 移动。字符’'表示可以被 任意 ‘L’或’R’ 片段占据的空位。如果在移动字符串 start 中的片段任意次之后可以得到字符串 target ,返回 true ;否则,返回 false 。
示例 1:
输入:start=“L___R___R”,target="L______RR"输出:true
示例 2:
输入:start=“R_L _”,target=" __LR"
输出:false解释:字符串 start 中的’R’ 片段可以向右移动一步得到"RL"但是,在这一步之后,不存在可以移动的片段,所以无法从字符串 start 得到 target 。
示例 3:输入:start="R",target="R"输出:false解释:字符串 start 中的片段只能向右移动,所以无法从字符串 start 得到 target 。
实现函数
bool canChange(char* start, char* target),并说明思路
class Solution {
public:
bool canChange(string start, string target) {
if(start.size()!=target.size())return false;
vector<pair<char,int>>s,t;
for(int i=0;i<start.size();i++){
if(start[i]!='_'){
s.push_back(make_pair(start[i],i));
}
if(target[i]!='_'){
t.push_back(make_pair(target[i],i));
}
}
if(s.size()!=t.size())return false;
for(int i=0;i<s.size();i++){
if(s[i].first=='L' && s[i].second<t[i].second)return false;
else if(s[i].first=='R' && s[i].second>t[i].second)return false;
else if(s[i].first!=t[i].first)return false;
}
return true;
}
};
将start和target数组里面的非’_'元素全存到vector<pair<char,int>>容器中,然后比较这两个容器,如果相同位置内容不同,则返回false;如果start代表的容器的’L’的下标位置小于target的位置,由于L不能右移,故返回false;同理start的R的位置大于target的R的位置则返回false;其他情况都是返回true。
链表题
leetcode 24 两两交换链表中的节点
https://leetcode.cn/problems/swap-nodes-in-pairs/description/?envType=study-plan-v2&envId=top-100-liked
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummy=new ListNode(0);
dummy->next=head;
ListNode* cur=dummy;
ListNode* pre=dummy;
while(cur->next!=NULL && cur->next->next!=NULL)
{
pre=cur;
cur=cur->next;
ListNode* tmp=cur->next;
cur->next=cur->next->next;
tmp->next=cur;
pre->next=tmp;
}
ListNode* res=dummy->next;
delete dummy;
return res;
}
};