主頁 >  其他 > 順序表超詳細決議 | 從零開始一步步解讀 | 畫圖理解+除錯分析 | 早起狂肝兩萬字【資料結構】

順序表超詳細決議 | 從零開始一步步解讀 | 畫圖理解+除錯分析 | 早起狂肝兩萬字【資料結構】

2021-10-17 08:38:35 其他

前言:

本章節將對順序表的概念進行介紹,著重講解動態順序表,對常用的介面函式進行一個個講解,并進行決議,順序表講解部分將從零實作順序表介面函式,遇到問題我會進行一步步地除錯說明,通過對本章的學習不僅能學會順序表,還能實戰練習下除錯的技能,除錯不僅僅是幫助我們分析程式找到錯誤的,也可以讓我們去觀察和理解程式,除錯才是硬技能!寫一點點測一點點,不要寫完了再來測,這樣我們就可以很輕松的找出問題,

🔗 C語言教學專欄:《維生素C語言》

🔗 除錯的學習:第八章 - 實用除錯技巧


一、線性表的概念

【百度百科】線性表是最基本、最簡單、也是最常用的一種資料結構,線性表(linear list)是資料結構的一種,一個線性表是n個具有相同特性的資料元素的有限序列,
線性表中資料元素之間的關系是一對一的關系,即除了第一個和最后一個資料元素之外,其它資料元素都是首尾相接的(注意,這句話只適用大部分線性表,而不是全部,比如,回圈鏈表邏輯層次上也是一種線性表(存盤層次上屬于鏈式存盤,但是把最后一個資料元素的尾指標指向了首位結點),

📚 概念:線性表(Linear list)是n個具有相同特性的資料元素的有限序列,線性表是一種在實際中廣泛使用的資料結構,常見的線性表有:順序表、鏈表、堆疊、佇列、字串等……

線性表在邏輯上是線性結構,呈現出一條線性,他們在邏輯上是挨著挨著的,但是在物理結構上并不一定是連續的,線性表在物理上存盤時,通常以陣列和鏈式結構的形式存盤,

二、順序表

0x00 順序表的概念

【百度百科】順序表是在計算機記憶體中以陣列的形式保存的線性表,線性表的順序存盤是指用一組地址連續的存盤單元依次存盤線性表中的各個元素、使得線性表中在邏輯結構上相鄰的資料元素存盤在相鄰的物理存盤單元中,即通過資料元素物理存盤的相鄰關系來反映資料元素之間邏輯上的相鄰關系,采用順序存盤結構的線性表通常稱為順序表,順序表是將表中的結點依次存放在計算機記憶體中一組地址連續的存盤單元中,

📚 概念:順序表是用一段物理地址連續的存盤單元依次存盤資料元素的線性結構,一般情況下采用陣列存盤,在陣列上完成資料的增刪查改,順序表一般可以分為靜態順序表和動態順序表:

靜態順序表:使用定長陣列存盤元素

動態順序表:使用動態開辟的陣列存盤

? 順序表的本質是什么?

🔑 順序表的本質就是陣列,但是在陣列的基礎上它還要求陣列是從頭開始存,并且是連續存盤的,不能跳躍間隔,換言之,順序表既然叫順序表,那么資料一定必須是挨著挨著存的,

0x01 靜態順序表

💬 使用定長陣列存盤元素:

#define N 8
typedef int SLDataType

typedef struct SeqList {
    SLDataType array[N];  //定長陣列
    size_t size;          //有效資料的個數
} SeqList;

📚 靜態順序表的特點:如果滿了就不讓插入,

💭 靜態順序表的缺點:是給多少合適呢?這個往往難以斟酌,N給小了不夠用,N給大了又浪費空間,

0x02 動態順序表

💬 使用動態開辟的陣列存盤元素:

typedef int SLDataType

typedef struct SeqList {
    SLDataType* array;    //指向動態開辟的陣列
    size_t size;          //有效資料的個數
    size_t capacity;      //容量空間的大小
} SeqList;

三、介面實作

0x00 Q&A

? 為什么使用動態順序表?

🔑 靜態順序表只適用于確定知道需要存多少資料的場景,如果靜態順序表的定長陣列導致N定大了,就會造成空間浪費,如果N定小了又不夠用,所以現實中基本都是使用動態順序表,可以根據需要動態地分配空間大小,

? 什么是介面函式?

🔑 介面函式是在資料進行操作時進行呼叫的函式,通過呼叫資料結構的介面幫助你完成一系列操作,

0x00 基本增刪查改介面

/* 介面函式 */
void SeqListInit(SL* psl);							   //初始化
void SeqListDestory(SL* psl);						   //銷毀
void SeqListCheckCapacity(SL* psl);					   //檢查是否需要增容
void SeqListPushBack(SL* psl, SLDataType x);	       //尾插
void SeqListPrint(SL* psl);							   //列印
void SeqListPushFront(SL* psl, SLDataType x);	       //頭插
void SeqListPopBack(SL* psl);						   //尾刪
void SeqListPopFront(SL* psl);					       //頭刪
int SeqListFind(SL* psl, SLDataType x);				   //查找
int SeqListInsert(SL* psl, int pos, SLDataType x);     //指定位置插入
int SeqListEarse(SL* psl, int pos);			           //指定位置洗掉

0x01順序表初始化(SeqListInit)

📜 為了養成模塊化好習慣,我們盡量把代碼分開來寫,首先打開 VS2017,在解決方案資源管理器中的 "頭檔案" 檔案夾中創建 SeqList.h 用來存放頭檔案,在 "源檔案" 檔案夾中創建 SeqList.c 用來實作函式,Test.c 用來測驗我們的順序表:

💬 SeqList.h

#pragma once
#include <stdio.h>
#include <stdlib.h>

typedef int SLDataType;

/* 動態順序表 */
typedef struct SeqList {
	SLDataType* array;
	int size;            //有效資料個數
	int capacity;        //陣列實際能存資料的空間容量是多大
}SL;

/* 介面函式 */
void SeqListInit(SL* psl);

🔑 解讀:

為了避免同一個頭檔案被包含多次我們可以使用 #pragma once(在C語言系列預處理章節提到過,為了加深印象我在這里不得不再重申一下)

為了方便后續修改資料型別,我們可以使用 typedef 定義一個新的資料型別,這里我們把它取名為 SLDataType(順序表資料型別)

我們為了讓定義的結構體使用時更方便,我們同樣可以使用 typedef 將其定義為 SL此時 SL = struct SeqList,后續在使用時可以更加方便),

