主頁 > 後端開發 > 從單片機到ARM Linux驅動——Linux驅動入門篇

從單片機到ARM Linux驅動——Linux驅動入門篇

2020-10-25 13:06:49 後端開發

大一到大二這段時間里學習過單片機的相關知識,對單片機有一定的認識和了解,如果要深究其原理可能還差了一些火候,知道如何撰寫程式來點量一個LED燈,改一改官方提供的例程來實作一些功能做一些小東西,對IIC、SPI底層的通信協議有一定的了解,但是學著學著逐漸覺得單片機我也就只能改改代碼了(當然有的代碼也不一定能改出來),對于我這種以后不想從事單片機開發想搬磚的碼農來說已經差不多了(僅僅是個人觀點),
在單片機開發中我們常常用到的是裸機,并沒有用到作業系統(或者接觸過ucos/rtos這種實時作業系統),但是嵌入式Linux開發就必須得在Linux系統中進行操作,我們需要熟悉Linux作業系統,知道Linux的常用命令、檔案系統、Linux網路、多執行緒/多行程,同時要會用vi編輯器、gcc編譯器、shell腳本和一些簡單的makefile的撰寫,在這些的基礎之上進行Linux驅動開發的學習就會如步青云,

往期推薦:
史上最全的Linux常用命令匯總(超全面!超詳細!)收藏這一篇就夠了!
STM32通過PWM產生頻率為20HZ占空比為50%方波,并通過單片機測量頻率并顯示在這里插入圖片描述

嵌入式Linux作業系統具有:開放原始碼、所需容量小(最小的安裝大約需要2MB)、不需著作權費用、成熟與穩定(經歷這些年的發展與使用)、良好的支持等特點,因此被廣泛應用于移動電話、個人數碼等產品中,嵌入式Linux開發主要包括:底層驅動、作業系統內核、應用開發三大類,需要掌握系統移植(Uboot、Linux Kernel的移植和裁剪、根檔案系統的構建)、Linux驅動及內核開發(字符設備驅動、塊設備驅動、網路設備驅動)應用開發由于博主能力有限所了解的也不多,

文章目錄

    • 字符設備驅動簡介
    • 字符設備驅動開發步驟
      • 驅動模塊的加載和卸載
      • 字符設備注冊與注銷
      • 實作設備的具體操作函式
      • 添加LICENSE和作者資訊
    • Linux設備號
      • 設備號的組成
      • 設備號的分配

字符設備驅動簡介

字符設備是Linux驅動中最基本的一類設備驅動,字符設備就是一個位元組,按照位元組進行讀寫操作設備,讀寫資料是分先后順序的,比如我們常見的點燈、按鍵、IIC、SPI、LCD等都是字符設備,這些設備的驅動就叫做字符設備驅動,
在Linux中開發一般只能是用戶態,也就是用戶只能撰寫應用程式,但是要作用于內核,那么就需要了解Linux中應用程式是如何呼叫內核中的驅動程式的,Linux 應用程式對驅動程式的呼叫如下圖所示:
在這里插入圖片描述
在Linux 中一切皆為檔案,驅動加載成功以后會在“/dev”目錄下生成一個相應的檔案,應用程式通過對這個名為“/dev/xxx” (xxx 是具體的驅動檔案名字)的檔案進行相應的操作即可實作對硬體的操作,比如現在有個叫做/dev/led 的驅動檔案,此檔案是 led 燈的驅動檔案,應用程式使用 open 函式來打開檔案/dev/led,使用完成以后使用 close 函式關閉/dev/led 這個檔案, open和 close 就是打開和關閉 led 驅動的函式,如果要點亮或關閉 led,那么就使用 write 函式來操作,也就是向此驅動寫入資料,這個資料就是要關倍訓是要打開 led 的控制引數,如果要獲取led 燈的狀態,就用 read 函式從驅動中讀取相應的狀態,
應用程式運行在用戶空間,而 Linux 驅動屬于內核的一部分,因此驅動運行于內核空間,當我們在用戶空間想要實作對內核的操作,比如使用 open 函式打開/dev/led 這個驅動,因為用戶空間不能直接對內核進行操作,因此必須使用一個叫做“系統呼叫”的方法來實作從用戶空間陷入到內核空間,這樣才能實作對底層驅動的操作, open、 close、 write 和 read 等這些函式是有 C 庫提供的,在 Linux 系統中,系統呼叫作為 C 庫的一部分,當我們呼叫 open 函式的時候流程如圖所示:
在這里插入圖片描述
應用程式使用到的函式在具體的驅動中都有與之對應的函式,比如應用程式中呼叫了 open 這個函式,那么在驅動程式中也得有一個名為 open 的函式,每一個系統呼叫,在驅動中都有與之對應的一個驅動函式,在 Linux 內核檔案 include/linux/fs.h 中有個叫做 file_operations 的結構體,此結構體就是 Linux 內核驅動操作函式集合

