C++ 学习记录 1
提示
这是一篇简单的学习记录,没有过多复杂的内容。但是因为是随心记录的,不适合于初学者学习,建议有一定编程基础与计算机知识的人参考。

环境

  • Microsoft Visual Stduio 2022 Community
  • ISO C++ 20 标准
  • vcpkg 包管理器

目标

制作一个简易的下载器,通过调整请求头 Range 来实现分段下载。此程序考查了:

  • 输入、输出
  • 模块、支持库的调用
  • 文件的读写
  • if 判断语句
  • for while 循环语句
  • 字符串拼接
  • 命名空间的使用
  • 异常的处理

代码

#include <iostream>
#include <fstream>
#include <string>
#include <thread>
#include <cpr/cpr.h>

static void progress(int per);
static std::string strip(const std::string& str);
static bool download_file(std::string download_url);

// 程序入口点
int main() {
    std::string download_url;
    std::cout << "欢迎使用没什么用下载器,请输入下载链接:";

    // 不用 std:cin 因为不可以获取空格
    std::getline(std::cin, download_url);
    download_url = strip(download_url);
    std::cout << "你输入的是:" + download_url + "\n我要开始下载喽亲!" << std::endl;
    std::cin.sync();  // 清空缓存区 或者使用 std::cin 吃掉换行

    // 请求服务器下载
    try {
        // 判断是否为合法的 URL
        if (download_url.substr(0, 3) != "http") {
            std::cout << "这不是一个合法的请求。" << std::endl;
            std::abort();
        }
        download_file(download_url);
    }
    catch (const std::exception& e) {
        std::cout << "("  << e.what() << ")我死了,因为程序运行出现了错误。" << std::endl;
    }


    return 0;
}

// 绘制进度条
static void progress(int per) {
    std::cout << "\r进度条:|";
    for (int i = 0; i < per; i++) {
        std::cout << "*";
    }
    for (int i = 0; i < 100 - per; i++) {
        std::cout << " ";
    }
    std::cout << "|" << per << "%" << std::flush;
}

static std::string strip(const std::string& str) {
    size_t start = str.find_first_not_of(" \t\r\n"); // 找到第一个非空白字符的位置
    size_t end = str.find_last_not_of(" \t\r\n");   // 找到最后一个非空白字符的位置
    if (start == std::string::npos) // 如果全是空白字符
        return ""; // 返回空字符串
    return str.substr(start, end - start + 1); // 返回去除首尾空白字符后的子串
}

static bool download_file(std::string download_url) {

    // 取出文件名
    std::string filename = download_url.substr(download_url.rfind("/") + 1);

    // 打开一个文件
    std::ofstream file{ filename , std::ios::binary };

    if (!file.is_open()) {
        std::cout << "文件不能创建!取消下载!" << std::endl;
        return false;
    }

    // 输出文件名
    std::cout << "文件名为 " << filename << " " << std::endl;

    // 建立一个会话
    cpr::Session session;
    session.SetUrl(
        cpr::Url(download_url)
    );

    // 获取文件长度
    size_t file_length = session.GetDownloadFileLength();

    std::cout << "长度为 " << file_length << " B" << std::endl;

    // 分段下载文件
    size_t response_length = 1024;  // 每次请求大小
    size_t downloaded_length = 0;  // 已经下载的大小

    while (downloaded_length < file_length) {
        session.SetHeader(
            cpr::Header{
                {"Range", "bytes=" + std::to_string(downloaded_length) + "-" + std::to_string(downloaded_length + response_length - 1)}
            }
        );

        cpr::Response response = session.Get();


        // & 代表直接引用 response.header 中的元素,const 设置常量,禁止修改
        /*for (const auto& header : response.header) {
            std::cout << header.first << ":" << header.second << std::endl;
        }*/

        auto data = response.text.data();
        auto size = std::stoi(response.header["Content-Length"]);
        file.write(data, size);
        downloaded_length += size;
        progress(static_cast<double> (downloaded_length) / file_length * 100);
        
    }

    return true;
}

说明

  1. 爬虫使用的是 cpr 库,其中的 spr::Response 类中的 text 对象虽然直译为“文本”,但本质就是存在内存的中的一段数据,是 std::string 类型的。使用 response.text.data() 可以获取到对应的指针。通过 response.downloaded_bytes 或者 response.header["Content-Length"] 可以获取到获取的字节大小,从而实现获取原字节数据。
  2. for 循环有两种写法,第一种是for (int i=0;i<100;i++)对于可遍历的对象可以有for (const auto& item: items)。其中 const表示定义为常量,不让后续代码进行破坏。&表示的是直接引用items中的各个项,避免不必要的复制浪费内存。
  3. 关于数据类型转化,一般可用(变量类型) 变量 或 变量 (变量类型) 进行强制转化,转化过程会产生一个新的变量(即会有一个新的指针地址)。
    1. 在 《C++ Primer Plus 第8版 中文版》第 55 页:C++还引入了4个强制类型转换运算符,对数据类型的转换更为严格。例如static_cast<> 可将数值类型转化为另一种数据类型。(个人理解:用于数值间的转化。)
    2. 在代码中我使用了 static_cast<double> 对 size_t类型的downloaded_length进行强制转化,传送到接收int类型的函数中,会抛出 warning C4244: “参数”: 从“double”转换到“int”,可能丢失数据的警告。我们可以显式的告诉编译器我知道这个风险,progress((int)(static_cast<double> (downloaded_length) / file_length * 100)); 同样,你可以掩耳盗铃在前面加上:#pragma warning(disable: 4244)之后再#pragma warning(default: 4244)
  4. 另外对于初始化数据类型时,使用{}更加安全,准确来说在 C++11 以上的标准中才加入进来的,其不允许窄化变量:int a{1.1};会报错,因为这种转化会丢失浮点位数据。但是使用()=是允许的。只不过会抛出异常。更多内容查看 《C++ Primer Plus 第8版 中文版》第 54 页。
  5. 程序中引用的#include <thread>并未使用,因为只是在测试进度条的时候用来延迟的,忘记删除了,延迟方法std::this_thread::sleep_for(std::chrono::milliseconds(100));
  6. 输入时使用std::getline(std:cin, 变量)之后还需要使用std::cin.sync();或者std::cin.get();来吃掉换行符,因为 std::getline(std:cin, 变量)不会读取换行符,会将其留在缓存区中。注: std::cin.sync()是清空缓存区。

不足

  1. 程序没有判断是否支持断点续传
  2. 对异常处理不够精确
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