最后宣告我們要實作的介面函式——順序表初始化函式并取名為 SeqListInit ,引數部分為 SL* psl這里的 SL 就是之前被 typedef 定義的結構體 struct SeqList ),考慮到形參是只是實參的臨時拷貝的問題,為了能夠傷其內部,這里我們會使用址傳遞,所以,為了能夠接收 "這個" 地址,我們使用指標 psl 來接收,

💬 SeqList.c

#include "SeqList.h"

/* 初始化 */
void SeqListInit(SL* psl) {
	psl->array = NULL;
	psl->size = psl->capacity = 0;
}

🔑 解讀:

首先引入我們自己創建的頭檔案 #include "SeqList.h" ,我們就可以開始動手實作順序表初始化函式了,

首先通過 psl 指向 array,將陣列為空,因為是初始化,所以將有效資料個數和陣列時即能存資料的空間容量一并置為0,

💬 Test.c

#include "SeqList.h"

void TestSeqList1() {
	SL sl;
	SeqListInit(&sl);
}

int main() {
	TestSeqList1();

	return 0;
}

🔑 解讀:測驗代碼部分,我們不在 main 函式內直接測驗而是通過函式來測驗,這樣的好處是可以方便我們選擇性的測驗一部分的代碼,

為了測驗,TestSeqList1 函式中首先創建一個結構體,這里取名為 sl ,隨后將 sl 址傳遞傳入 SeqListInit 函式中進行測驗,

我們運行代碼發現代碼可以成功運行,我們下面進入除錯來看一下 SeqListInit 函式是否起作用了,

🐞 除錯代碼:

至此,SeqListInit 函式就寫好了,

0x02 尾插(SeqListPushBack)

📚 說明:所謂的尾插,就是在后面進行插入,

💡 出現三種情況:

① 第一種情況是順序表壓根就沒有空間,

② 第二種情況就是我們創建的 capacity 空間滿了,

③ 第三種情況是空間足夠,直接插入資料即可,

💬 SeqList.h

...
void SeqListPushBack(SL* psl, SLDataType x);  //尾插

🔑 解讀:SLDataType x 是要添加到順序表的元素,

💬 SeqList.c

...
/* 尾插 */
void SeqListPushBack(SL* psl, SLDataType x) {		
	// 首先判斷有沒有空間,如果沒有空間或者空間不足,那么我們就擴容
	if (psl->size == psl->capacity) {
        // 如果容量是0(第一次)就賦4,如果不是0,就把容量翻一倍
		int new_capacity = psl->capacity == 0 ? 4 : psl->capacity*2;
		// 這里使用realloc,因為如果原空間為空,就等于malloc,調整為 new_capacity個SLDataType大小的空間
		SLDataType* tmp_array = (SLDataType*)realloc(psl->array, new_capacity * sizeof(SLDataType));
        // 檢測是否realloc成功
		if (tmp_array == NULL) {
			printf("realloc fail {擴容失敗}\n");
			exit(-1);
		}
		// 更新它們的大小
		psl->array = tmp_array;
		psl->capacity = new_capacity;
	}

	//插入
	psl->array[psl->size] = x;
	psl->size++;
}

🔑 解讀:

根據我們剛才分析的三種情況,首先我們需要判斷是空間是否足夠,判斷思路如下:如果 size == capacity有效資料個數等于實際能存的最大空間容量),我們進行擴容操作,

如果空間不足,我們就創建一個變數 new_capacity 用來存放 "新容量" ,int new_capacity = psl->capacity 首先把 capacity 的值賦值給這個 "新容量" ,因為考慮到第一次使用 capacity 大小為0,翻倍會出問題(0乘以任何數都是0),這里巧妙地使用三目運算子:int new_capacity = psl->capacity == 0 ? 4 : psl->capacity*2 , 如果 capacity 為 0 (第一次使用大小是0),就把4賦值給它;如果不為0,就把 capacity 的值翻一倍(x2)