struct file_operations {
	struct module *owner;//owner 擁有該結構體的模塊的指標,一般設定為 THIS_MODULE
	loff_t (*llseek) (struct file *, loff_t, int);//llseek 函式用于修改檔案當前的讀寫位置
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t*);//read 函式用于讀取設備檔案
	ssize_t (*write) (struct file *, const char __user *, size_t,loff_t *);//write 函式用于向設備檔案寫入(發送)資料
	ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
	ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
	int (*iterate) (struct file *, struct dir_context *);
	unsigned int (*poll) (struct file *, struct poll_table_struct*);//poll 是個輪詢函式,用于查詢設備是否可以進行非阻塞的讀寫
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);//unlocked_ioctl 函式提供對于設備的控制功能,與應用程式中的 ioctl 函式對應,
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);//compat_ioctl 函式與 unlocked_ioctl 函式功能一樣,區別在于在 64 位系統上,32 位的應用程式呼叫將會使用此函式,在 32 位的系統上運行 32 位的應用程式呼叫的是unlocked_ioctl,
	int (*mmap) (struct file *, struct vm_area_struct *);//mmap 函式用于將將設備的記憶體映射到行程空間中(也就是用戶空間),一般幀緩沖設備會使用此函式,比如 LCD 驅動的顯存,將幀緩沖(LCD 顯存)映射到用戶空間中以后應用程式就可以直接操作顯存了,這樣就不用在用戶空間和內核空間之間來回復制,
	int (*mremap)(struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);//open 函式用于打開設備檔案,
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);//release 函式用于釋放(關閉)設備檔案,與應用程式中的 close 函式對應,
	int (*fsync) (struct file *, loff_t, loff_t, int datasync);//fasync 函式用于重繪待處理的資料,用于將緩沖區中的資料重繪到磁盤中,
	int (*aio_fsync) (struct kiocb *, int datasync);//aio_fsync 函式與 fasync 函式的功能類似,只是 aio_fsync 是異步重繪待處理的資料
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t,loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long,
	unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *,
	loff_t *, size_t, unsigned int);
	ssize_t (*splice_read)(struct file *, loff_t *, struct
	pipe_inode_info *, size_t, unsigned int);
	int (*setlease)(struct file *, long, struct file_lock **, void **);
	long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len);
	void (*show_fdinfo)(struct seq_file *m, struct file *f);
	#ifndef CONFIG_MMU
		unsigned (*mmap_capabilities)(struct file *);
	#endif
};

字符設備驅動開發步驟

在學習裸機或者 STM32 的時候關于驅動的開發就是初始化相應的外設暫存器,在 Linux 驅動開發中肯定也是要初始化相應的外設暫存器,這是毫無疑問的,只是在 Linux 驅動開發中我們需要按照其規定的框架來撰寫驅動,所以說學 Linux 驅動開發重點是學習其驅動框架

驅動模塊的加載和卸載

Linux 驅動有兩種運行方式第一種就是將驅動編譯進 Linux 內核中,這樣當 Linux 內核啟動的時候就會自動運行驅動程式第二種就是將驅動編譯成模塊(Linux 下模塊擴展名為.ko),在Linux 內核啟動以后使用“insmod”命令加載驅動模塊,在除錯驅動的時候一般都選擇將其編譯為模塊,這樣我們修改驅動以后只需要編譯一下驅動代碼即可,不需要編譯整個 Linux 代碼,而且在除錯的時候只需要加載或者卸載驅動模塊即可,不需要重啟整個系統,

模塊有加載和卸載兩種操作,我們在撰寫驅動的時候需要注冊這兩種操作函式,模塊的加載和卸載注冊函式如下:

module_init(xxx_init); //注冊模塊加載函式
module_exit(xxx_exit); //注冊模塊卸載函式

module_init 函式用來向 Linux 內核注冊一個模塊加載函式,引數 xxx_init 就是需要注冊的具體函式,當使用“insmod”命令加載驅動的時候, xxx_init 這個函式就會被呼叫, module_exit()函式用來向 Linux 內核注冊一個模塊卸載函式,引數 xxx_exit 就是需要注冊的具體函式,當使用“rmmod”命令卸載具體驅動的時候 xxx_exit 函式就會被呼叫,字符設備驅動模塊加載和卸載模板如下所示:

 /* 驅動入口函式 */
