c++多文件编程

今日学习内容关键词:C++ 分文件编程、hpp 与 cpp、类外成员函数定义、作用域解析运算符 ::、构造函数、对象成员变量、static 文件作用域、const 成员函数、运算符重载、流输入输出重载、friend 友元函数、引用传参、头文件包含规则

main.cpp

#include <bits/stdc++.h>
#include "expression.hpp"
int main()
{
	expression e;
	e.get_input();
	std::cout<<e.get_result()<<'\n';
	return 0;
}

big_int.hpp

#pragma once
#include <string>
#include <iostream>

class big_int
{
public:
	big_int();
	big_int(const std::string &data);
	big_int operator+(const big_int &b) const;
	friend std::ostream &operator<<(std::ostream &output,const big_int &a);
	friend std::istream &operator>>(std::istream &input,big_int &a);
private:
	std::string data;
};

big_int.cpp

#include "big_int.hpp"
#include <algorithm>

static std::string clean(const std::string &s)
{
	int p=0;
	while(p+1<(int)s.size()&&s[p]=='0') ++p;
	return s.substr(p);
}

big_int::big_int()
{
	data="0";
}

big_int::big_int(const std::string &data)
{
	this->data=clean(data);
}

big_int big_int::operator+(const big_int &b) const
{
	std::string x=data;
	std::string y=b.data;
	std::reverse(x.begin(),x.end());
	std::reverse(y.begin(),y.end());
	std::string res;
	int carry=0;
	int n=std::max(x.size(),y.size());
	for(int i=0;i<n;++i)
	{
		int sum=carry;
		if(i<(int)x.size()) sum+=x[i]-'0';
		if(i<(int)y.size()) sum+=y[i]-'0';
		res.push_back(char(sum%10+'0'));
		carry=sum/10;
	}
	if(carry) res.push_back(char(carry+'0'));
	std::reverse(res.begin(),res.end());
	return big_int(res);
}

std::ostream &operator<<(std::ostream &output,const big_int &a)
{
	output<<a.data;
	return output;
}

std::istream &operator>>(std::istream &input,big_int &a)
{
	input>>a.data;
	a.data=clean(a.data);
	return input;
}

expression.hpp

#pragma once
#include "big_int.hpp"//为什么调用hpp而非cpp?
#include <string>//可以不包含因为上面的hpp已经包含了
class expression
{
    public:
        void get_input();
        big_int get_result() const;
    private:
        void parse();
        std::string data;
        big_int result;//为什么big_int::big_int result; 会报错

};

expression.cpp

#include "expression.hpp"
#include<iostream>
#include<vector>
#include<cctype>
#include<algorithm>
void expression::get_input()//为什么这里要声明 expression::?
{
    std::getline(std::cin,this->data);
    parse();
}
void expression::parse()
{
    std::string num1{},num2{};
    bool has_got_symbol{false};
    const std::vector<char> symbols{'+'};
    for(const auto &i:data)
    {
        if(std::find(symbols.begin(),symbols.end(),i)!=symbols.end())
        {
            has_got_symbol=true;
            continue;
        }
        if(std::isdigit(i))
        {
            if(!has_got_symbol)
                num1+=i;
            else num2+=i;
        }
    }
    result=big_int{num1}+big_int{num2};
}
big_int expression::get_result() const 
{
    return result;
}

编译命令:

g++ main.cpp expression.cpp big_int.cpp -o main

二、.hpp.cpp 分别放什么

这次代码被拆成了:

main.cpp
big_int.hpp
big_int.cpp
expression.hpp
expression.cpp

一般来说:

.hpp / .h:放声明
.cpp:放实现

.hpp 的作用是告诉编译器:

这个类叫什么?
它有哪些成员函数?
这些函数的参数和返回值是什么?
它有哪些成员变量?

.cpp 的作用是写函数的具体实现。

例如 big_int.hpp 里有:

class big_int
{
public:
	big_int();
	big_int(const std::string &data);
	big_int operator+(const big_int &b) const;
	friend std::ostream &operator<<(std::ostream &output,const big_int &a);
	friend std::istream &operator>>(std::istream &input,big_int &a);
private:
	std::string data;
};

这只是声明了 big_int 这个类的结构。

而真正的构造函数、operator+operator<<operator>> 的函数体,都写在 big_int.cpp 里面。

这种写法的好处是把“接口”和“实现”分开。

使用这个类的人只需要看头文件,就能知道这个类能怎么用;具体内部实现放在 .cpp 里。


三、为什么 include 的是 .hpp,不是 .cpp

expression.hpp 里写:

#include "big_int.hpp"

原因是 expression 类里面有:

big_int result;

编译器看到这行时,必须知道 big_int 是什么类型。

big_int 的类声明在 big_int.hpp 里,所以要 include 这个头文件。

不应该写:

#include "big_int.cpp"

因为 .cpp 里面是函数实现。如果多个文件都 include 同一个 .cpp,这些函数实现会被展开多份,链接时容易出现重复定义。

正确理解是:

#include 负责让当前文件看到声明。
编译命令负责把所有 cpp 的实现编译进去。

所以应该这样编译:

g++ main.cpp expression.cpp big_int.cpp -o main

如果漏掉 expression.cpp,编译器可能能看到声明,但链接器找不到函数实现。


四、#pragma once 是什么

头文件开头写:

#pragma once

作用是防止同一个头文件被重复包含。

例如:

main.cpp include expression.hpp
expression.hpp include big_int.hpp

如果别的文件也 include 了 big_int.hpp,编译器可能在同一个编译单元里多次看到同一个类声明。

#pragma once 的作用就是告诉编译器:

这个头文件在同一个编译单元中只包含一次。

传统写法也可以是:

#ifndef BIG_INT_HPP
#define BIG_INT_HPP

// 头文件内容

#endif

目前直接用 #pragma once 更简洁。


五、为什么 expression::get_input() 要写 expression::

代码里有:

void expression::get_input()
{
    std::getline(std::cin,this->data);
    parse();
}

这里的:

expression::

叫作用域解析运算符。

它表示:

get_input 是 expression 类里面的成员函数。

因为这个函数是在类外定义的。

expression.hpp 里只是声明了:

void get_input();

但函数体写在 expression.cpp 里。

所以在类外写函数体时,必须告诉编译器这个函数属于哪个类:

void expression::get_input()

如果写成:

void get_input()
{
}

那它就是普通全局函数,不属于 expression 类。

规则:

类里面直接写函数体:不用 类名::
类外面写函数体:必须写 类名::

例如:

void expression::parse()
{
}

表示 parseexpression 类的成员函数。

big_int expression::get_result() const
{
	return result;
}

表示 get_result 也是 expression 类的成员函数。


六、构造函数是什么

big_int.hpp 里有:

big_int();
big_int(const std::string &data);

这两个是构造函数。

构造函数的特点:

1. 函数名和类名完全一样
2. 没有返回值类型
3. 创建对象时自动调用

例如:

big_int a;

会调用:

big_int::big_int()

也就是默认构造函数。

例如:

big_int b{"123"};

会调用:

big_int::big_int(const std::string &data)

构造函数的作用是让对象一创建出来就处于一个合法状态。


七、为什么 big_int::big_int result; 会报错

expression.hpp 里正确写法是:

big_int result;

不能写:

big_int::big_int result;

原因是:

big_int

才是类型名。

而:

big_int::big_int

是构造函数,不是类型。

构造函数不能拿来声明变量。

类名才可以拿来声明变量:

big_int result;

这句话的意思是:

定义一个 big_int 类型的成员变量,名字叫 result。

而:

big_int::big_int result;

看起来像是在说:

big_int 这个类里面有一个叫 big_int 的内部类型。

但实际上 big_int::big_int 是构造函数,不是内部类型,所以报错。

合法的类似写法是:

std::vector<int>::iterator it;

这里的 iteratorstd::vector<int> 里面定义的类型。

但构造函数不是类型。


八、static 修饰全局函数是什么意思

big_int.cpp 里有:

static std::string clean(const std::string &s)
{
	int p=0;
	while(p+1<(int)s.size()&&s[p]=='0') ++p;
	return s.substr(p);
}

这里的 static 修饰的是一个全局函数。

它的意思是:

这个函数只在当前 big_int.cpp 文件内部可见。

也就是说,其他文件比如 main.cppexpression.cpp 不能直接调用这个 clean 函数。

如果不写 static

std::string clean(const std::string &s)

那么它就是一个普通全局函数,可能和其他 .cpp 文件里的同名函数发生冲突。

所以如果一个函数只是当前 .cpp 内部使用,可以写成:

static 返回值类型 函数名(...)

或者用匿名命名空间:

namespace
{
	std::string clean(const std::string &s)
	{
	}
}

先记住这个结论:

static 修饰全局函数:限制函数作用域,只允许当前 cpp 文件内部使用。

九、const 成员函数是什么

expression.hpp 里有:

big_int get_result() const;

对应实现:

big_int expression::get_result() const 
{
    return result;
}

函数后面的:

const

表示:

这个成员函数不会修改当前对象。

也就是说,在 get_result() 里面不能修改成员变量。

例如:

big_int expression::get_result() const 
{
    data="123"; // 错误
    return result;
}

get_result() 只是返回结果,不应该改变对象状态,所以适合加 const

const 的好处:

1. 语义更清楚:这个函数只是读取,不修改对象
2. const 对象也可以调用这个函数
3. 编译器能帮忙检查,防止误修改成员变量

十、运算符重载是什么

C++ 允许给自定义类型重新定义一些运算符的含义。

普通整数可以写:

int a=1,b=2;
int c=a+b;

但自定义类型默认不能直接这样写:

big_int a{"123"};
big_int b{"456"};
big_int c=a+b;

除非自己定义:

big_int operator+(const big_int &b) const;

这就是运算符重载。

在代码里:

big_int operator+(const big_int &b) const;

表示重载 + 运算符。

对应实现:

big_int big_int::operator+(const big_int &b) const
{
	// ...
}

所以:

big_int c=a+b;

可以理解成:

big_int c=a.operator+(b);

成员函数版本的二元运算符重载里:

左操作数是当前对象
右操作数是参数

例如:

a+b

对应:

a 是当前对象
b 是参数

十一、为什么 operator+ 后面也有 const

声明是:

big_int operator+(const big_int &b) const;

这里有两个 const

第一个:

const big_int &b

表示参数 b 不会被修改。

第二个:

末尾的 const

表示当前对象不会被修改。

也就是说:

a+b

不应该改变 a,也不应该改变 b,而是返回一个新的结果。

所以这种写法比较合理:

big_int operator+(const big_int &b) const;

拆开看:

big_int big_int::operator+(const big_int &b) const

含义是:

第一个 big_int:返回值类型
big_int::operator+:big_int 类里的 + 运算符重载函数
const big_int &b:右操作数,传引用且不允许修改
最后的 const:不修改左操作数

十二、为什么参数经常写成 const big_int &b

这里用的是:

const big_int &b

而不是:

big_int b

原因是避免拷贝。

如果写:

big_int operator+(big_int b) const;

调用时会复制一份右操作数。

而写:

big_int operator+(const big_int &b) const;

表示引用传参,不复制对象。

其中 const 又保证函数内部不能修改 b

常见写法:

const 类型名 &参数名

适合传入比较大的对象,比如:

const std::string &s
const std::vector<int> &v
const big_int &b

总结:

传值:会拷贝
引用:不拷贝
const 引用:不拷贝,也不允许修改

十三、流输出重载:为什么可以写 std::cout<<a

代码里有:

friend std::ostream &operator<<(std::ostream &output,const big_int &a);