? 這里為什么要x2(翻一倍)

💡 隨便你,你想一次擴容多少就擴容多少,乘2只是一個比較折中的擴容容量方案,

此時,new_capacity 中就存放了擴容的新容量(如果第一次使用為容量為4,容量擴容后為capacity的二倍)

確定好新的容量后,我們可以對陣列動刀子了,為了防止翻車,我們先創建一個臨時陣列來擴容,這里要進行動態記憶體開辟,我們選擇使用 realloc 而不是 malloc 函式,因為 realloc 可以調整并且如果是第一次用自帶 malloc 的效果:

(摘自MSDN)

下面我們來看是如何擴容的:

🔗 如果還是比較懵,建議詳細復習: 《維生素C語言》第十三章 - 動態記憶體管理

這里我們還加了一段判斷,如果你動態記憶體管理學的比較好這里就不難理解:

如果沒有問題,就可以進行賦值操作了,將 new_capacity 交給 psl->capacity,將 tmp_array 交給 psl->array

擴容成功后就可以插入了,插入相對來說就很很簡單了,

🔺 當然,如果一開始空間足夠就不需要擴容,就可以直接跳到這里:

💬 Test.c

#include "SeqList.h"

void TestSeqList1() {
	SL sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
}

int main() {
	TestSeqList1();

	return 0;
}

🔑 解讀:我們來測驗下 SeqListPushBack 尾插功能是否實作成功了,我們之前定義的 SLDataTypeint 型,所以我們傳入數字進行測驗,

🐞 除錯:

至此,SeqListPushBack 函式就寫好了,

0x03 順序表列印(SeqListPrint)

📚 順序表列印就是列印順序表內資料,我們可以利用回圈解決,

💬 SeqList.h

···
void SeqListPrint(SL* psl);        //列印

🔑 解讀:因為是單純的列印,所以只需要把順序表傳過去就行,

💬 SeqList.c

/* 列印 */
void SeqListPrint(SL* psl) {
	int i = 0;
	for (i = 0; i < psl->size; i++) {
		printf("%d ", psl->array[i]);
	}
	printf("\n");
}

🔑 解讀:遍歷整個順序表,利用 for 回圈就可以輕松解決,把他們都列印出來就可以了,

💬 Test.c

void TestSeqList1() {
	SL sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);

	SeqListPrint(&sl);
}

🚩 運行結果如下:

0x04 銷毀(SeqListDestroy)

📚 因為是動態開辟的,所以如果空間不用我們就需要銷毀掉,如果不銷毀會存在記憶體泄漏的風險,所以與之對應的我們寫一個銷毀的介面函式,

💬 SeqList.h

...
void SeqListDestory(SL* psl);		//銷毀

💬 SeqList.c

/* 銷毀 */
void SeqListDestory(SL* psl) {
	//首先把空間還給作業系統
	free(psl->array);
	psl->array = NULL; //置空防止空指標
	psl->capacity = psl->size = 0; //空間置0
}

🔑 解讀:首先使用 free 函式把動態開辟的記憶體空間釋放掉,因為 free 之后那塊開辟的記憶體空間已經不在了,為了防止野指標,我們還需要手動把它置為空指標,最后再把 capacity size 全部設為0,做到空著手來,空著手去,銷毀部分就實作好了,

💬 Test.c

void TestSeqList1() {
	SL sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);

	SeqListPrint(&sl);

	SeqListDestory(&sl);
}

0x05 尾刪(SeqListPopBack)

📚 如果我們想把第一個資料洗掉,最簡單的思路是把要尾刪的地方置為 0,然后再把 size-- 即可實作,這就是尾刪:

💬 SeqList.h

void SeqListPopBack(SL* psl);                    //尾刪

💬 SeqList.c

/* 尾刪 */
void SeqListPopBack(SL* psl) {
	psl->array[psl->size - 1] = 0;		//因為是下標,所以size-1才能對的上
	psl->size--;	//實際有效個數-1
}

🔑 解讀:首先 psl->array[psl -> size - 1] 這里 -1 ,是因為下標的原因,減1才可以對的上,這里把末尾的元素設為了0,此時再 size-- 讓實際有效個數減1,

💬 Test.c:我們來洗掉兩個資料測驗一下

void TestSeqList1() {
	SL sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); //列印

	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPrint(&sl);  //列印

    SeqListDestory(&sl); //銷毀
}

🚩 運行結果:

🤔 不知道你有沒有發現,其實我們只需要 psl -> size-- 就可以了…… 找到尾部的目標然后把他置為0其實沒有意義,因為 size 一減,貨一拉,就什么痕跡都沒有了,是 size 標識了我們存入了多少個有效資料,比如有5個資料,size-- 后就只有前4個資料有意義了,所以,尾刪我們只需要 size-- ,就可以達到目的:

/* 尾刪 */
void SeqListPopBack(SL* psl) {
	// psl->array[psl->size - 1] = 0;
	psl->size--;						//實際有效個數-1
}