static int __init xxx_init(void)
{
	/* 入口函式具體內容 */
	return 0;
}
/* 驅動出口函式 */
static void __exit xxx_exit(void)
{
	 /* 出口函式具體內容 */
 }

	/* 將上面兩個函式指定為驅動的入口和出口函式 */
	module_init(xxx_init);
	module_exit(xxx_exit);
  • 第 2 行,定義了個名為 xxx_init 的驅動入口函式,并且使用了“__init”來修飾,
  • 第 9 行,定義了個名為 xxx_exit 的驅動出口函式,并且使用了“__exit”來修飾,
  • 第 15 行,呼叫函式 module_init 來宣告 xxx_init 為驅動入口函式,當加載驅動的時候 xxx_init函式就會被呼叫,
  • 第16行,呼叫函式module_exit來宣告xxx_exit為驅動出口函式,當卸載驅動的時候xxx_exit函式就會被呼叫,

驅動編譯完成以后擴展名為.ko,有兩種命令可以加載驅動模塊: insmodmodprobe,insmod是最簡單的模塊加載命令,此命令用于加載指定的.ko 模塊,比如加載 drv.ko 這個驅動模塊,命令如下:

	insmod drv.ko

insmod 命令不能解決模塊的依賴關系,比如 drv.ko 依賴 first.ko 這個模塊,就必須先使用insmod 命令加載 first.ko 這個模塊,然后再加載 drv.ko 這個模塊,但是 modprobe 就不會存在這個問題, modprobe 會分析模塊的依賴關系,然后會將所有的依賴模塊都加載到內核中,因此modprobe 命令相比 insmod 要智能一些, modprobe 命令主要智能在提供了模塊的依賴性分析、錯誤檢查、錯誤報告等功能,推薦使用 modprobe 命令來加載驅動, modprobe 命令默認會去/lib/modules/目錄中查找模塊,比如本書使用的 Linux kernel 的版本號為 4.1.15,因此 modprobe 命令默認到/lib/modules/4.1.15 這個目錄中查找相應的驅動模塊,一般自己制作的根檔案系統中是不會有這個目錄的,所以需要自己手動創建,驅動模塊的卸載使用命令“rmmod”即可,比如要卸載 drv.ko,使用如下命令即可:

	rmmod drv.ko

也可以使用“modprobe -r”命令卸載驅動,比如要卸載 drv.ko,命令如下:

	modprobe -r drv.ko

使用 modprobe 命令可以卸載掉驅動模塊所依賴的其他模塊,前提是這些依賴模塊已經沒有被其他模塊所使用,否則就不能使用 modprobe 來卸載驅動模塊,所以對于模塊的卸載,還是推薦使用 rmmod 命令,

字符設備注冊與注銷

對于字符設備驅動而言,當驅動模塊加載成功以后需要注冊字符設備,同樣,卸載驅動模塊的時候也需要注銷掉字符設備,字符設備的注冊和注銷函式原型如下所示:

static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
static inline void unregister_chrdev(unsigned int major, const char *name)
  • register_chrdev 函式用于注冊字符設備,此函式一共有三個引數,這三個引數的含義如下:
  • major: 主設備號, Linux 下每個設備都有一個設備號,設備號分為主設備號和次設備號兩部分,關于設備號后面會詳細講解,
  • name:設備名字,指向一串字串,
  • fops: 結構體 file_operations 型別指標,指向設備的操作函式集合變數,
  • unregister_chrdev 函式用戶注銷字符設備,此函式有兩個引數,這兩個引數含義如下:
  • major: 要注銷的設備對應的主設備號,
  • name: 要注銷的設備對應的設備名,

一般字符設備的注冊在驅動模塊的入口函式 xxx_init 中進行,字符設備的注銷在驅動模塊的出口函式 xxx_exit 中進行,在下面代碼中字符設備的注冊和注銷,內容如下所示:

static struct file_operations test_fops;

/* 驅動入口函式 */
static int __init xxx_init(void)
{
	/* 入口函式具體內容 */
	int retvalue = 0;

	/* 注冊字符設備驅動 */
	retvalue = register_chrdev(200, "chrtest", &test_fops);
	if(retvalue < 0){
		/* 字符設備注冊失敗,自行處理 */
	}
	return 0;
}

/* 驅動出口函式 */
static void __exit xxx_exit(void)
{
	/* 注銷字符設備驅動 */
	unregister_chrdev(200, "chrtest");
}

