【cpp】深蓝学院 c++课程 基于课后作业的学习复习汇总 其1 1-6节

写在前面

N年前上来深蓝学院cpp的课,老师是大牛但却有讲的特别细致,感觉对于入门的我有很大帮助。当时作为初学者有很多不理解的地方,现在工作了几年回头去看理解深刻了一些,将会在学习行业算法的时候,复习一下课程内容。

不会去罗列课程知识点,老师讲的太细致了,可以作为工作书遇到回来再查看。其实问CHATGPT也很方便了,但问之前也需要有一个知识框架。

同时网上书籍也很多,这里就计划基于每一章节的作业区复习整理相关的知识,即作业使用到了知识点才复习,同时与实际使用时尚未见过的章节和知识点会优先级排后。

知识点是我写代码过程中或修改时候时候想到要使用的,对于记忆不清的内容在最后做了查询的记录

第一二三章合并作业

  • 设计自动出加减法和判断的题目
  • 知识点:无特别

效果

在这里插入图片描述

qkf代码

#include <cstdlib>
#include <iostream>
#include <string>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <ctime>
#include <chrono> // 包含用于时间计算的头文件
using namespace std;
using namespace std::chrono; 

class Calculator
{
public:
    Calculator() = default;
    ~Calculator() = default;

    void run(const int &count)
    {
        int counter = 0;
        int count_t = count;
        duration<double> total_time{0}; 

        while (count_t--)
        {
            auto start_time = high_resolution_clock::now(); 
            counter += static_cast<int>(computing());
            auto end_time = high_resolution_clock::now();
            total_time += duration_cast<duration<double>>(end_time - start_time);
        }

        cout << "In total " << counter << " out of " << count << " are correct." << endl;
        cout << "Total time spent: " << total_time.count() << " seconds." << endl; 
    }

private:
    int stringToNumber(const string &input)
    {
        int res = 0;
        int size = input.length(); // no \0

        for (const auto &x : input)
        {
            res += (x - '0') * pow(10, size - 1);
            --size;
        }

        return res;
    }

    bool computing()
    {
        int a = rand() % 100 + 1;
        int b = rand() % 100 + 1;
        printf("Please enter the result of %d + %d: \n", a, b);
        string c = "";

        while (1)
        {
            cin >> c;
            bool isNumber = true;

            for (const auto &x : c)
            {
                if (x > '9' || x < '0')
                {
                    printf("Please enter an integer, not a character.");
                    isNumber = false;
                    break;
                }
            }

            if (isNumber)
            {
                break;
            }
        }

        int temp = stringToNumber(c);
        printf("The correct answer is %d and yours is %d.\n", a + b, temp);

        return (temp == a + b);
    }
};

int main()
{
    srand(static_cast<unsigned int>(time(nullptr)));
    Calculator caltest1;
    caltest1.run(10);

    cout << "Press 'q' to quit: ";
    while (cin.get() != 'q');

    return 0;
}

扩展记录

rand随机种子

 srand(static_cast<unsigned int>(time(nullptr)));

输入流及其回车控制问题

见链接挺好

char 和string区别终止符问题

QKF:char 和string的结尾 哪一个有用到 \0

GPT:在 C++ 中,字符串的结尾通常由空字符 \0(空字符,ASCII值为0)表示。这被称为字符串的null终止符。这个字符告诉程序字符串的结束位置。在处理字符串的函数中,很多都会使用这个 null 终止符来确定字符串的末尾。

对于字符数组(char 数组),如果你手动创建一个字符数组来存储字符串,你需要确保在字符串的末尾添加 null 终止符。例如:

cpp
char myString[10];
myString[0] = ‘H’;
myString[1] = ‘e’;
myString[2] = ‘l’;
myString[3] = ‘l’;
myString[4] = ‘o’;
myString[5] = ‘\0’; // 添加 null 终止符表示字符串的结束
在这个例子中,myString 数组的前5个元素包含了字符串 “Hello” 的字符,第6个元素是 null 终止符。

相反,对于 std::string 类型,C++ 标准库会自动处理字符串的 null 终止符。你只需要在字符串的初始化时赋值即可:

cpp
std::string myString = “Hello”; // C++ 标准库会自动在末尾添加 null 终止符
在 std::string 中,你无需手动添加 null 终止符,它会自动处理字符串的大小和 null 终止符的添加。

总体而言,C++ 标准库的 std::string 类型对于字符串的处理更加方便,避免了手动添加 null 终止符的烦琐工作,并提供了更多的字符串操作函数。如果你使用字符数组,要确保手动添加 null 终止符,以确保字符串处理函数正确识别字符串的结束。

第四五六合并作业章

  • 题目 设计一个大数加减法
  • 知识点:构造、运算符重载、友元、文件流、main函数入口参数传递、迭代器(反向)

QKF代码