? 尾刪就這么簡單?

當然不是,我們還需要考慮一些情況的發生,如果我們一直刪,刪到沒有資料可以刪了,是不是會出現問題呢?我們試著刪完5個資料后繼續刪,然后再插入輸入列印看看:


void TestSeqList1() {
	SL sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); //列印

	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPrint(&sl); //列印
	
	SeqListPushBack(&sl, 10);
	SeqListPushBack(&sl, 20);
	SeqListPrint(&sl); //列印

	SeqListDestory(&sl); //銷毀
}

🚩 運行結果如下:

(吐槽:這時候一列印,就爆炸了,以為寫的很完美的尾插,一直以為它是不會翻車的,就,就,爆炸了,碎成了片片,我同情它甚至超過了同情我自己,)

🐞 不慌,我們來除錯看看!

💡 解決方案:添加 SeqListPopBack 限制條件

方式一:如果沒有資料了,使呼叫 SeqListPopBack 沒效果(儒雅隨和的方式)

💬 SeqList.c

/* 尾刪 */
void SeqListPopBack(SL* psl) {
	if (psl->size > 0) {
		// psl->array[psl->size - 1] = 0;
		psl->size--;	//實際有效個數-1
	}
}

方式二:使用斷言,直接不允許你做類似這樣的動作,你在呼叫這個介面之前你必須確定它是有資料的,如果沒有資料就不能調了,調了就給你報錯,

💬 SeqList.h:使用斷言需引入 assert.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
···

💬 SeqList.c

/* 尾刪 */
void SeqListPopBack(SL* psl) {
	//if (psl->size > 0) {
	//	// psl->array[psl->size - 1] = 0;
	//	psl->size--;	//實際有效個數-1
	//}

	assert(psl->size > 0);
	psl->size--;
}

🔺 至于選擇儒雅隨和的方式還是暴力解決的方式,就看你自己喜好了,

0x06 頭插(SeqListPushFront)和檢查是否需要增容(SeqListCheckCapacity)

📚 順序表要求資料是連續存盤的,且必須是從頭開始存盤,所以,對于順序表而言如果要實作頭插,就需要把資料往后挪動,不能從前往后挪,如果從前往后挪就挪就會把后面的資料覆寫掉,

💬 SeqList.h

void SeqListPushFront(SL* psl, SLDataType x);    //頭插

💬 SeqList.c

/* 頭插 */
void SeqListPushFront(SL* psl, SLDataType x) {
	// 挪動資料
	int end = psl->size - 1; // 因為指向的是資料的下標,所以要 -1
	while (end >= 0) { 
		psl->array[end + 1] = psl->array[end];
		end--; //讓end指向下一個要移的資料
	}

	// 此時第一個位置已經被騰出來了,可以插入了
	psl->array[0] = x;
	psl->size++;
}

🔑 解讀:首先創建一個 end 變數用來指向要移動的資料,因為指向的是資料的下標,所以是 size 要減 1 ,隨后進入 while 回圈,如果 end >= 0 說明還沒有移動完,就會進入回圈,回圈體內利用下標,進行向后移動操作,移動結束后再 end-- ,進行下一個資料的向后移動,挪動資料成功后,就可以插入了,此時順序表第一個位置就被騰出來了,就可以在下標0位置插入欲插入的資料 x 了,最后記得 size++ ,

💬 Test.c:測驗頭插

我們之前用的 TestSeqList1 測驗函式里東西太多了很亂,現在我們再創建一個 TestSeqList2 函式來繼續測驗,現在就能體現出不在 main 函式內直接測驗而是通過函式來測驗的好處了,我們可以選擇性地測驗一部分代碼,互相之間不干擾,

void TestSeqList2() {
	SL sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); //列印

	SeqListPushFront(&sl, -1);
	SeqListPushFront(&sl, -2);
	SeqListPushFront(&sl, -3);
	SeqListPrint(&sl); //列印

	SeqListDestory(&sl); //銷毀
}

int main() {
	// TestSeqList1();
	TestSeqList2();

	return 0;
}

🚩 運行結果如下:(先插入了12345,又在12345前面頭插了 -1、-2、-3)

? 我們是不是少了點啥?

💡 我們還需要檢查是否需要擴容,和尾插一樣,

📚 我們之前在完成 SeqListPushBack 尾插的時候就已經寫好了,我們不妨把它抽取出來寫成一個函式,方便以后多次呼叫:

💬 SeqList.c:檢查是否需要增容函式

/* 檢查是否需要擴容 */
void SeqListCheckCapacity(SL* psl) {
	// 首先判斷有沒有空間,如果沒有空間或者空間不足,那么我們就擴容
	if (psl->size == psl->capacity) {
		// 如果容量是0(第一次)就賦4,如果不是0,就把容量翻一倍
		int new_capacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
		// 這里使用realloc,因為如果原空間為空,就等于malloc,調整為 new_capacity個SLDataType大小的空間
		SLDataType* tmp_array = (SLDataType*)realloc(psl->array, new_capacity * sizeof(SLDataType));
		// 檢測是否realloc成功
		if (tmp_array == NULL) {
			printf("realloc fail {擴容失敗}\n");
			exit(-1);
		}
		// 更新它們的大小
		psl->array = tmp_array;
		psl->capacity = new_capacity;
	}
}