/* 將上面兩個函式指定為驅動的入口和出口函式 */
module_init(xxx_init);
module_exit(xxx_exit);
  • 以上代碼中,一開始定義了一個 file_operations 結構體變數 test_fops, test_fops 就是設備的操作函式集合,只是此時我們還沒有初始化 test_fops 中的 open、 release 等這些成員變數,所以這個操作函式集合還是空的,
  • 第十行,呼叫函式 register_chrdev 注冊字符設備,主設備號為 200,設備名字為“chrtest”,設備操作函式集合就是第 1 行定義的 test_fops,要注意的一點就是,選擇沒有被使用的主設備號,輸入命令cat /proc/devices可以查看當前已經被使用掉的設備號,
  • 第二十一行,呼叫函式 unregister_chrdev 注銷主設備號為 200 的這個設備,

實作設備的具體操作函式

file_operations 結構體就是設備的具體操作函式,在示例代碼 40.2.2.1 中我們定義了file_operations結構體型別的變數test_fops,但是還沒對其進行初始化,也就是初始化其中的open、release、 read 和 write 等具體的設備操作函式,本節小節我們就完成變數 test_fops 的初始化,設定好針對 chrtest 設備的操作函式,在初始化 test_fops 之前我們要分析一下需求,也就是要對chrtest 這個設備進行哪些操作,只有確定了需求以后才知道我們應該實作哪些操作函式,假設對 chrtest 這個設備有如下兩個要求:
1、能夠對 chrtest 進行打開和關閉操作
設備打開和關閉是最基本的要求,幾乎所有的設備都得提供打開和關閉的功能,因此我們需要實作 file_operations 中的 open 和 release 這兩個函式,
2、對 chrtest 進行讀寫操作
假設 chrtest 這個設備控制著一段緩沖區(記憶體),應用程式需要通過 read 和 write 這兩個函式對 chrtest 的緩沖區進行讀寫操作,所以需要實作 file_operations 中的 read 和 write 這兩個函式,需求很清晰了,修改驅動示例代碼在其中加入 test_fops 這個結構體變數的初始化操作,完成以后的內容如下所示:

/* 打開設備 */
static int chrtest_open(struct inode *inode, struct file *filp)
{
	/* 用戶實作具體功能 */
	return 0;
}
/* 從設備讀取 */
static ssize_t chrtest_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt)
{
	/* 用戶實作具體功能 */
	return 0;
}

/* 向設備寫資料 */
static ssize_t chrtest_write(struct file *filp,
const char __user *buf,
size_t cnt, loff_t *offt)
{
	/* 用戶實作具體功能 */
	return 0;
}
/* 關閉/釋放設備 */
static int chrtest_release(struct inode *inode, struct file *filp)
{
	/* 用戶實作具體功能 */
	return 0;
}

static struct file_operations test_fops = {
	.owner = THIS_MODULE,
	.open = chrtest_open,
	.read = chrtest_read,
	.write = chrtest_write,
	.release = chrtest_release,
};

/* 驅動入口函式 */
static int __init xxx_init(void)
{
	/* 入口函式具體內容 */
	int retvalue = 0;
	
	/* 注冊字符設備驅動 */
	retvalue = register_chrdev(200, "chrtest", &test_fops);
	if(retvalue < 0){
		/* 字符設備注冊失敗,自行處理 */
	}
	return 0;
}

/* 驅動出口函式 */
static void __exit xxx_exit(void)
{
	/* 注銷字符設備驅動 */
	unregister_chrdev(200, "chrtest");
}

/* 將上面兩個函式指定為驅動的入口和出口函式 */
module_init(xxx_init);
module_exit(xxx_exit);
  • 在上面代碼中,我們一開始撰寫了四個函式:chrtest_openchrtest_readchrtest_writechrtest_release,這四個函式就是 chrtest 設備的 open、 read、 write 和 release 操作函式,第 29行~35 行初始化 test_fops 的 open、read、 write 和 release 這四個成員變數,

添加LICENSE和作者資訊

在驅動撰寫最后,我們需要在驅動中加入LICENSE資訊和作者資訊,其中LICENSE是必須添加的,否則的話編譯時會報錯,作者資訊可以添加也可以不添加, LICENSE 和作者資訊的添加使用如下兩個函式:

	MODULE_LICENSE() //添加模塊 LICENSE 資訊
	MODULE_AUTHOR() //添加模塊作者資訊

給示例代碼加入 LICENSE 和作者資訊,完成以后的內容如下:

/* 打開設備 */
static int chrtest_open(struct inode *inode, struct file *filp)
{
	/* 用戶實作具體功能 */
	return 0;
}
......

/* 將上面兩個函式指定為驅動的入口和出口函式 */
module_init(xxx_init);
module_exit(xxx_exit);

