HTTP协议中的返回码
一、概述
HTTP状态码一共有五大类:1xx, 2xx, 3xx, 4xx, 5xx。
各个大类的含义如下:
- 1xx: 临时响应代码,少见
- 2xx: 成功响应状态,常见
- 3xx: 重定向
- 4xx: 客户端错误
- 5xx: 服务端错误
HTTP状态码一共有五大类:1xx, 2xx, 3xx, 4xx, 5xx。
各个大类的含义如下:
SELECT是数据库四大基本操作的一种,用于查询表中的数据信息。
基本的查询语法为:SELECT 列1, 列2, ... FROM 表,表示从表中取出对应的列。
SELECT语句的用法多种多样,并且还有很多高级的操作(如排序、分组以及联合等等),是增删改查四种基本操作中用法最多也运用最广的命令。创建测试表stu_info,所有的测试将会在这张表上进行:
CREATE TABLE `stu_info` (
`stu_id` int(12) unsigned zerofill NOT NULL AUTO_INCREMENT,
`stu_name` varchar(255) NOT NULL DEFAULT '',
`age` tinyint(4) NOT NULL,
`classno` tinyint(4) NOT NULL,
`city` varchar(255) NOT NULL,
PRIMARY KEY (`stu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8表中数据:
+--------------+-----------+-----+---------+----------+
| stu_id | stu_name | age | classno | city |
+--------------+-----------+-----+---------+----------+
| 000000000001 | maqian | 24 | 1 | changsha |
| 000000000002 | xiaoming | 19 | 2 | shanghai |
| 000000000003 | xiaohua | 23 | 2 | shenzhen |
| 000000000004 | xiaobai | 22 | 3 | shenzhen |
| 000000000005 | xiaowang | 19 | 4 | hunan |
| 000000000006 | xiaozhou | 20 | 3 | wuhan |
| 000000000007 | xiaoli | 20 | 1 | changsha |
| 000000000008 | xiaopeng | 23 | 1 | changsha |
| 000000000009 | xiaozheng | 22 | 1 | fujian |
+--------------+-----------+-----+---------+----------+查询所有的学生名字:
SELECT stu_name FROM stu_info;结果:
+-----------+
| stu_name |
+-----------+
| maqian |
| xiaoming |
| xiaohua |
| xiaobai |
| xiaowang |
| xiaozhou |
| xiaoli |
| xiaopeng |
| xiaozheng |
+-----------+注意事项:查询时,如果没有明确指定排序对象,返回的数据中顺序没有特殊意义,每次返回的顺序可能都不同。只要保证每次返回的行数是一样的就是正常。
查询所有学生的名字及年龄信息,查询多列时,不同列之间使用,隔开:
SELECT stu_name, age FROM stu_info;结果:
+-----------+-----+
| stu_name | age |
+-----------+-----+
| maqian | 24 |
| xiaoming | 19 |
| xiaohua | 23 |
| xiaobai | 22 |
| xiaowang | 19 |
| xiaozhou | 20 |
| xiaoli | 20 |
| xiaopeng | 23 |
| xiaozheng | 22 |
+-----------+-----+使用通配符*表示查询所有列:
SELECT * FROM stu_info;结果:
+--------------+-----------+-----+---------+----------+
| stu_id | stu_name | age | classno | city |
+--------------+-----------+-----+---------+----------+
| 000000000001 | maqian | 24 | 1 | changsha |
| 000000000002 | xiaoming | 19 | 2 | shanghai |
| 000000000003 | xiaohua | 23 | 2 | shenzhen |
| 000000000004 | xiaobai | 22 | 3 | shenzhen |
| 000000000005 | xiaowang | 19 | 4 | hunan |
| 000000000006 | xiaozhou | 20 | 3 | wuhan |
| 000000000007 | xiaoli | 20 | 1 | changsha |
| 000000000008 | xiaopeng | 23 | 1 | changsha |
| 000000000009 | xiaozheng | 22 | 1 | fujian |
+--------------+-----------+-----+---------+----------+使用*来输出所有列时,会严重降低检索和应用程序的性能。大部分时候,尽量少使用用通配符,明确自己所需要的列。在查询时,可以通过DISTINCT关键字来剔除重复的行。如查询所有的班级(不重复):
SELECT DISTINCT classno FROM stu_info;
结果:
+---------+
| classno |
+---------+
| 1 |
| 2 |
| 3 |
| 4 |
+---------+
当对多个关键字使用DISTINCT时,只有所有列都相同才会被认为是重复的,其中某个字段相同并不会认为是同一个行:
SELECT DISTINCT classno, city FROM stu_info;
上面的数据中,有多个classno=1并且city=changsha的结果,使用DISTINCT之后,这些重复的行被剔除了,而同样classno=但是city=fujian的记录却依然存在:
+---------+----------+
| classno | city |
+---------+----------+
| 1 | changsha |
| 2 | shanghai |
| 2 | shenzhen |
| 3 | shenzhen |
| 4 | hunan |
| 3 | wuhan |
| 1 | fujian |
+---------+----------+
使用LIMIT关键字可以限制输出的结果数量,语法格式为:
LIMIT N:只输出前面N条记录。LIMIT M,N:从第M条记录开始,输出N条记录。LIMIT N OFFSET M:MYSQL从5.0开始支持的语法,作用和第二条一样,从M开始输出N条记录。第三种用法实际上是为了解决用户会混淆M,N究竟是从M开始的N条记录还是N开始的M条记录的问题。
SELECT classno, city FROM stu_info LIMIT 5;
结果:
+---------+----------+
| classno | city |
+---------+----------+
| 1 | changsha |
| 2 | shanghai |
| 2 | shenzhen |
| 3 | shenzhen |
| 4 | hunan |
+---------+----------+
SELECT classno, city FROM stu_info LIMIT 5, 5;
结果:
+---------+----------+
| classno | city |
+---------+----------+
| 3 | wuhan |
| 1 | changsha |
| 1 | changsha |
| 1 | fujian |
+---------+----------+
当实际的记录数量小于剩余记录时,输出的结果并不会达到我们想要的行数。
使用SELECT classno, city from stu_info LIMIT 4 OFFSET 5的结果也和上面一样!排序是查询是最常用的的功能之一,语法格式为ORDER BY col1, col2,表示根据col1和col2排序。
MYSQL支持对单行和多行数据排序,也支持正序和倒序排序。默认情况是正序排序,逆序排序需要手动添加关键字DESC。
输出学生的年龄、班级和名字,并针对年龄排序:
SELECT age, stu_name FROM stu_info ORDER BY age;
结果:
+-----+---------+-----------+
| age | classno | stu_name |
+-----+---------+-----------+
| 19 | 2 | xiaoming |
| 19 | 4 | xiaowang |
| 20 | 3 | xiaozhou |
| 20 | 1 | xiaoli |
| 22 | 3 | xiaobai |
| 22 | 1 | xiaozheng |
| 23 | 2 | xiaohua |
| 23 | 1 | xiaopeng |
| 24 | 1 | maqian |
+-----+---------+-----------+
可以看到,age列都是从小到大排列,而classno还是处于无序的状态。
在上面的基础上,添加对班级排序逻辑。即当学生年龄一致的时候,根据所在的班级排序:
SELECT age, classno, stu_name FROM stu_info ORDER BY age, classno;
结果:
+-----+---------+-----------+
| age | classno | stu_name |
+-----+---------+-----------+
| 19 | 2 | xiaoming |
| 19 | 4 | xiaowang |
| 20 | 1 | xiaoli |
| 20 | 3 | xiaozhou |
| 22 | 1 | xiaozheng |
| 22 | 3 | xiaobai |
| 23 | 1 | xiaopeng |
| 23 | 2 | xiaohua |
| 24 | 1 | maqian |
+-----+---------+-----------+
结果中,所有年龄相同的行,班级序号也是有序的。
逆序输出所有的学生名字:
SELECT stu_name FROM stu_info ORDER BY stu_name DESC;
结果:
+-----------+
| stu_name |
+-----------+
| xiaozhou |
| xiaozheng |
| xiaowang |
| xiaopeng |
| xiaoming |
| xiaoli |
| xiaohua |
| xiaobai |
| maqian |
+-----------+
查询时,可以明确查询的表名和列名,如:
SELECT stu_info.stu_name from stu_info;
注意:不可省略最后的表信息,不要认为列中限定了表名最后就不用再添加表名了。
效果等同于:
SELECT stu_name FROM stu_info;
这种用法一般适用于多表之间的联合查询,当两个表中的字段有重合时,需要明确指定表名来限定查询的是哪个表中的字段。
相对于栈和链表等数据结构来说,树有着更复杂的结构。正如我们平常生活中看到的树一样,它有很多分支,而且分支上面还会有分支。
树的用途十分广泛,最常见的树是二叉树,衍生了很多类型的树,红黑树,搜索树等等,被用来查找效率十分高,一个最典型的应用就是mysql中的索引。
树是由很多个节点构成,要实现一个树最最主要的就是实现树的节点。
链表的遍历算是十分简单了,从头到尾获取next指针的值,如果next不为0,一直打印。
// 遍历链表,结果保存在vector中返回
template <typename T>
std::vector<T> CMyList<T>::traversal() const {
vector<T> v;
CListNode<T> *p = root.getNext();
while (p)
{
v.push_back(p->getData());
p = p->getNext();
}
return v;
} 链表是一种线性结构,通过一个链把所有的节点都链起来,因此叫做链表。它和数组最大的不同是:数组的内存是连续的,而链表不是。数组支持随机读写,但是插入和删除麻烦,链表不支持随机读写,但是插入和删除很方便。在更多场景下,链表用途更广。
一个链表的图形示例如下,每个链表节点都包含了一个数据域和指向下一个节点指针:

链表是由一系列的节点构成,从面向对象角度来说,链表和节点是两个完全不同的东西。节点是链表中的实体,而链表只是节点的抽象,它包含若干个组成链表的节点。因此,链表和节点这两个对象应该区别开来。
一个基本的单向链表支持的操作:
一个节点的结构至少应该包含必要的数据域和next指针域,next只用用于指向下一个节点的地址。
以下是一个最简单的链表节点示例:

链表节点的C++定义:
template<typename T>
class list_node {
// 友元类,方便链表类读取节点元素数据
friend class singly_linklist<T>;
private:
T data; // 数据域
list_node<T> *next; // 下一个节点指针
};链表是由一个个的节点构成,在它的内部只要保存链表头节点的地址,就能找到链表中所有节点地址。
因此一个链表中,只要包含头结点的地址就行了。然后根据其他的扩展,我们可能还需要用到:
链表的结构示意图:

链表的C++定义:
template<typename T>
class singly_linklist {
public:
singly_linklist() : len(0), head(nullptr), tail(nullptr) {}
private:
size_t len; // 链表长度
list_node<T> *head; // 头结点
list_node<T> *tail; // 尾结点
};所有代码使用C++完成,后面的函数无特殊说明都是类成员函数。
以下图为例,不考虑首尾节点状态以及插入位置,一个节点被插入的逻辑是:

在链表类中,定义一个私有的添加节点函数add_node来完成这个插入操作:
/*
* 添加节点操作,新添加的节点需确保不是空值
* @new_node 新节点,不能为空
* @prev 前驱节点,可能为空
* @next 后继节点,可能为空
*/
void add_node(list_node<T> *new_node, list_node<T> *prev, list_node<T> *next) {
if (new_node == nullptr)
return;
len++; // 长度加1
new_node->next = next;
if (prev) {
prev->next = new_node;
}
}头插法的意思是把节点插入到链表头部,这种情况下除了插入节点到链表中,还要注意的是修改链表的head节点指针。

头插法函数实现:
/*
* 插入数据到链表开头
* @node 待插入的数据节点
*/
singly_linklist &push_front(list_node<T> *node) {
if (node == nullptr)
return *this;
// 添加节点到头部
add_node(node, nullptr, head);
// 修改首尾节点指针
head = node;
if (tail == nullptr)
tail = node;
return *this;
}尾插法和头插法相对,尾插法的意思是把节点插入到链表尾部,因此,插入后也要更新尾结点指针。

尾插法代码实现:
/*
* 插入元素到末尾
* @node 待插入的数据节点
*/
singly_linklist &push_front(list_node<T> *node) {
if (node == nullptr)
return *this;
// 添加节点到头部
add_node(node, nullptr, head);
// 修改首尾节点指针
head = node;
if (tail == nullptr)
tail = node;
return *this;
}/*
* 在链表中查找指定值的节点
* @data 待查找节点
* @return 找到返回对应的节点,否则返回nullptr
*/
list_node<T> *find(const T &data) {
list_node<T> *p = head;
// 遍历链表
while (p) {
// 找到了
if(p->data == data)
return p;
// 没找到,继续找下一个
p = p->next;
}
return p;
}在增加或删除节点之前,都要修改前节点的指针指向,因此,需要实现一个查找指定节点的头结点函数。
/*
* 查找某个节点的前一个节点
* @node 当前节点
* @return 如果存在上一个节点,返回上一个节点的地址,否则返回nullptr
*/
list_node<T> *find_prev(list_node<T> *node) {
list_node<T> *p = head;
if (node == nullptr)
return nullptr;
while (p != nullptr) {
if (p->next == node) {
return p;
}
p = p->next;
}
return p;
}以下图为例,删除节点2的操作是:

核心的删除代码:
/*
* 删除节点,待删除的节点请确保不是空值
* @del_node 待删除节点,不为空
* @prev 前驱节点,可能为空
* @next 后继节点,可能为空
*/
void remove_node(list_node<T> *del_node, list_node<T> *prev, list_node<T> *next) {
if (del_node == nullptr)
return;
len--; // 链表长度减1
del_node->next = nullptr; // 被删除节点的next指针置空
if (prev) { // prev可能为空
prev->next = next;
}
}删除节点的一种操作是找到前节点指针,同时要注意的是,删除节点可能导致首尾指针失效,要注意更新首尾指针。
/*
* 删除节点
* @node 待删除节点
*/
singly_linklist<T> &remove(list_node<T> *node) {
list_node<T> *prev;
if (node == nullptr)
return *this;
// 找到前节点
prev = find_prev(node);
// 修改首尾节点指向
if (head == node)
head = node->next;
if (tail == node)
tail = prev;
// 删除节点
remove_node(node, prev, node->next);
delete node;
return *this;
}有了上面的删除指定节点函数之后,删除首尾元素的操作就变得非常简单了:
// 弹出首元素
singly_linklist &pop_front() {
return remove(head);
}
// 弹出尾元素
singly_linklist &pop_back() {
return remove(tail);
}TEST(singly_linklist, push_front) {
int i;
singly_linklist<int> list;
const list_node<int> *p;
vector<int> v{1, 2, 3};
list.push_front(3).push_front(2).push_front(1);
EXPECT_EQ(3, list.get_len());
p = list.get_head();
for (i = 0; i < v.size(); i++) {
EXPECT_EQ(p->get_data(), v[i]);
p = p->get_next();
}
EXPECT_EQ(p, nullptr);
}TEST(singly_linklist, push_back) {
int i;
singly_linklist<int> list;
const list_node<int> *p;
vector<int> v{1, 2, 3};
list.push_back(1).push_back(2).push_back(3);
EXPECT_EQ(3, list.get_len());
p = list.get_head();
for (i = 0; i < v.size(); i++) {
EXPECT_EQ(p->get_data(), v[i]);
p = p->get_next();
}
EXPECT_EQ(p, nullptr);
}TEST(singly_linklist, get_prev) {
int i;
singly_linklist<int> list;
const list_node<int> *p;
list_node<int> *node1, *node2, *node3;
// 插入3个节点
node1 = new list_node<int>(1);
node2 = new list_node<int>(2);
node3 = new list_node<int>(3);
list.push_back(node1).push_back(node2).push_back(node3);
EXPECT_EQ(3, list.get_len());
p = list.find_prev(node1);
EXPECT_EQ(p, nullptr);
p = list.find_prev(node2);
EXPECT_EQ(p, node1);
p = list.find_prev(node3);
EXPECT_EQ(p, node2);
}// 测试删除节点
TEST(singly_linklist, remove) {
int i;
singly_linklist<int> list;
const list_node<int> *p;
list_node<int> *node1, *node2, *node3;
node1 = new list_node<int>(1);
node2 = new list_node<int>(2);
node3 = new list_node<int>(3);
list.push_front(node1);
list.push_back(node2);
list.push_back(node3);
// 删除中间节点
list.remove(node2);
EXPECT_EQ(node1, list.get_head());
EXPECT_EQ(node3, list.get_tail());
EXPECT_EQ(node1->get_next(), node3);
// 删除头节点
list.remove(node1);
EXPECT_EQ(node3, list.get_head());
EXPECT_EQ(node3, list.get_tail());
EXPECT_EQ(node3->get_next(), nullptr);
// 删除尾节点
list.remove(node3);
EXPECT_EQ( list.get_head(), nullptr);
EXPECT_EQ( list.get_tail(), nullptr);
}
队列是一种先进先出的数据结构,因和平常生活中的排队流程一样因此被称为队列。操作逻辑和栈刚好相反。
常用操作:
enqueue: 元素入队dequeue: 首元素出队size: 返回队列中元素的个数empty: 判断队列是否为空front: 返回队首元素它有两个指针分别指向队列开头和结尾,出队和入队的流程为:

顺序栈的实现和使用数组实现原理一样,都是预先申请一段连续的地址块作为数据域,通过栈顶下标或指针移动完成压栈、出栈等操作。不同的是,使用指针的顺序栈支持栈满时扩容操作,原理更倾向于vector的实现。
顺序栈初始化时申请一块固定大小内存空间保存数据,栈顶指针在内存区域来回移动:

链栈的原理和链表的原理一样,通过一个next指针把一个个的节点链起来:

初始时,栈底指针和栈顶指针都为空,每插入一个节点,栈顶指针改变,当前插入节点的next指针指向之前的栈顶元素。
栈是一种“先进后出”的数据结构,最先进入栈的元素位于栈的底端,最后进入的位于顶端。
其主要的接口函数为:
pop(): 弹出顶端元素size(): 返回栈容量empty(): 判断栈是否为空push(T data): 添加元素到栈顶top(): 返回顶端元素