🔑 解讀:我們把之前寫在 SeqListPushBack 中的檢查增容的代碼直接復制粘貼過來就可以了,這樣一來,我們需要檢查是否需要增容時直接呼叫 SeqListCheckCapacity 函式就可以了!

💬 SeqList.c:更新下尾插函式

/* 尾插 */
void SeqListPushBack(SL* psl, SLDataType x) {
	//檢查增容
	SeqListCheckCapacity(psl); 

	//插入
	psl->array[psl->size] = x;
	psl->size++;
}

💬 SeqList.c:更新下頭插函式

/* 頭插 */
void SeqListPushFront(SL* psl, SLDataType x) {
	//檢查增容
	SeqListCheckCapacity(psl); 

	// 挪動資料
	int end = psl->size - 1; // 因為指向的是資料的下標,所以要 -1
	while (end >= 0) { 
		psl->array[end + 1] = psl->array[end];
		end--; //讓end指向下一個要移的資料
	}

	// 此時第一個位置已經被騰出來了,可以插入了
	psl->array[0] = x;
	psl->size++;
}

📌 如果不擴容,繼續往下添加會造成的后果:【不感興趣可跳過】

💬 Test.c:把尾插函式內的 SeqListCheckCapacity(psl); 注釋掉

void TestSeqList2() {
	SL sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); //列印

	SeqListPushFront(&sl, -1);
	SeqListPushFront(&sl, -2);
	SeqListPushFront(&sl, -3);
	SeqListPushFront(&sl, -4); //繼續添加
	SeqListPrint(&sl); //列印

	SeqListDestory(&sl); //銷毀
}

0x07 頭刪(SeqListPopFront)

📚 如果我們想把第一個資料洗掉,用尾刪的方法直接把 size-- 顯然是沒有用的了,因為順序表資料是從頭開始存且有順序的, size-- 無效的也只是最后一個資料,所以要想實作頭刪,我們不得不把資料先往前挪動,然后再 --size ,

💬 SeqList.h

···
void SeqListPopFront(SL* psl);                  //頭刪

💬 SeqList.c

/* 頭刪 */
void SeqListPopFront(SL* psl) {
	assert(psl->size > 0);

	//挪動資料
	int begin = 1;
	while (begin < psl->size) {
		psl->array[begin - 1] = psl->array[begin];
		begin++;
	}
    
    //int begin = 0;
	//while (begin < psl->size - 1) {
	//	psl->array[begin] = psl->array[begin + 1];
	//	begin++;
	//}

	psl->size--; //實際有效個數-1
}

🔑 解讀:首先斷言順序表有資料,這個前面講過了這里就不再贅述了,然后開始挪動資料,創建一個 begin 變數并賦個 1,然后進入回圈,只要 size 大于 begin 就會進入回圈,每次進入回圈后將下標 begin -1 上的資料賦給下標 begin 上的資料,這樣就達到了右向左移動的目的,最后 begin++ 移動下一個(如果滿足條件的話),移動完畢后,第一個資料就被第二個資料覆寫掉了,而第二個資料被第三個資料覆寫掉了……最后多出來的一塊我們再用 size-- 解決掉,實際容量減1 ,此時,就實作了頭刪,

💬 Test.c:測驗代碼

void TestSeqList2() {
	SL sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); //列印

	SeqListPushFront(&sl, -1);
	SeqListPushFront(&sl, -2);
	SeqListPushFront(&sl, -3);
	SeqListPushFront(&sl, -4);
	SeqListPrint(&sl); //列印

	SeqListPopFront(&sl);
	SeqListPopFront(&sl);
	SeqListPrint(&sl); //列印

	SeqListDestory(&sl); //銷毀
}

🚩 運行結果如下:

0x08 查找位置(SeqListFind)

📚 查找順序表中某值的位置,如果找到了就回傳該值的下標,沒有找到我們就回傳 -1 ,

💬 SeqList.h

···
int SeqListFind(SL* psl, SLDataType x);				   //查找

💬 SeqList.c:查找

/* 查找 */
int SeqListFind(SL* psl, SLDataType x) {
	int i = 0;
	for (i = 0; i < psl->size; i++) {
		if (psl->array[i] == x) {
			return i;
		}
	}
	// 沒找到
	return -1;
}

🔑 解讀:首先遍歷整個順序表,如果 psl->array[i] == x 就回傳 i 接,沒找到就回傳 -1,這里我們用的方法就是簡單粗暴的 O(N) Find 暴力求解,當然你還可以試著用二分查找做,排序一下,寫一個二分查找的介面,

💬 Test.c

void TestSeqList3() {
	SL sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); //列印

	int ret = SeqListFind(&sl, 3);
	if (ret != -1)
		printf("找到了,下標為%d\n", ret);
	else
		printf("沒找到!\n");


	SeqListDestory(&sl); //銷毀
}

int main() {
	// TestSeqList1();
	// TestSeqList2();
	TestSeqList3();

	return 0;
}

