C++利用libcurl庫(kù)實(shí)現(xiàn)多線(xiàn)程文件下載
可以平時(shí)在linux上當(dāng)作自己的一個(gè)小小的下載器
linux環(huán)境準(zhǔn)備
1.在項(xiàng)目目錄下新建:
- build 目錄: 用來(lái)編譯
- src目錄:用來(lái)編寫(xiě)源碼(本例比較簡(jiǎn)單,就沒(méi)建了)
2.在build目錄下新建CMakeLists.txt,寫(xiě)入以下內(nèi)容
cmake_minimum_required(VERSION 3.3) project(ThreadDownLoad) set(CMAKE_CXX_STANDARD 14) # 這里要寫(xiě)../main.cpp,因?yàn)樾枰跇?gòu)建目錄下make,可執(zhí)行文件要填相對(duì)構(gòu)建目錄的路徑 add_executable(ThreadDownLoad ../main.cpp)
3.Cmake生成Makefile等文件
Cmake . -B ./build
4.完成main.cpp,進(jìn)行make
注意可執(zhí)行文件設(shè)置,因?yàn)樾枰跇?gòu)建目錄下make,可執(zhí)行文件要填相對(duì)構(gòu)建目錄的路徑
多線(xiàn)程文件下載
實(shí)現(xiàn)功能:
網(wǎng)絡(luò)文件下載:libcurl庫(kù),設(shè)置文件寫(xiě)入函數(shù),以及下載進(jìn)度回調(diào)函數(shù)
多文件下載:先獲取服務(wù)器文件大小,將文件分段平均分配給子線(xiàn)程下載
慎用mmap,雖然它能較少磁盤(pán)IO,但是它只能進(jìn)行整個(gè)文件的映射,可能會(huì)造成內(nèi)存不足
斷點(diǎn)續(xù)傳:注冊(cè)信號(hào)(如中斷信號(hào)),信號(hào)發(fā)生時(shí),將子線(xiàn)程的下載range存入文件
文件下載方式
mmap內(nèi)存映射
- 先向服務(wù)器發(fā)送請(qǐng)求,獲取文件大小fileLen,創(chuàng)建文件,將其指針移動(dòng)到fileLen,寫(xiě)入數(shù)據(jù)1
- 類(lèi)似迅雷下載預(yù)先分配大小),將其mmap映射到內(nèi)存,然后再發(fā)請(qǐng)求進(jìn)行下載
fwrite直接寫(xiě)文件
這里選擇第二種
mmap實(shí)現(xiàn)內(nèi)存映射原理:
- 調(diào)用mmap函數(shù)時(shí),系統(tǒng)會(huì)為這個(gè)文件在進(jìn)程的地址空間中分配一塊虛擬內(nèi)存區(qū)域,這個(gè)區(qū)域的大小通常是文件大小的整數(shù)倍。
- 系統(tǒng)會(huì)將文件的內(nèi)容讀入內(nèi)存中,并將這個(gè)虛擬內(nèi)存區(qū)域與文件建立映射關(guān)系,使得進(jìn)程可以通過(guò)對(duì)這個(gè)虛擬內(nèi)存區(qū)域的訪(fǎng)問(wèn)來(lái)訪(fǎng)問(wèn)文件的內(nèi)容。
- 進(jìn)程對(duì)這個(gè)虛擬內(nèi)存區(qū)域的訪(fǎng)問(wèn)會(huì)被轉(zhuǎn)換為對(duì)文件的讀寫(xiě)操作,這些操作會(huì)被操作系統(tǒng)自動(dòng)同步到磁盤(pán)上,從而保證文件內(nèi)容的一致性。
- 當(dāng)進(jìn)程不再需要這個(gè)虛擬內(nèi)存區(qū)域時(shí),可以調(diào)用munmap函數(shù)來(lái)釋放這個(gè)區(qū)域,這個(gè)區(qū)域的內(nèi)容也會(huì)被自動(dòng)同步到磁盤(pán)上。
mmap實(shí)現(xiàn)進(jìn)程貢獻(xiàn)原理:(mmap不是在進(jìn)程內(nèi)分配虛擬內(nèi)存嗎,怎么實(shí)現(xiàn)進(jìn)程的共享內(nèi)存呢?)
- 使用共享文件:進(jìn)程A和進(jìn)程B都打開(kāi)同一個(gè)文件,并使用mmap將這個(gè)文件映射到自己的虛擬內(nèi)存中。這樣,進(jìn)程A和進(jìn)程B就可以通過(guò)對(duì)這個(gè)虛擬內(nèi)存區(qū)域的訪(fǎng)問(wèn)來(lái)實(shí)現(xiàn)共享內(nèi)存。當(dāng)一個(gè)進(jìn)程修改了這個(gè)虛擬內(nèi)存區(qū)域的內(nèi)容時(shí),操作系統(tǒng)會(huì)自動(dòng)同步到磁盤(pán)上,從而保證數(shù)據(jù)的一致性。
- 使用匿名映射:進(jìn)程A和進(jìn)程B都使用mmap函數(shù)創(chuàng)建一個(gè)匿名映射區(qū)域,并將這個(gè)區(qū)域映射到自己的虛擬內(nèi)存中。這樣,進(jìn)程A和進(jìn)程B就可以通過(guò)對(duì)這個(gè)虛擬內(nèi)存區(qū)域的訪(fǎng)問(wèn)來(lái)實(shí)現(xiàn)共享內(nèi)存。當(dāng)一個(gè)進(jìn)程修改了這個(gè)虛擬內(nèi)存區(qū)域的內(nèi)容時(shí),操作系統(tǒng)會(huì)自動(dòng)同步到物理內(nèi)存中,從而保證數(shù)據(jù)的一致性。
加鎖控制:
- 無(wú)論是使用共享文件還是匿名映射,進(jìn)程間共享內(nèi)存的原理都是一樣的,即通過(guò)將同一個(gè)物理內(nèi)存區(qū)域映射到多個(gè)進(jìn)程的虛擬內(nèi)存中來(lái)實(shí)現(xiàn)共享。 =》 核心原理
- 由于多個(gè)進(jìn)程可以同時(shí)訪(fǎng)問(wèn)這個(gè)物理內(nèi)存區(qū)域,因此需要使用同步機(jī)制來(lái)保證數(shù)據(jù)的一致性。常用的同步機(jī)制包括信號(hào)量、互斥鎖、讀寫(xiě)鎖等。
多線(xiàn)程下載
首先,開(kāi)啟10個(gè)(根據(jù)文件大小和機(jī)器性能確定)線(xiàn)程,然后為每個(gè)子線(xiàn)程創(chuàng)建一個(gè)fileInfo實(shí)例,構(gòu)成一個(gè)fileInfo指針數(shù)組傳給每個(gè)線(xiàn)程(方便子線(xiàn)程打印下載進(jìn)度)
struct fileInfo {
const char *url; // 服務(wù)器下載路徑
char *fileptr; // 共享內(nèi)存指針
int offset; // start
int end; // end
pthread_t thid;
double download; // 當(dāng)前線(xiàn)程下載量
double totalDownload; // 所有線(xiàn)程總下載量
FILE *recordfile; // 文件句柄
};
開(kāi)啟線(xiàn)程下載:
int download(const char *url, const char *filename) {
// 1. 先向服務(wù)器發(fā)送請(qǐng)求,獲取文件大小,創(chuàng)建本地文件(迅雷下載預(yù)先分配大?。?,然后再發(fā)一次請(qǐng)求進(jìn)行下載
// 文件下載兩種思路:fwrite直接寫(xiě)文件;預(yù)先創(chuàng)建文件通過(guò)mmap內(nèi)存映射;這里選第二種
long fileLength = getDownloadFileLength(url);
printf("downloadFileLength: %ld\n", fileLength);
// write
int fd = open(filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); //
if (fd == -1) {
return -1;
}
// 移動(dòng)文件指針,使得預(yù)創(chuàng)建的文件和服務(wù)器的文件一樣大(在最后寫(xiě)一個(gè)1)
if (-1 == lseek(fd, fileLength-1, SEEK_SET)) {
perror("lseek");
close(fd);
return -1;
}
if (1 != write(fd, "", 1)) {
perror("write");
close(fd);
return -1;
}
// 2. 將文件映射到內(nèi)存
char *fileptr = (char *)mmap(NULL, fileLength, PROT_READ| PROT_WRITE, MAP_SHARED, fd, 0);
if (fileptr == MAP_FAILED) {
perror("mmap");
close(fd);
return -1;
}
FILE *fp = fopen("a.txt", "r");
// thread arg
int i = 0;
long partSize = fileLength / THREAD_NUM;
struct fileInfo *info[THREAD_NUM+1] = {NULL};
// 3.創(chuàng)建多個(gè)線(xiàn)程,每個(gè)線(xiàn)程一個(gè)文件下載器
for (i = 0;i <= THREAD_NUM;i ++) {
info[i] = (struct fileInfo*)malloc(sizeof(struct fileInfo));
memset(info[i], 0, sizeof(struct fileInfo));
// 多個(gè)線(xiàn)程將文件分塊下載
info[i]->offset = i * partSize;
if (i < THREAD_NUM) {
info[i]->end = (i+1) * partSize - 1;
} else {
info[i]->end = fileLength - 1;
}
info[i]->fileptr = fileptr; // 內(nèi)存映射(多線(xiàn)程共享內(nèi)存)
info[i]->url = url;
info[i]->download = 0;
info[i]->recordfile = fp;
}
pInfoTable = info;
//pthread_t thid[THREAD_NUM+1] = {0};
for (i = 0;i <= THREAD_NUM;i ++) {
pthread_create(&(info[i]->thid), NULL, worker, info[i]); // 將要傳給worker的文件信息封裝成一個(gè)結(jié)構(gòu)體
usleep(1);
}
// 等到子線(xiàn)程都退出,子線(xiàn)程才退出
for (i = 0;i <= THREAD_NUM;i ++) {
pthread_join(info[i]->thid, NULL);
}
// 釋放資源
for (i = 0;i <= THREAD_NUM;i ++) {
free(info[i]);
}
if (fp)
fclose(fp);
munmap(fileptr, fileLength);
close(fd);
return 0;
}
子線(xiàn)程利用curl庫(kù)下載
子線(xiàn)程實(shí)現(xiàn)邏輯: 文件寫(xiě)入回調(diào)與進(jìn)度回調(diào)
void *worker(void *arg) {
struct fileInfo *info = (struct fileInfo*)arg;
char range[64] = {0};
// mutex_lock
if (info->recordfile) {
// 用于斷點(diǎn)續(xù)傳,從文件中讀取range
fscanf(info->recordfile, "%d-%d", &info->offset, &info->end);
}
// mutex_unlock
if (info->offset > info->end) return NULL;
snprintf(range, 64, "%d-%d", info->offset, info->end);
CURL *curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, info->url); // url
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunc); // 文件下載函數(shù),libcurl在寫(xiě)入時(shí)執(zhí)行
curl_easy_setopt(curl, CURLOPT_WRITEDATA, info); // 寫(xiě)入回調(diào)傳參
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); // progress =》 沒(méi)有下載進(jìn)度為0
curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, progressFunc); // 回調(diào)函數(shù):libcurl發(fā)起請(qǐng)求時(shí)調(diào)用
curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, info); // 進(jìn)度回調(diào)函數(shù)傳參
curl_easy_setopt(curl, CURLOPT_RANGE, range); // 控制從服務(wù)器文件的下載范圍
// http range
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
printf("res %d\n", res);
}
curl_easy_cleanup(curl);
return NULL;
}
文件寫(xiě)入:將服務(wù)器傳回的n塊文件寫(xiě)入共享內(nèi)存,這里不需要加鎖,每個(gè)進(jìn)程寫(xiě)入的共享內(nèi)存地址不一樣
// 寫(xiě)入回調(diào):ptr 參數(shù)是接收到的數(shù)據(jù)的指針;size 參數(shù)是每個(gè)數(shù)據(jù)塊的大??;memb 參數(shù)是數(shù)據(jù)塊的數(shù)量
size_t writeFunc(void *ptr, size_t size, size_t memb, void *userdata) {
struct fileInfo *info = (struct fileInfo *)userdata;
printf("writeFunc\n");
memcpy(info->fileptr + info->offset, ptr, size * memb);
info->offset += size * memb;
return size * memb;
}
進(jìn)度回調(diào):子進(jìn)程打印文件的當(dāng)前下載進(jìn)度
// 進(jìn)度回調(diào)
int progressFunc(void *userdata, double totalDownload, double nowDownload, double totalUpload, double nowUpload) {
printf("progressFunc\n");
int percent = 0;
static int print = 1;
struct fileInfo *info = (struct fileInfo*)userdata;
info->download = nowDownload;
info->totalDownload = totalDownload;
// save
if (totalDownload > 0) {
int i = 0;
double allDownload = 0;
double total = 0;
for (i = 0;i <= THREAD_NUM;i ++) {
allDownload += pInfoTable[i]->download;
total += pInfoTable[i]->totalDownload;
}
percent = (int)(allDownload / total * 100);
}
if (percent == print) {
printf("threadid: %ld, percent: %d%%\n", info->thid, percent);
print += 1;
}
return 0;
}
斷點(diǎn)續(xù)傳
注冊(cè)一個(gè)信號(hào)(如中斷信號(hào)),當(dāng)信號(hào)發(fā)生時(shí),在文件中記錄每個(gè)線(xiàn)程的下一次下載range,下一次子線(xiàn)程下載時(shí),下載文件中的range部分即可
// 當(dāng)收到中斷信號(hào)后,記錄每個(gè)線(xiàn)程的下載進(jìn)度
void signal_handler(int signum) {
printf("signum: %d\n", signum);
int fd = open("a.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
if (fd == -1) {
exit(1);
}
int i = 0;
for (i = 0;i <= THREAD_NUM;i ++) {
char range[64] = {0};
snprintf(range, 64, "%d-%d\r\n", pInfoTable[i]->offset, pInfoTable[i]->end);
write(fd, range, strlen(range));
}
close(fd);
exit(1);
}
源碼
// gcc -o multi_download multi_download.c -lcurl
#include <stdio.h>
#include <unistd.h>
#include <curl/curl.h>
#include <fcntl.h>
// 需要在linux上使用
#include <sys/mman.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <signal.h>
// fork();
struct fileInfo {
const char *url; // 服務(wù)器下載路徑
char *fileptr; // 共享內(nèi)存指針
int offset; // start
int end; // end
pthread_t thid;
double download; // 當(dāng)前線(xiàn)程下載量
double totalDownload; // 所有線(xiàn)程總下載量
FILE *recordfile; // 文件句柄
};
#define THREAD_NUM 10
struct fileInfo **pInfoTable; // 二級(jí)指針=>指針數(shù)組
double downloadFileLength = 0;
// 寫(xiě)入回調(diào):ptr 參數(shù)是接收到的數(shù)據(jù)的指針;size 參數(shù)是每個(gè)數(shù)據(jù)塊的大小;memb 參數(shù)是數(shù)據(jù)塊的數(shù)量
size_t writeFunc(void *ptr, size_t size, size_t memb, void *userdata) {
struct fileInfo *info = (struct fileInfo *)userdata;
printf("writeFunc\n");
memcpy(info->fileptr + info->offset, ptr, size * memb);
info->offset += size * memb;
return size * memb;
}
// 進(jìn)度回調(diào)
int progressFunc(void *userdata, double totalDownload, double nowDownload, double totalUpload, double nowUpload) {
printf("progressFunc\n");
int percent = 0;
static int print = 1;
struct fileInfo *info = (struct fileInfo*)userdata;
info->download = nowDownload;
info->totalDownload = totalDownload;
// save
if (totalDownload > 0) {
int i = 0;
double allDownload = 0;
double total = 0;
for (i = 0;i <= THREAD_NUM;i ++) {
allDownload += pInfoTable[i]->download;
total += pInfoTable[i]->totalDownload;
}
percent = (int)(allDownload / total * 100);
}
if (percent == print) {
printf("threadid: %ld, percent: %d%%\n", info->thid, percent);
print += 1;
}
return 0;
}
//
double getDownloadFileLength(const char *url) {
CURL *curl = curl_easy_init();
printf("url: %s\n", url);
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36");
curl_easy_setopt(curl, CURLOPT_HEADER, 1);
curl_easy_setopt(curl, CURLOPT_NOBODY, 1);
CURLcode res = curl_easy_perform(curl);
if (res == CURLE_OK) {
printf("downloadFileLength success\n");
curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &downloadFileLength);
} else {
printf("downloadFileLength error\n");
downloadFileLength = -1;
}
curl_easy_cleanup(curl);
return downloadFileLength;
}
int recordnum = 0;
void *worker(void *arg) {
struct fileInfo *info = (struct fileInfo*)arg;
char range[64] = {0};
// mutex_lock
if (info->recordfile) {
// 用于斷點(diǎn)續(xù)傳,從文件中讀取range
fscanf(info->recordfile, "%d-%d", &info->offset, &info->end);
}
// mutex_unlock
if (info->offset > info->end) return NULL;
snprintf(range, 64, "%d-%d", info->offset, info->end);
CURL *curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, info->url); // url
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunc); // 文件下載函數(shù),libcurl在寫(xiě)入時(shí)執(zhí)行
curl_easy_setopt(curl, CURLOPT_WRITEDATA, info); // 寫(xiě)入回調(diào)傳參
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); // progress =》 沒(méi)有下載進(jìn)度為0
curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, progressFunc); // 回調(diào)函數(shù):libcurl發(fā)起請(qǐng)求時(shí)調(diào)用
curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, info); // 進(jìn)度回調(diào)函數(shù)傳參
curl_easy_setopt(curl, CURLOPT_RANGE, range); // 控制從服務(wù)器文件的下載范圍
// http range
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
printf("res %d\n", res);
}
curl_easy_cleanup(curl);
return NULL;
}
// https://releases.ubuntu.com/22.04/ubuntu-22.04.2-live-server-amd64.iso.zsync
// ubuntu.zsync.backup
int download(const char *url, const char *filename) {
// 1. 先向服務(wù)器發(fā)送請(qǐng)求,獲取文件大小,創(chuàng)建本地文件(迅雷下載預(yù)先分配大?。?,然后再發(fā)一次請(qǐng)求進(jìn)行下載
// 文件下載兩種思路:fwrite直接寫(xiě)文件;預(yù)先創(chuàng)建文件通過(guò)mmap內(nèi)存映射;這里選第二種
long fileLength = getDownloadFileLength(url);
printf("downloadFileLength: %ld\n", fileLength);
// write
int fd = open(filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); //
if (fd == -1) {
return -1;
}
// 移動(dòng)文件指針,使得預(yù)創(chuàng)建的文件和服務(wù)器的文件一樣大(在最后寫(xiě)一個(gè)1)
if (-1 == lseek(fd, fileLength-1, SEEK_SET)) {
perror("lseek");
close(fd);
return -1;
}
if (1 != write(fd, "", 1)) {
perror("write");
close(fd);
return -1;
}
// 2. 將文件映射到內(nèi)存
char *fileptr = (char *)mmap(NULL, fileLength, PROT_READ| PROT_WRITE, MAP_SHARED, fd, 0);
if (fileptr == MAP_FAILED) {
perror("mmap");
close(fd);
return -1;
}
FILE *fp = fopen("a.txt", "r");
// thread arg
int i = 0;
long partSize = fileLength / THREAD_NUM;
struct fileInfo *info[THREAD_NUM+1] = {NULL};
// 3.創(chuàng)建多個(gè)線(xiàn)程,每個(gè)線(xiàn)程一個(gè)文件下載器
for (i = 0;i <= THREAD_NUM;i ++) {
info[i] = (struct fileInfo*)malloc(sizeof(struct fileInfo));
memset(info[i], 0, sizeof(struct fileInfo));
// 多個(gè)線(xiàn)程將文件分塊下載
info[i]->offset = i * partSize;
if (i < THREAD_NUM) {
info[i]->end = (i+1) * partSize - 1;
} else {
info[i]->end = fileLength - 1;
}
info[i]->fileptr = fileptr; // 內(nèi)存映射(多線(xiàn)程共享內(nèi)存)
info[i]->url = url;
info[i]->download = 0;
info[i]->recordfile = fp;
}
pInfoTable = info;
//pthread_t thid[THREAD_NUM+1] = {0};
for (i = 0;i <= THREAD_NUM;i ++) {
pthread_create(&(info[i]->thid), NULL, worker, info[i]); // 將要傳給worker的文件信息封裝成一個(gè)結(jié)構(gòu)體
usleep(1);
}
// 等到子線(xiàn)程都退出,子線(xiàn)程才退出
for (i = 0;i <= THREAD_NUM;i ++) {
pthread_join(info[i]->thid, NULL);
}
// 釋放資源
for (i = 0;i <= THREAD_NUM;i ++) {
free(info[i]);
}
if (fp)
fclose(fp);
munmap(fileptr, fileLength);
close(fd);
return 0;
}
// 當(dāng)收到中斷信號(hào)后,記錄每個(gè)線(xiàn)程的下載進(jìn)度
void signal_handler(int signum) {
printf("signum: %d\n", signum);
int fd = open("a.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
if (fd == -1) {
exit(1);
}
int i = 0;
for (i = 0;i <= THREAD_NUM;i ++) {
char range[64] = {0};
snprintf(range, 64, "%d-%d\r\n", pInfoTable[i]->offset, pInfoTable[i]->end);
write(fd, range, strlen(range));
}
close(fd);
exit(1);
}
//
#if 1
// 2G: ./ThreadDownLoad https://releases.ubuntu.com/22.04/ubuntu-22.04.3-live-server-amd64.iso ubuntu.zenger
// 11M: ./ThreadDownLoad https://releases.ubuntu.com/22.04/ubuntu-22.04.3-desktop-amd64.iso.zsync ubuntu.zenger
int main(int argc, const char *argv[]) {
if (argc != 3) {
printf("arg error\n");
return -1;
}
if (SIG_ERR == signal(SIGINT, signal_handler)) {
perror("signal");
return -1;
}
return download(argv[1], argv[2]);
}
#endif
以上就是C++利用libcurl庫(kù)實(shí)現(xiàn)多線(xiàn)程文件下載的詳細(xì)內(nèi)容,更多關(guān)于C++ libcurl文件下載的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C語(yǔ)言 常量,變量及數(shù)據(jù)詳細(xì)介紹
這篇文章主要介紹了C語(yǔ)言 常量,變量及數(shù)據(jù)詳解的相關(guān)資料,需要的朋友可以參考下2016-10-10
C語(yǔ)言 實(shí)現(xiàn)輸入任意多個(gè)整數(shù)
這篇文章主要介紹了C語(yǔ)言 實(shí)現(xiàn)輸入任意多個(gè)整數(shù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12
C語(yǔ)言實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)和雙向鏈表操作
這篇文章主要介紹了C語(yǔ)言實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)雙向鏈表操作,需要的朋友可以參考下2017-03-03
虛函數(shù)表-C++多態(tài)的實(shí)現(xiàn)原理解析
這篇文章主要介紹了虛函數(shù)表-C++多態(tài)的實(shí)現(xiàn)原理,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02
C++中volatile關(guān)鍵字及常見(jiàn)的誤解總結(jié)
這篇文章主要給大家介紹了關(guān)于C++中volatile關(guān)鍵字及常見(jiàn)的誤解的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-05-05
詳細(xì)分析C++ 數(shù)據(jù)封裝和數(shù)據(jù)抽象
這篇文章主要介紹了C++ 數(shù)據(jù)封裝和數(shù)據(jù)抽象的的相關(guān)資料,文中代碼非常詳細(xì),幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下2020-06-06
vc6.0中c語(yǔ)言控制臺(tái)程序中的定時(shí)技術(shù)(定時(shí)器)
這篇文章主要介紹了vc6.0中c語(yǔ)言控制臺(tái)程序中的定時(shí)技術(shù)(定時(shí)器),需要的朋友可以參考下2014-04-04
C語(yǔ)言中計(jì)算函數(shù)執(zhí)行時(shí)間的三種方式
本文主要介紹了C語(yǔ)言中計(jì)算函數(shù)執(zhí)行時(shí)間的三種方式,主要包括clock(),timeb和time,具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09