#include <iostream>
#include <vector>
#include <string>
#include <fstream>
#include <algorithm>

class LongInteger
{
private:
    std::vector<int> digits_;
    bool isNegative_;         // 标记整数是否为负

public:
    LongInteger(const std::string &number = "0") : isNegative_(false)
    {
        bool isNonZero = false; // 标记是否已经遇到非零数字
        for (char digit : number)
        {
            if (digit == '+' || digit == '-')
            {
                isNegative_ = (digit == '-');
            }
            else if (digit != '0' || isNonZero)
            {
                isNonZero = true;
                digits_.push_back(digit - '0'); 
            }
        }
        if (digits_.empty()) // 应对全是0的情况
        {
            digits_.push_back(0);
        }
    }

    // 加法重载
    LongInteger operator+(const LongInteger &other) const
    {
        if (isNegative_ == other.isNegative_)
        { // 同号
            return addDigits(other);
        }
        else if (isGreaterAbs(other))
        { // 异号且当前数绝对值大
            return subtractDigits(other);
        }
        else
        { // 异号且另一个数绝对值大
            return other.subtractDigits(*this);
        }
    }

    // 重载输出
    friend std::ostream &operator<<(std::ostream &os, const LongInteger &num)
    {
        if (num.isNegative_ && !num.digits_.empty())
            os << '-';
        if (num.digits_.empty())
            os << '0';
        else
        {
            for (int digit : num.digits_)
            {
                os << digit;
            }
        }
        return os;
    }

private:
    // 加法
    LongInteger addDigits(const LongInteger &other) const
    {
        std::string sum;
        int carry = 0;
        auto it1 = digits_.rbegin(), it2 = other.digits_.rbegin();
        while (it1 != digits_.rend() || it2 != other.digits_.rend() || carry)//反向迭代器 ++为--
        {
            int val1 = it1 != digits_.rend() ? *it1++ : 0;
            int val2 = it2 != other.digits_.rend() ? *it2++ : 0;// 取最大长度不够补0  
            int total = val1 + val2 + carry;
            carry = total / 10;
            sum.push_back('0' + total % 10);// to_string 也可以
        }
        std::reverse(sum.begin(), sum.end());
        return LongInteger(isNegative_ && !sum.empty() ? "-" + sum : sum);// 排除 -0的情况, 同时结果还是用LongInteger表示
        //其实vector也可以,这里为了使用友元和重载os
    }

    // 减法,假设当前数字绝对值大于另一个数字
    LongInteger subtractDigits(const LongInteger &other) const
    {
        std::string difference;
        int borrow = 0;
        auto it1 = digits_.rbegin(), it2 = other.digits_.rbegin();
        while (it1 != digits_.rend() || it2 != other.digits_.rend())
        {
            int val1 = it1 != digits_.rend() ? *it1++ : 0;
            int val2 = it2 != other.digits_.rend() ? *it2++ + borrow : borrow;//把借位放在减数上
            if (val1 < val2)
            {
                val1 += 10;
                borrow = 1;
            }
            else
            {
                borrow = 0;
            }
            difference.push_back('0' + val1 - val2);
        }
        std::reverse(difference.begin(), difference.end());
        return LongInteger(isNegative_ && !difference.empty() ? "-" + difference : difference);
        // 减法正负号可以由两数绝对值大小大的数字来界定,亦可以使用大减小和小减大分类讨论,这里使用第一种方式
    }

    // 比较绝对值大小
    bool isGreaterAbs(const LongInteger &other) const
    {
        if (digits_.size() != other.digits_.size())// 这时候以及除前导零了可以直接用size来比较
        {
            return digits_.size() > other.digits_.size();
        }
        for (size_t i = 0; i < digits_.size(); ++i)
        {
            if (digits_[i] != other.digits_[i])
            {
                return digits_[i] > other.digits_[i];
            }
        }
        return false; 
    }
};

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << " <filename>" << std::endl;
        return 1;
    }
    std::ifstream file(argv[1]);
    std::string line1, line2;
    if (std::getline(file, line1) && std::getline(file, line2))
    {
        LongInteger num1(line1), num2(line2);
        std::cout << num1 + num2 << std::endl;
    }
    else
    {
        std::cerr << "Error reading file." << std::endl;
    }
    return 0;
}


效果

在这里插入图片描述

拓展记录

size_t

size_t 是专门用来表示对象大小和数组索引的类型,它的最大值应该足够大以容纳系统中可能存在的最大对象或数组

main 入口参数

第一个是参数数量 ,默认按顺序是有程序名 和 各种参数,各种参数在上图红框中体现,存储在 char *argv[] 中,就好像传入数组,给个地址头给一个参数size

错误输出

QKF:cout cerr 区别
缓冲区:

cout 对象通常是经过缓冲的,这意味着它的输出可能会先存储在内存缓冲区中,直到遇到换行符 \n 或显式地刷新缓冲区时才会被输出到终端。这种缓冲行为有助于提高输出效率,但也可能导致在程序崩溃或异常退出时丢失输出内容。
cerr 对象通常是不经过缓冲的,它的输出会立即被发送到终端,这样可以确保及时显示出错信息,即使在程序崩溃或异常退出时也能够看到相应的错误信息。
标准错误输出:

cerr 通常用于向标准错误流输出错误信息。与 cout 不同,cerr 的输出通常不会被重定向到文件或管道中,而是直接显示在终端上,这使得它非常适合用于输出与程序执行相关的重要错误信息,如运行时错误、异常情况等。
cout 用于标准输出流,通常用于输出一般性的信息、警告或调试信息。

反向迭代器

for (auto it = num.digits.rbegin(); it != num.digits.rend(); ++it)//反向迭代 ++实际为减法

列表初始化

提高了运行速度比起在函数体内赋值

 LongInteger(const std::string &number = "0") : isNegative_(false)

友元函数

使用了友元函数,因为使用重载的<<时候,在外部访问了类的private成员。而+号运算法没有

    LongInteger operator+(const LongInteger &other) const
    {
        if (isNegative_ == other.isNegative_)
        { // 同号
            return addDigits(other);
        }
        else if (isGreaterAbs(other))
        { // 异号且当前数绝对值大
            return subtractDigits(other);
        }
        else
        { // 异号且另一个数绝对值大
            return other.subtractDigits(*this);
        }
    }

    // 重载输出
    friend std::ostream &operator<<(std::ostream &os, const LongInteger &num)
    {
        if (num.isNegative_ && !num.digits_.empty())
            os << '-';
        if (num.digits_.empty())// 至少给个0,当处理过的数据为空
            os << '0';
        else
        {
            for (int digit : num.digits_)
            {
                os << digit;
            }
        }
        return os;
    }

QKF:为什么成员函数 +在被使用的时候不是 num1.+nums2 而是可以直接写成 num1+num2
GPT:在 C++ 中,当你使用 obj1 + obj2 形式时,编译器会将其转换为 obj1.operator+(obj2) 的形式,其中 operator+ 是一个成员函数。这种转换是因为 C++ 支持一种特殊的语法,称为运算符重载(operator overloading),允许你重载类中的某些运算符,使得它们能够操作类的对象。

因此,当你写 num1 + num2 时,实际上是在调用 num1 对象的 operator+ 成员函数,并将 num2 作为参数传递给该函数。这种语法使得代码更加清晰和易读,因为它反映了你想要执行的操作,而不需要显式地写成 `num1.operator+(num2)

额外挺好的解释

<<的重载和链式传递

这一块书写的时候我借鉴了资料,但上面 <<的重载我理解的不是很透彻,为什么一定要有形式参数传入且同时返回,返回我能理解是为了链式传递,但是为什么不能局部一个ostream,GPT的回答ostream是一个虚基类无法实例化,我可以理解,这个回答。 GPT推荐使用ostringstream 类型来声名局部变量

#include <sstream>

friend std::ostream& operator<<(const LongInteger& num) {
    std::ostringstream os;
    if (num.isNegative_ && !num.digits_.empty())
        os << '-';
    if (num.digits_.empty()) // 至少给个0,当处理过的数据为空
        os << '0';
    else {
        for (int digit : num.digits_) {
            os << digit;
        }
    }
    return os;
}

在看到输出运算符重载的时候 不能太理解为什么+可以定义为友元但是<<不可以,询问了gpt
QKF:为什么不能直接把<< 定义为成员函数 是因为返回值类型的原因嘛
GPT: << 定义为成员函数的确是一种常见的做法,但它可能会导致一些问题。在C++中,重载 << 运算符作为成员函数,需要将左操作数(被输出的对象)作为调用者,而右操作数(输出流)作为参数。然而,这种方式限制了左操作数的类型,因为成员函数必须属于该类型或者可以隐式地转换为该类型。

这样做可能会导致两个问题:

修改原始类: 如果你想要将 << 定义为成员函数,你需要修改原始类的定义,将它包含在你自己的类中。这样一来,如果你没有控制这个类的定义,就无法添加这样的成员函数。

左操作数的类型限制: 如果 << 被定义为成员函数,它只能用于该类的对象,并且只能接受右操作数作为参数。这样一来,如果你想要支持多种类型的左操作数(例如自定义类型和标准类型),你就需要在每个类型中定义对应的 << 成员函数,这会增加代码的冗余度。

因此,将 << 定义为非成员函数,而是作为友元函数或全局函数,通常更加灵活和通用。这种方式可以支持更多类型的左操作数,并且不需要修改原始类的定义。此外,非成员函数的形式还更符合面向对象编程的原则,因为输出操作通常不需要直接访问类的私有成员。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值