🚩 運行結果如下

0x09 在指定的下標位置插入(SeqListInsert)

📚 順序表要求資料從第一個開始放并且資料是連續存盤的,所以我們就要注意下指定指定的下標位置 pos 的位置了!有些位置并不可以,所以我們需要進行一些檢查,

💬 SeqList.h

int SeqListInsert(SL* psl, int pos, SLDataType x);  //指定位置插入

💬 SeqList.c

/* 指定位置插入 */
int SeqListInsert(SL* psl, int pos, SLDataType x) {
	//if (pos > psl->size || pos < 0) {
	//	printf("pos invalid {pos非法}\n");
	//	return;
	//}

	assert(pos >= 0 && pos <= psl->size);

	//檢查增容
	SeqListCheckCapacity(psl);

	int end = psl->size - 1;
	while (end >= pos) {
		psl->array[end + 1] = psl->array[end];
		end--;
	}
	 
	//插入
	psl->array[pos] = x;
	psl->size++;
}

🔑 解讀:首先添加 pos 位置的限定條件,可以根據自己的喜好選擇是儒雅隨和的處理方式,還是暴力處理方式,這里我的選擇是簡單粗暴地使用斷言解決,限定 pos >= 0 并且 pos <= psl->size 從而保證 pos 合法,然后,因為是插入所以免不了要檢查增容,直接呼叫之前寫好的檢查增容的函式即可,檢查完后就可以開始移動了,和頭插差不多,我們創建一個變數 end 記錄最后一個的下標(psl->size-1),并通過它來指向要移動的資料,最后進入 while 回圈,以 end >= pos 作為條件,移動完后,x 的位置就騰出來了,再把 x 插入進去,最后再 size++,就完成了,

💬 Test.c

void TestSeqList3() {
	SL sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); //列印

	SeqListInsert(&sl, 2, 30);
	SeqListPrint(&sl); //列印

	int pos = SeqListFind(&sl, 4);
	if (pos != -1) {
		SeqListInsert(&sl, pos, 40);
	}
	SeqListPrint(&sl); //列印
	
	SeqListInsert(&sl, 0, -10);
	SeqListPrint(&sl); //列印

	SeqListInsert(&sl, 8, 80);
	SeqListPrint(&sl); //列印

	    
	SeqListDestory(&sl); //銷毀
}

int main() {
	// TestSeqList1();
	// TestSeqList2();
	TestSeqList3();

	return 0;
}

🔑 解讀:這里為了防止有問題,我們不妨多測測驗,

🚩 運行結果如下:

? 頭插函式和尾插函式的修改:

我們都把功能這么強的 SeqListInsert 寫出來了,我們之前寫的頭插和尾插介面函式是不是可以直接復用下 SeqListInsert

💬 SeqList.c:尾插復用 SeqListInsert

/* 尾插 */
void SeqListPushBack(SL* psl, SLDataType x) {
	SeqListInsert(psl, psl->size, x);
}

💬 SeqList.c:頭插復用 SeqListInsert

/* 頭插 */
void SeqListPushFront(SL* psl, SLDataType x) {
	SeqListInsert(psl, 0, x);
}

💬 Test.c:測驗下有沒有問題,先尾插12345,再頭插12345

void TestSeqList4() {
	SL sl;
	SeqListInit(&sl);
	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); //列印

	SeqListPushFront(&sl, 1);
	SeqListPushFront(&sl, 2);
	SeqListPushFront(&sl, 3);
	SeqListPushFront(&sl, 4);
	SeqListPushFront(&sl, 5);
	SeqListPrint(&sl); //列印

    
	SeqListDestory(&sl); //銷毀
}

int main() {
	// TestSeqList1();
	// TestSeqList2();
	// TestSeqList3();
	TestSeqList4();


	return 0;
}

🚩 運行結果如下:

🔺 我們把 Insert 寫好了,就可以直接復用到前面的寫的尾插和頭插了,其實只要寫好 SeqListInsert 任意位置插入,頭尾就都能控制了,

0x10 洗掉指定位置的資料(SeqListErase)

📚 洗掉指定位置的資料,我們仍然要限制 pos 的位置,限制條件部分和 SeqListInsert 不同的是,因為 psl->size 這個位置沒有效資料,所以洗掉的位置不能是 psl->size

💬 SeqList.h

···
int SeqListEarse(SL* psl, int pos);			       //指定位置洗掉

💬 SeqList.c

/* 指定位置洗掉 */
int SeqListEarse(SL* psl, int pos) {
	assert(pos >= 0 && pos < psl->size);

	int begin = pos + 1;
	while (begin < psl->size) {
		psl->array[begin - 1] = psl->array[begin];
		begin++;
	}

	psl->size--;
}

💬 Test.c:測驗下代碼

void TestSeqList4() {
	SL sl;
	SeqListInit(&sl);
	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); //列印

	SeqListPushFront(&sl, 1);
	SeqListPushFront(&sl, 2);
	SeqListPushFront(&sl, 3);
	SeqListPushFront(&sl, 4);
	SeqListPushFront(&sl, 5);
	SeqListPrint(&sl); //列印

	SeqListEarse(&sl, 2);
	SeqListPrint(&sl); //列印

    
	SeqListDestory(&sl); //銷毀
}