实现是:

std::ostream &operator<<(std::ostream &output,const big_int &a)
{
	output<<a.data;
	return output;
}

这个函数重载了输出运算符:

<<

所以可以写:

big_int a{"123"};
std::cout<<a;

这句话本质上会被编译器理解成:

operator<<(std::cout,a);

这个 operator<< 有两个参数:

std::ostream &output
const big_int &a

第一个参数对应左边:

std::cout

第二个参数对应右边:

a

所以:

std::cout<<a;

本质是:

operator<<(std::cout,a);

这里的 output 不一定只能是 std::cout,也可以是其他输出流,比如文件输出流:

std::ofstream fout("out.txt");
fout<<a;

所以输出运算符重载一般写成:

std::ostream &operator<<(std::ostream &output,const 类型 &a)

十四、为什么 operator<< 要返回 std::ostream &

输出重载函数写的是:

std::ostream &operator<<(std::ostream &output,const big_int &a)
{
	output<<a.data;
	return output;
}

返回类型是:

std::ostream &

这样是为了支持连续输出:

std::cout<<a<<'\n';

这句话本质上是:

(std::cout<<a)<<'\n';

先执行:

std::cout<<a

调用我们自己写的:

operator<<(std::cout,a)

如果这个函数返回 std::cout 本身,后面才能继续接:

<<'\n'

所以最后要写:

return output;

并且要返回引用:

std::ostream &

因为流对象一般不能复制,也不应该复制。

标准形式:

std::ostream &operator<<(std::ostream &output,const 类型 &a)
{
	output<<...;
	return output;
}

十五、流输入重载:为什么可以写 std::cin>>a

代码里有:

friend std::istream &operator>>(std::istream &input,big_int &a);

实现是:

std::istream &operator>>(std::istream &input,big_int &a)
{
	input>>a.data;
	a.data=clean(a.data);
	return input;
}

这个函数重载了输入运算符:

>>

所以可以写:

big_int a;
std::cin>>a;

这句话本质上会被编译器理解成:

operator>>(std::cin,a);

第一个参数:

std::istream &input

对应左边的输入流:

std::cin

第二个参数:

big_int &a

对应右边要被读入的对象。

注意这里不能写成:

const big_int &a

因为输入操作要修改 a

所以:

std::istream &operator>>(std::istream &input,big_int &a)

里第二个参数必须是普通引用,而不是 const 引用。


十六、为什么输入输出运算符通常不用成员函数写

对于 operator+,可以写成成员函数:

big_int operator+(const big_int &b) const;

因为:

a+b

可以理解成:

a.operator+(b)

左边的 abig_int 对象,所以它可以调用 big_int 的成员函数。

但是输出:

std::cout<<a

左边是:

std::cout

它的类型是:

std::ostream

不是 big_int

如果把输出运算符写成 big_int 的成员函数,调用形式会更像:

a.operator<<(std::cout)

对应表达式更接近:

a<<std::cout

这不是我们想要的。

我们想要的是:

std::cout<<a

而左边的 std::cout 不属于我们,不能去修改标准库的 std::ostream 类。

所以 operator<< 通常写成普通函数:

std::ostream &operator<<(std::ostream &output,const big_int &a)

输入同理。

std::cin>>a

左边是 std::cin,类型是 std::istream,也不是 big_int

所以 operator>> 也通常写成普通函数:

std::istream &operator>>(std::istream &input,big_int &a)

十七、friend 是什么

big_int.hpp 里:

friend std::ostream &operator<<(std::ostream &output,const big_int &a);
friend std::istream &operator>>(std::istream &input,big_int &a);

这里用了 friend

friend 的准确含义是:

这个函数不是类的成员函数,但是它被允许访问这个类的 private 和 protected 成员。

例如 big_int 里有私有成员:

private:
	std::string data;

正常情况下,类外的普通函数不能访问:

a.data

因为 data 是 private。

但是如果在类里面声明:

