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()
{
}
表示 parse 是 expression 类的成员函数。
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;
这里的 iterator 是 std::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.cpp、expression.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)
左边的 a 是 big_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>>
属于典型友元函数场景。
二十一、friend 和 public 的区别
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。
这就是今天最重要的语法点。

浙公网安备 33010602011771号