int main() {
	// TestSeqList1();
	// TestSeqList2();
	// TestSeqList3();
	TestSeqList4();

	return 0;
}

🚩 運行結果如下:

? 對應的,和上面一樣,頭刪尾刪都可以復用 SeqListEarse

💬 SeqList.c:頭刪復用 SeqListEarse

/* 頭刪 */
void SeqListPopFront(SL* psl) {
	SeqListEarse(psl, 0);
}

💬 SeqList.c:尾刪復用 SeqListEarse

/* 尾刪 */
void SeqListPopBack(SL* psl) {
	/*
	//if (psl->size > 0) {
	//	 psl->array[psl->size - 1] = 0;
	//	psl->size--;	//實際有效個數-1
	//}

	assert(psl->size > 0);
	psl->size--;
	*/
	
	SeqListEarse(psl, psl->size - 1);
}

💬 Test.c:測驗下代碼

void TestSeqList4() {
	SL sl;
	SeqListInit(&sl);
	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); //列印

	SeqListPushFront(&sl, 1);
	SeqListPushFront(&sl, 2);
	SeqListPushFront(&sl, 3);
	SeqListPushFront(&sl, 4);
	SeqListPushFront(&sl, 5);
	SeqListPrint(&sl); //列印

	SeqListEarse(&sl, 2);
	SeqListPrint(&sl); //列印

	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPopFront(&sl);
	SeqListPopFront(&sl);
	SeqListPrint(&sl); //列印
        

	SeqListDestory(&sl); //銷毀
}

int main() {
	// TestSeqList1();
	// TestSeqList2();
	// TestSeqList3();
	TestSeqList4();

	return 0;
}

🚩 運行結果如下:

四、完整代碼

💬 SeqList.h:頭檔案以及函式申明

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>


typedef int SLDataType;

/* 動態順序表 */
typedef struct SeqList {
	SLDataType* array;
	int size;            //有效資料個數
	int capacity;        //陣列實際能存資料的空間容量是多大
}SL;


/* 介面函式 */
void SeqListInit(SL* psl);							   //初始化
void SeqListDestory(SL* psl);						   //銷毀
void SeqListCheckCapacity(SL* psl);					   //檢查是否需要增容
void SeqListPushBack(SL* psl, SLDataType x);	       //尾插
void SeqListPrint(SL* psl);							   //列印
void SeqListPushFront(SL* psl, SLDataType x);	       //頭插
void SeqListPopBack(SL* psl);						   //尾刪
void SeqListPopFront(SL* psl);					       //頭刪

int SeqListFind(SL* psl, SLDataType x);				   //查找
int SeqListInsert(SL* psl, int pos, SLDataType x);     //指定位置插入
int SeqListEarse(SL* psl, int pos);			           //指定位置洗掉

💬 SeqList.c:介面實作

#include "SeqList.h"

/* 初始化 */
void SeqListInit(SL* psl) {
	psl->array = NULL;
	psl->size = psl->capacity = 0;
}

/* 銷毀 */
void SeqListDestory(SL* psl) {
//首先把空間還給作業系統
free(psl->array);
psl->array = NULL; //置空防止空指標
psl->capacity = psl->size = 0; //空間置0
}

/* 列印 */
void SeqListPrint(SL* psl) {
	int i = 0;
	for (i = 0; i < psl->size; i++) {
		printf("%d ", psl->array[i]);
	}
	printf("\n");
}

/* 檢查是否需要擴容 */
void SeqListCheckCapacity(SL* psl) {
	if (psl->size == psl->capacity) {
		// 如果容量是0(第一次)就賦4,如果不是0,就把容量翻一倍
		int new_capacity = psl->capacity == 0 ? 4 : psl->capacity * 2;
		// 這里使用realloc,因為如果原空間為空,就等于malloc,調整為 new_capacity個SLDataType大小的空間
		SLDataType* tmp_array = (SLDataType*)realloc(psl->array, new_capacity * sizeof(SLDataType));
		// 檢測是否realloc成功
		if (tmp_array == NULL) {
			printf("realloc fail {擴容失敗}\n");
			exit(-1);
		}
		// 更新它們的大小
		psl->array = tmp_array;
		psl->capacity = new_capacity;
	}
}

/* 尾插 */
void SeqListPushBack(SL* psl, SLDataType x) {
	/*
	//檢查增容
	SeqListCheckCapacity(psl);

	//插入
	psl->array[psl->size] = x;
	psl->size++; 
	*/

	SeqListInsert(psl, psl->size, x);
}

/* 尾刪 */
void SeqListPopBack(SL* psl) {
	/*
	//if (psl->size > 0) {
	//	 psl->array[psl->size - 1] = 0;
	//	psl->size--;	//實際有效個數-1
	//}

	assert(psl->size > 0);
	psl->size--;
	*/
	
	SeqListEarse(psl, psl->size - 1);
}