friend std::ostream &operator<<(std::ostream &output,const big_int &a);

那么这个 operator<< 函数就获得了访问权限。

所以它虽然不是 big_int 的成员函数,但可以写:

output<<a.data;

如果没有 friend,这里的:

a.data

会报错。


十八、friend 不是成员函数

这一点很重要。

在类里面写:

friend std::ostream &operator<<(std::ostream &output,const big_int &a);

不是说 operator<< 变成了 big_int 的成员函数。

它仍然是普通函数。

friend 只是给它一个权限:

你不是我的成员函数,但我允许你访问我的 private。

所以它的实现不是:

std::ostream &big_int::operator<<(std::ostream &output,const big_int &a)
{
}

而是:

std::ostream &operator<<(std::ostream &output,const big_int &a)
{
}

注意没有:

big_int::

因为它不是 big_int 的成员函数。

这和成员函数版本的 operator+ 不一样。

operator+ 是成员函数,所以实现要写:

big_int big_int::operator+(const big_int &b) const

operator<< 是友元普通函数,所以实现写:

std::ostream &operator<<(std::ostream &output,const big_int &a)

十九、什么时候才需要用 friend

friend 不应该乱用。

它会让类外部的函数访问 private 成员,所以会削弱一部分封装。

但是有些场景下,friend 很合适。

1. 重载 operator<<operator>>

这是最常见的场景。

因为:

std::cout<<a;
std::cin>>a;

左操作数分别是:

std::cout
std::cin

它们不是自己的类对象。

所以输入输出运算符通常写成普通函数,而不是成员函数。

但普通函数又需要访问类的私有数据,所以可以写成友元函数:

friend std::ostream &operator<<(std::ostream &output,const big_int &a);
friend std::istream &operator>>(std::istream &input,big_int &a);

这是 friend 最典型的使用方式。

2. 某个非成员运算符需要访问 private

有些运算符不适合写成成员函数。

比如希望支持:

2+a

假设 a 是自定义类型。

如果写成员函数,一般只能自然处理:

a+2

因为成员函数要求左操作数是当前类对象。

但:

2+a

左边是 int,不是自己的类对象,所以不能通过 a.operator+ 的形式处理。

这时可以写一个普通函数:

number operator+(int lhs,const number &rhs);

如果这个普通函数需要访问 rhs 的 private 成员,就可以声明成 friend。

例如:

class number
{
private:
	int x;
public:
	number(int x):x(x){}
	friend number operator+(int lhs,const number &rhs);
};

number operator+(int lhs,const number &rhs)
{
	return number(lhs+rhs.x);
}

这里 operator+ 不是成员函数,但它需要访问 rhs.x,所以用 friend

3. 两个类关系非常紧密

如果两个类本身关系非常紧密,一个类确实需要访问另一个类的内部数据,可以用友元类。

例如:

class matrix;

class vec
{
	friend class matrix;
private:
	int data[10];
};

这表示:

matrix 类可以访问 vec 的 private 成员。

但这种权限给得比较大,应该谨慎使用。

4. 测试代码需要检查内部状态

在一些工程里,测试代码可能需要访问类的内部状态。

这时也可能把测试类或测试函数声明成 friend。

不过这属于工程测试场景,日常写小项目不一定常见。


二十、什么时候不该用 friend

如果普通 public 接口能解决,就不要急着用 friend。

例如,如果只是想让外部拿到字符串,可以提供:

std::string to_string() const;

然后输出函数可以写成:

std::ostream &operator<<(std::ostream &output,const big_int &a)
{
	output<<a.to_string();
	return output;
}

这样就不需要 friend。

但这样会把 to_string() 变成 public 接口。

如果不想额外暴露接口,只想让输出函数能访问内部数据,那么用 friend 是合理的。

判断是否适合用 friend,可以看这几个问题:

1. 这个函数是否必须写成非成员函数?
2. 它是否确实需要访问 private?
3. 如果通过 public 接口实现,会不会很别扭?
4. 这个函数和这个类的关系是否足够紧密?