MODULE_LICENSE("GPL");//LICENSE 采用 GPL 協議,
MODULE_AUTHOR("wly");//添加作者名字

當添加完作者和LICENSE和作者資訊后,字符設備驅動的完整流程就基本上結束了,并且也提供了一個完整的Linux驅動的模板,以后字符設備驅動開發就可以修改這個模板,

Linux設備號

Linux的設備管理是和檔案系統緊密結合的,各種設備都以檔案的形式存放在/dev目錄下,稱為設備檔案,應用程式可以打開、關閉和讀寫這些設備檔案,完成對設備的操作,就像操作普通的資料檔案一樣,為了管理這些設備,系統為設備編了號,這個號就被稱為Linux設備號!

設備號的組成

設備號由主設備號和次設備號兩部分組成,主設備號表示某一個具體的驅動,次設備號表示使用這個驅動的各個設備, Linux 提供了一個名為 dev_t 的資料型別表示設備號, dev_t 定義在檔案include/linux/types.h 里面,定義如下:

typedef __u32 __kernel_dev_t;
......
typedef __kernel_dev_t dev_t;

可以看出 dev_t 是__u32 型別的,而__u32 定義在檔案 include/uapi/asm-generic/int-ll64.h 里面,定義如下:

typedef unsigned int __u32;

dev_t 其實就是 unsigned int 型別,是一個 32 位的資料型別,這 32 位的資料構成了主設備號和次設備號兩部分,其中高 12 位為主設備號,第 20 位為次設備號,因此 Linux系統中主設備號范圍為0~4095,所以大家在選擇主設備號的時候一定不要超過這個范圍,在檔案 include/linux/kdev_t.h 中提供了幾個關于設備號的操作函式(本質是宏),如下所示:

#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
  • 第 1 行,宏 MINORBITS 表示次設備號位數,一共是 20 位,
  • 第 2 行,宏 MINORMASK 表示次設備號掩碼,
  • 第 3 行,宏 MAJOR 用于從 dev_t 中獲取主設備號,將 dev_t 右移 20 位即可,
  • 第 4 行,宏 MINOR 用于從 dev_t 中獲取此設備號,取 dev_t 的低 20 位的值即可,
  • 第 5 行,宏 MKDEV 用于將給定的主設備號和次設備號的值組合成 dev_t 型別的設備號,

設備號的分配

1、靜態分配設備號

注冊字符設備的時候需要給設備指定一個設備號,這個設備號可以是驅動開發者靜態的指定一個設備號,比如選擇 200 這個主設備號,有一些常用的設備號已經被 Linux 內核開發者給分配掉了,具體分配的內容可以查看檔案 Documentation/devices.txt,并不是說內核開發者已經分配掉的主設備號我們就不能用了,具體能不能用還得看我們的硬體平臺運行程序中有沒有使用這個主設備號,使用cat /proc/devices命令即可查看當前系統中所有已經使用了的設備號,

2、動態分配設備號

靜態分配設備號需要我們檢查當前系統中所有被使用了的設備號,然后挑選一個沒有使用的,而且靜態分配設備號很容易帶來沖突問題, Linux 社區推薦使用動態分配設備號,在注冊字符設備之前先申請一個設備號,系統會自動給你一個沒有被使用的設備號,這樣就避免了沖突,卸載驅動的時候釋放掉這個設備號即可,設備號的申請函式如下:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
  • dev:保存申請到的設備號,
  • baseminor: 次設備號起始地址, alloc_chrdev_region 可以申請一段連續的多個設備號,這些設備號的主設備號一樣,但是次設備號不同,次設備號以 baseminor 為起始地址地址開始遞增,一般 baseminor 為 0,也就是說次設備號從 0 開始,
  • count: 要申請的設備號數量,
  • name:設備名字,

注銷字符設備之后要釋放掉設備號,設備號釋放函式如下:

void unregister_chrdev_region(dev_t from, unsigned count)
  • from:要釋放的設備號,
  • count: 表示從 from 開始,要釋放的設備號數量,

不積小流無以成江河,不積跬步無以至千里,而我想要成為萬里羊,就必須堅持學習來獲取更多知識,用知識來改變命運,用博客見證成長,用行動證明我在努力,
如果我的博客對你有幫助、如果你喜歡我的博客內容,記得“點贊” “評論” “收藏”一鍵三連哦!聽說點贊的人運氣不會太差,每一天都會元氣滿滿呦!如果實在要白嫖的話,那祝你開心每一天,歡迎常來我博客看看,
在這里插入圖片描述

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

標籤:java

上一篇:1024程式員的節日~

下一篇:2020-10-24

標籤雲
其他(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)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more