/* 頭插 */
void SeqListPushFront(SL* psl, SLDataType x) {
	/*
	//檢查增容
	SeqListCheckCapacity(psl);

	// 挪動資料
	int end = psl->size - 1; // 因為指向的是資料的下標,所以要 -1
	while (end >= 0) {
		psl->array[end + 1] = psl->array[end];
		end--; //讓end指向下一個要移的資料
	}

	// 此時第一個位置已經被騰出來了,可以插入了
	psl->array[0] = x;
	psl->size++;
	*/

	SeqListInsert(psl, 0, x);
}


/* 頭刪 */
void SeqListPopFront(SL* psl) {
	assert(psl->size > 0);
	/*
	// 挪動資料
	int begin = 1;
	while (begin < psl->size) {
		psl->array[begin - 1] = psl->array[begin];
		begin++;
	}

	psl->size--; //實際有效個數-1
	*/
	
	SeqListEarse(psl, 0);
}

/* 查找 */
int SeqListFind(SL* psl, SLDataType x) {
	int i = 0;
	for (i = 0; i < psl->size; i++) {
		if (psl->array[i] == x) {
			return i;
		}
	}
	// 沒找到
	return -1;
}

/* 指定位置插入 */
int SeqListInsert(SL* psl, int pos, SLDataType x) {
	//if (pos > psl->size || pos < 0) {
	//	printf("pos invalid {pos非法}\n");
	//	return;
	//}

	assert(pos >= 0 && pos <= psl->size);

	//檢查增容
	SeqListCheckCapacity(psl);

	int end = psl->size - 1;
	while (end >= pos) {
		psl->array[end + 1] = psl->array[end];
		end--;
	}
	 
	//插入
	psl->array[pos] = x;
	psl->size++;
}

/* 指定位置洗掉 */
int SeqListEarse(SL* psl, int pos) {
	assert(pos >= 0 && pos < psl->size);

	int begin = pos + 1;
	while (begin < psl->size) {
		psl->array[begin - 1] = psl->array[begin];
		begin++;
	}

	psl->size--;
}

💬 Test.c:測驗用例

#include "SeqList.h"

void TestSeqList1() {
	SL sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); //列印

	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPrint(&sl); //列印
	
	SeqListPushBack(&sl, 10);
	SeqListPushBack(&sl, 20);
	SeqListPrint(&sl); //列印

	SeqListDestory(&sl); //銷毀
}

void TestSeqList2() {
	SL sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); //列印

	SeqListPushFront(&sl, -1);
	SeqListPushFront(&sl, -2);
	SeqListPushFront(&sl, -3);
	SeqListPushFront(&sl, -4);
	SeqListPrint(&sl); //列印

	SeqListPopFront(&sl);
	SeqListPopFront(&sl);
	SeqListPrint(&sl); //列印

	SeqListDestory(&sl); //銷毀
}

void TestSeqList3() {
	SL sl;
	SeqListInit(&sl);

	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); //列印

	int ret = SeqListFind(&sl, 3);
	if (ret != -1)
		printf("找到了,下標為%d\n", ret);
	//else
		printf("沒找到!\n");

	SeqListInsert(&sl, 2, 30);
	SeqListPrint(&sl); //列印

	int pos = SeqListFind(&sl, 4);
	if (pos != -1) {
		SeqListInsert(&sl, pos, 40);
	}
	SeqListPrint(&sl); //列印
	
	SeqListInsert(&sl, 0, -10);
	SeqListPrint(&sl); //列印

	SeqListInsert(&sl, 8, 80);
	SeqListPrint(&sl); //列印

	SeqListDestory(&sl);
}

void TestSeqList4() {
	SL sl;
	SeqListInit(&sl);
	SeqListPushBack(&sl, 1);
	SeqListPushBack(&sl, 2);
	SeqListPushBack(&sl, 3);
	SeqListPushBack(&sl, 4);
	SeqListPushBack(&sl, 5);
	SeqListPrint(&sl); //列印

	SeqListPushFront(&sl, 1);
	SeqListPushFront(&sl, 2);
	SeqListPushFront(&sl, 3);
	SeqListPushFront(&sl, 4);
	SeqListPushFront(&sl, 5);
	SeqListPrint(&sl); //列印

	SeqListEarse(&sl, 2);
	SeqListPrint(&sl); //列印

	SeqListPopBack(&sl);
	SeqListPopBack(&sl);
	SeqListPopFront(&sl);
	SeqListPopFront(&sl);
	SeqListPrint(&sl); //列印


	SeqListDestory(&sl);
}

int main() {
	// TestSeqList1();
	// TestSeqList2();
	// TestSeqList3();
	// TestSeqList4();

	return 0;
}


參考資料

Microsoft. MSDN(Microsoft Developer Network)[EB/OL]. []. .

百度百科[EB/OL]. []. https://baike.baidu.com/.

位元科技. 資料結構v5[EB/OL]. 2021[21]. .

📌 作者:王亦優

📃 更新: 2021.10.16

? 勘誤: 無

📜 宣告: 由于作者水平有限,本文有錯誤和不準確之處在所難免,本人也很想知道這些錯誤,懇望讀者批評指正!

本章完,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/319727.html

標籤:其他

上一篇:【C語言學習記錄】初識C語言

下一篇:爭當后“浪”

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more