如果答案比较明确,才考虑使用 friend。

对于本次代码:

operator<<
operator>>

属于典型友元函数场景。


二十一、friendpublic 的区别

public 是把成员开放给所有外部代码。

例如:

class big_int
{
public:
	std::string data;
};

这样任何地方都能写:

a.data="123";

这会破坏封装。

friend 是只给指定函数或指定类权限:

friend std::ostream &operator<<(std::ostream &output,const big_int &a);

这表示只有这个指定函数有特殊访问权限。

所以:

public:对所有外部代码开放
friend:只对指定函数或指定类开放

从封装角度看,friend 比直接把成员变量设成 public 更可控。


二十二、friend 和成员函数的区别

成员函数:

class big_int
{
public:
	big_int operator+(const big_int &b) const;
};

实现时写:

big_int big_int::operator+(const big_int &b) const
{
}

调用时可以理解成:

a.operator+(b)

它属于类。

友元函数:

friend std::ostream &operator<<(std::ostream &output,const big_int &a);

实现时写:

std::ostream &operator<<(std::ostream &output,const big_int &a)
{
}

调用时可以理解成:

operator<<(std::cout,a)

它不属于类,只是有访问 private 的权限。

总结:

成员函数:属于类,要写 类名::
友元函数:不属于类,不写 类名::,但可以访问 private

二十三、operator<<operator>>const 区别

输出函数:

std::ostream &operator<<(std::ostream &output,const big_int &a)

这里 a 是:

const big_int &a

因为输出只读取 a,不应该修改 a

输入函数:

std::istream &operator>>(std::istream &input,big_int &a)

这里 a 是:

big_int &a

没有 const

因为输入要把内容写入 a

可以这样记:

cout << a:读取 a,所以 const big_int &
cin >> a:修改 a,所以 big_int &

二十四、引用返回:为什么返回的是 std::ostream &std::istream &

输入输出运算符重载返回的是引用:

std::ostream &
std::istream &

原因有两个。

第一,流对象一般不能复制,也不应该复制。

第二,返回引用可以支持链式调用:

std::cout<<a<<b<<'\n';
std::cin>>a>>b;

如果不返回流对象引用,链式调用就断了。

标准写法:

std::ostream &operator<<(std::ostream &output,const 类型 &a)
{
	output<<...;
	return output;
}

std::istream &operator>>(std::istream &input,类型 &a)
{
	input>>...;
	return input;
}

二十五、operator+ 是成员函数,operator<< 是友元普通函数

本次代码里有两类运算符重载。

第一类是成员函数版本:

big_int operator+(const big_int &b) const;

实现:

big_int big_int::operator+(const big_int &b) const

它属于 big_int 类。

第二类是友元普通函数版本:

friend std::ostream &operator<<(std::ostream &output,const big_int &a);
friend std::istream &operator>>(std::istream &input,big_int &a);

实现:

std::ostream &operator<<(std::ostream &output,const big_int &a)
std::istream &operator>>(std::istream &input,big_int &a)

它们不属于 big_int 类,所以实现时不写:

big_int::

对比:

a+b
左边是 big_int,所以可以写成 big_int 成员函数。

cout<<a
左边是 cout,不是 big_int,所以通常写成普通函数。

cin>>a
左边是 cin,不是 big_int,所以通常写成普通函数。

二十六、头文件里的 #include <string> 能不能省略

expression.hpp 里有:

#include "big_int.hpp"//为什么调用hpp而非cpp?
#include <string>//可以不包含因为上面的hpp已经包含了

因为 big_int.hpp 里已经 include 了:

#include <string>

所以当前情况下,expression.hpp 里即使不写:

#include <string>

也可能能编译。

但这不是好习惯。

规则应该是:

当前文件用到了什么类型,就自己 include 对应头文件。

因为 expression.hpp 自己用了:

std::string data;

所以它自己就应该 include:

#include <string>

不要依赖别的头文件顺便包含了 <string>

否则以后如果 big_int.hpp 改了,不再 include <string>expression.hpp 就会突然编译失败。

所以这行建议保留。


二十七、std::getline(std::cin,this->data)std::cin >> data

代码里用的是:

std::getline(std::cin,this->data);

它会读取一整行。

如果输入:

123 + 456

包括中间空格也会被读进 data

如果用:

std::cin>>data;

那它只会读到空白字符前。

比如输入:

123 + 456

只会读到:

123

所以处理表达式字符串时,getline 更适合。

语法上:

std::getline(输入流,字符串变量);

例如:

std::getline(std::cin,data);

二十八、为什么 parse() 是 private

expression.hpp 里:

class expression
{
    public:
        void get_input();
        big_int get_result() const;
    private:
        void parse();
        std::string data;
        big_int result;
};

这里:

get_input()
get_result()

是 public。

外部可以调用:

expression e;
e.get_input();
std::cout<<e.get_result();

但:

parse()

是 private。

外部不能调用:

e.parse(); // 错

因为 parse() 是内部细节。

外部使用 expression 时,只需要知道:

可以输入
可以拿结果

不应该关心内部什么时候解析、怎么解析。

所以 parse() 放 private 合理。

这就是封装。

封装的基本思路是:

该给外部用的放 public
只给类内部用的放 private

二十九、这次代码里的核心语法点总结

1. 分文件

.hpp 放声明
.cpp 放实现
include 头文件
编译命令里带所有 cpp

2. 类外定义成员函数

void expression::get_input()

必须写类名和 ::,否则就是普通全局函数。

3. 构造函数

big_int();
big_int(const std::string &data);

构造函数和类同名,没有返回值,创建对象时自动调用。

4. static 修饰全局函数

static std::string clean(...)

表示这个函数只在当前 .cpp 文件可见。

5. const 成员函数

big_int get_result() const;

表示这个函数不会修改当前对象。

6. 运算符重载

big_int operator+(const big_int &b) const;

让自定义类型支持:

a+b

7. 流输出重载

std::ostream &operator<<(std::ostream &output,const big_int &a)

让自定义类型支持:

std::cout<<a

8. 流输入重载

std::istream &operator>>(std::istream &input,big_int &a)

让自定义类型支持:

std::cin>>a

9. friend

friend std::ostream &operator<<(std::ostream &output,const big_int &a);

让一个非成员函数可以访问类的 private 成员。

适合输入输出运算符重载这类场景。


三十、个人理解版总结

这次代码虽然表面上是一个大整数加法程序,但语法重点是 C++ 的分文件、类、运算符重载和友元函数。

今天主要学到:

1. 一个类可以拆成 hpp 和 cpp。
2. hpp 负责声明,cpp 负责实现。
3. include 的应该是 hpp,不应该 include cpp。
4. 类外定义成员函数时,要写 类名::函数名。
5. 构造函数不是类型,big_int 是类型,big_int::big_int 不是类型。
6. static 修饰全局函数时,可以把辅助函数限制在当前 cpp 内部。
7. const 成员函数表示不会修改当前对象。
8. operator+ 可以让自定义类型支持 a+b。
9. operator<< 和 operator>> 可以让自定义类型支持 cout<<a 和 cin>>a。
10. friend 不是成员函数,而是给普通函数访问 private 的权限。
11. friend 适合用于输入输出运算符重载,因为 cout 和 cin 在左边,不是自己的类对象。

最关键的区别是:

operator+ 是成员函数,要写 big_int::
operator<< 是友元普通函数,不写 big_int::
operator>> 是友元普通函数,不写 big_int::

friend 的核心作用不是“让函数变成类成员”,而是:

让一个不是成员的函数,也能访问这个类的 private。

这就是今天最重要的语法点。

posted @ 2026-06-16 16:46  dtcoke  阅读(1)  评论(0)    收藏  举报