ps:终于来到c嘎嘎了,芜湖!!!
如何学:思考+画图
在最开始之前,老规矩,看一下c++的hello world输出。
ok,接下来会一点点带入c++。
c++的关键字差不多有60个左右,c++只不过是c的扩充,所以基本关键字学过c的大家都知道就不赘述了,至于新增加的,见到一个解释一个。
在实际应用场景下,一个项目都是好多人做的,最后把每个人的代码汇总到一起,这其中就避免不了起名的冲突,预防这点,c++之父本贾尼引入了一个新的定义“namespace”意味使用后接的内容(前提是包含这个文件),他的使用有个前提,需要所引用文件代码对防止冲突函数名等进行“打包”。
namespace jwp
{
int a = 1;
int add(int x , int y)
{
return x + y;
}
}
以上为打包过程,在主文件引用次文件后,需要对其“解开”才行。
有时候只需要“解开”单个函数或者仅一次使用,也可以以下操作。
std::cout << "hellow world" << std::endl;
using std::cout
第一行就是仅仅一次使用,第二个就是对该包装(std)内的cout多次使用不需要加std::。
至于using意思就是全解开,全使用的意思,比如刚刚的cout,意思就是对std中的cout全放开。
ps:真正使用肯定会有很多问题,按照常理思维想一下应该问题不大。
输出大家已经看到了,就是cout和<<,意思也不难,就是<<后的符号要“流进”cout中并且打印出来。相而,输入就是cin>>[a],意思就是数据“流入”[a]。
在上面的hello world中,endl意思就是换行,这个指令“流入”前面的字符,意思合起来就是这段字符要输出并且打印。
ps:c++和c一句话就是本是同根生,怎么方便怎么来,混起来不犯毛病。
直接来
void add(int a = 10, int b = 20, int c = 30)
{
printf("%d %d %d", a, b, c);
}
int main()
{
add(1);
return 0;
}
当传参时,引用的是传参值,无参时,用的是函数中的自带值。
注意:传参时不能跳着传,只能从左往右传(假设上面那个就是a=1,b=20,c=30);只能在声明给,不能声明和定义同时给。
(编程中的天狗,有伴了就不需要,没人了就被需要了,dddd)
然后上面那个叫全缺省,还有一种叫半缺省,意思就是参数a可以不给数值,只给b和c。
简单来说就是在同一作用于下允许函数重名,然后根据不同的参数类型去选择使用哪个函数;还有就是,他只会辨别参数类型,不会辨别返回值。
补充一下:除了 参数函数类型 还有参数个数,参数类型顺序。
而编译是怎么区分两个名字一样的函数的呢。这就展示出了c和c++的区别(本来就是同一种,真的不想去拉踩,就像手心手背一样)c++在编译链接时会在修饰函数时多加上刚才说的三种。
int add(int a, int b)
{
return a + b;
}
//_Zaddii(函数地址)
int add(char a, char b)
{
return a + b;
}
//_Zaddcc(函数地址)
相信聪明的大伙看出来了区别和规则,这,就是Linux的函数修饰。当然不同平台规则不一样。而c呢,就单纯的在调用时用函数名查找了,没有修饰的那种。
这里说完修饰就能解释为什么不会辨别返回值了。
就是取个别名,有个外号,写一段代码,简单说一下。
int i = 1;
int j = i;
int& k = i;
int& n = k;
int&就是“取别名”,他和直接等于的区别有两点,第一点等于的话地址会不同,&后地址相同;第二点,别名改变会影响本体,别名的别名改变也会影响本体,依次类推,但等于不会改变。
好,那他的实际应用场景是什么呢,最直接的就是形参和实参。
int add(int& x, int& y)
{
int tmp = x;
int x = y;
int y = tmp;
}
int main()
{
int i = 1;
int j = 2;
}
这样, x 和 y 就不是 i 和 j 的一份临时拷贝了,而是 i 和 j 的别名,这样,就很完美的解决了指针的冗杂使用。
(这里说的并不全面,函数引用以及传值,指针,引用的内存及时间后续更新)
{
这里说一个小知识点:
int a = 1;
double& b = 1;
这是运行不了的,问题并不是说int转double转不了,而是一个权限放大的问题,权限只能缩小或者平移,不能放大,而对应的这段代码在哪里放大了呢,新的知识就来了。
在看不到的地方,系统默认创建了一个临时变量double来接收 a 的值,这个临时变量时有const修饰的,然后再把这个临时变量的值给 b ,一切也就说的通了,至于为什么这么做,以后再说。
}
这个是用于替代宏的一个函数,一般用const和enum替代宏常量,inline替代宏函数。
因为宏确实有很多缺点,
第一不能调试,在编译的时候就替换掉了。
第二没有类型安全的检查。
第三在某些场景非常复杂。
比如以下这个ADD宏函数
#define ADD(int x, int y) return x + y;
#define ADD(x, y) x + y;
#define ADD(x, y) (x + y);
#define ADD(x, y) (x) + (y);
#define ADD(x, y) ((x) + (y));
对于不熟练的这里的可能就写出这样的东西来,恰恰就说明的宏函数的复杂。
再比如说,假设某个函数有十行,被调用了10000次,这里的代码在替换的时候,咦,可执行程序太大了。
所以他们也仅仅只适用于一小段代码。
这里要注意的点是,内联函数并不会像一般函数一样,有类似于压栈,展开之类的底层操作,只会单纯的找到对应链接,即在使用的时候,声明和定义不可以分离。直接在声明里敲出来就好。
因为博客写到了类和对象这个章节,这里有句话可以讲了,就是成员函数要是写在类里,会被认成内联函数。
最简单的说法就是自动识别变量类型并给予,就是当类型用就行,而对应的就产生了一个检查类型的函数,如下
int a = 0;
auto b = a;
auto c = &a;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
可能我这样举例显得这个auto很鸡肋,实则不然,在迭代器或是其他的很长很长的类型名的时候,用这个就很爽。要说缺点吧 就像双刃剑一样,是十分便利,但是恰巧,当你不知道这个类型的时候,只能去查,就看个人熟练度呗。
有一点要注意的是auto不能做形参,也不能做数组。
这个真的是太爽了,真的好便利。
正常我们在遍历数组的时候 是这样(数组a[])
for(int i = 0 ; i < sizeof(a)/sizeof(int) ; i++)
{
cout << a[i] << " ";
}
在这里有一个范围for写出来就是这样、for(auto e : a)
{
cout << e << " ";
}
意思就是依次取数组中的数组赋值给e对象, 自动判断结束。
这里的auto 和 e 不是必须的
那么假设我想改数组里的值我该怎么做呢 如下
for(auto& c : a)
{
c*= 2;
cout << e << " ";
}
还是之前的知识,引用和auto哦
在语言的长河里,难免会有那么一些坑,比如NULL。在c++某些场景里就被定义成了0,所以就有了nullptr来替代null。
语言的发展里有一个原则,就算知道是坑,也不能改,只能向前兼容,遇到坑了,只能打补丁,因为这是语法,很多人在用,只要改了,可能别人的就挂了。
终于码完这些边边角角了,虽然不是很全,但能进入这个章节了。
简单说就是把结构体加深改造了,让他更安全了等等。
那什么是类和对象,简单说就是房子蓝图和房子。这里c++自定义了一个新的变量名字class。
class jwp
{
public:
void test1();
int _d;
private:
int _a;
int _b;
int _c;
protected:
void test3():
};
以上呢,就是class的用法,这个呢,就是蓝图,首先定义了一个变量名,然后分别设置了里面内容的使用权限:
public:公有访问,谁都可以看,谁都能用,一般只放函数,不会存在对象里
private:私有访问,只有结构体自己可以用,其他人不行,能很好的保护,一般放数据
protected:保护访问,同上,结构体自己和自己派生出来的可以访问
那怎么创建房子呢,如下:
int main()
{
jwp p1;
p1.test1();
p1._d = 3;
return 0;
}
用结构体的变量名创建一个对象,然后像struct一样去用就可以了。
这里的变量 a b c d 我在他们的前面都加了一个“_”,可能会显得多此一举,其实不是的,想象以下一种情景
class jwp
{
public:
void test1(int a, int b)
{
a = a;
b = b;
}
private:
int _a;
int _b;
};
其实这里不免看不来,这几个变量给的就很迷惑,虽然说意思也能大致明白,就是把函数里的变量再存回结构体,就是很难受看着,加个下划线就会好很多。
接下来再讨论下成员变量和成员函数的位置问题:
这里我们简单的看下对于他们的大小做了下计算,结果告诉我们,里面只有两个整形变量的大小“8”。并没有成员函数,其实这里转换到现实就可以很简单理解,首先数据是每个人都有自己单独的那一份,而函数是每个人都用一套,就像在一个小区里,每个人都有着属于自己独特的房间(数据),但是像公共健身器材啊,小卖部啊等等都是公用的。
那他们的存储位置呢就是成员变量存储在对象中,成员函数存储在公共代码段。
这里有一个比较有意思的问题:
试问这里的d1有多大,是零呢,还是什么,我们来运行下。
结果是一,怎么理解呢,还是转换到现实,虽然我这个图纸是空的,什么都没有,但是我有施工场地,我有这个实实在在的1,那些已经装修完的可能很大或者怎样,我这就是1.
内存里也是这样,类是空的不代表以后有没有,所以就拿1占个位置。
下面就说一下同一个class多个class变量的问题,就是说都用同一个函数存了很多的变量,如以下情况,是怎么做到各存各的呢。
这里其实是有隐形参数的如下
class jwp
{
public:
void data(Data* this,int year,int day)
{
this->_year = year;
this->_day = day;
}
private:
int _year;
int _day;
};
int main()
{
jwp d1;
jwp d2;
d1.data(&d1,1, 2);
d2.data(&d2,1, 2);
return 0;
}
像上面的this啊,&d1啊,都被隐藏了,而d1什么的就是区分不同对象的方法。
这里是验证
然后就是这个隐形参数this的存储位置,无论怎么说,他是个参数,理论就是存储在栈里。
这里开始进入重点,六个默认成员函数:
1.构造函数(初始化)析构函数(清理)
2.拷贝构造(初始化一个一摸一样的)复值重载(把值复值给另一个)
3.取地址重载 const成员函数 (第四和五都属于运算符重载,六是一个权限函数)
默认成员函数才是类和对象的重点,以上是可以系统自动生成的,也可以是自己写,自己写了系统就不会自动生成了,都叫默认成员函数,有 且只有一个。我会以我认为最简单的方式给大家代入这里,步骤为:首先自己实现,然后说下需要注意的细节。
我们先看这么一段代码
#include
class Date {
public:
void SetDate(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
printf("%d %d %d\n", _year, _month, _day);
}
private:
int _year;
int _month;
int _day;
};
int main(void)
{
Date d1;
d1.SetDate(2022, 3, 8);
d1.Print();
Date d2;
d2.SetDate(2022, 3, 12);
d2.Print();
return 0;
}
一段关于日期的类,每次都先要 Date 然后 SerDste 就很烦,初始化 传参 初始化 传参,既然这样,我们为何不把他简化或者省略呢,我们接着看下面这段代码
#include
class Date {
public:
//无参构造函数
Date()
{
_year = 0;
_month = 1;
_day = 1;
}
//带参构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//缺省构造函数
Date(int year = 1, int month = 0, int day = 0)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
printf("%d %d %d\n", _year, _month, _day);
}
private:
int _year;
int _month;
int _day;
};
int main(void)
{
Date d1;
d1.Print();
Date d2(2022, 3, 9);
d2.Print();
Date d3;
//Date d4(); 代入缺省看
return 0;
}
这里有三种,分别是无参,有参,缺省 用法也是如上,需要注意三点
一:可以Date d1 不可以d1.Date() 【构造函数是特殊的函数,不是常规的成员函数】
二:无参构造函数创建对象,对象后面不用跟括号,否则就成了函数声明。
三:带参构造函数,需要传递三个参数。
四:无参和全缺省可以同时存在,但会有二义性从而崩溃(有且只有一个)
这些什么的我觉得太麻烦了这么记,一句话,传参就带括号传全了,不然就不带括号
#include
class Date {
public:
void Print()
{
printf("%d %d %d\n", _year, _month, _day);
}
private:
int _year;
int _month;
int _day;
};
int main(void)
{
Date d1;
d1.Print();
return 0;
}
这个是使用系统自带的构造函数,可是运行结果如下
是很不理解的随机值,也不铺垫了,这里需要注意的就是系统自带的只能初始化自己认识的,就是
基本类型(内置类型(int char...))不认识自定义类型(class struct...)。
所以呢 大家在使用的时候 酌情处理就好啦
清理就是清理啦,只不过清理的是资源,不是彻底销毁,彻底销毁还是编译器。
1.没有参数和返回值(没有重载概念)
2.和构造一样 有且只有一个
3.生命周期结束后自动调用
4.类中声明时 函数名前面有~
既然涉及到了资源清理 Data 就合适了,我们可以看下面这段开空间的
class Stack
{
public:
Stack(int capacity = 4)
{
_a = (int*)malloc(sizeof(int) * capacity);
if (_a == nullptr)
{
cout << "malloc fail" << endl;
exit(-1);
}
_top = 0;
_capacity = capacity;
}
~Stack()
{
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
int* _a;
size_t _top;
size_t _capacity;
};
int main()
{
Stack s1;
Stack s2;
return 0;
}
这里要注意的就是析构函数只会对自定义类型做处理,内置类型不处理。这样其实挺稳妥的,需要注意的就是涉及到指针这种内置类型开空间的,自己写一个析构就好了。
建立一个对象的同时给他初始化成某一个函数,或者说就是以某个对象为样板进行初始化。
#include
class Date
{
public:
Date(int year = 0, int month = 0, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//拷贝构造函数
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main(void)
{
Date d1(2023, 4, 30);
Date d2(d1);
return 0;
}
1.他也是初始化,所以他只是构造函数的一种重载形式,没有返回值
2.参数一般用const修饰 因为传过去的对象不能被改变(应该好理解吧?)不加const属于是放大权限了 第二个就是可以防止赋值那里写反了
3.参数必须是引用传参 传值传参会无限循环
(这里我不能讲 因为我也理解的不是很透彻 这里原因就是 调用拷贝构造,需要先传参数,传值传参又是一个拷贝构造 无限循环)
这里我会以后理解了再重新组织语言哈哈哈明白了 我知道这句话差什么了
就是说自定义类型在传值传参的时候不能直接传过去,我想半天这一切不是正好吗?咋就循环了
问题就出在这里,他需要先复制一份,把复制的传过去,这里就涉及到了下面说的那个深/浅拷贝,编译器也不管,他也没法管,就统统调用拷贝构造帮他复制一份,注意!!就在这里,复制一份就需要调用拷贝构造,调拷贝构造就需要传参,参是自定义类型,就需要再拷贝构造一份,拷贝构造一份就需要传参,自定义传参就需要拷贝构造一次循环。引用是理想的传值,所以我觉得逻辑可以这么说(理解几天在写一句简单的):
自定义类型传值传参需要额外开数据进行拷贝构造再传(不是直接传),自写拷贝构造的参数在传值时就会 传值 是自定义?是 调用自写拷贝构造 传值 是自定义?是 调用自写拷贝构造 传值 是自定义?是 调用自写拷贝构造...
最后到了注意细节的这里 还是内置类型和自定义类型 ? 不是不是哈哈哈 这里涉及到的是浅拷贝和深拷贝 当前说这个不是很适合 简单说就是 浅拷贝是两个指针指向同一块空间(原本只有一个指针指向一块空间 浅浅的拷贝一下 再让一个指针指向这块空间 这样第二个指针也是这个值了,弊端是容易被析构两次) 深拷贝就是把空间里的内容 再开一块空间放进去 这样就是单独空间单独指针。
现在了解就是拷贝构造基本不用自己写,系统的会适应大部分情况,除了栈。
还有一个就是这里的顺序问题,其他博主写的很学术的太多了,我这里永远只给出我自己的浅显见解:构造是按顺序来的,析构是和他相反的,有class外的先构造,最后析构,静态的顺序构造,比class先析构。
我觉得这俩属于一个大类的一个分支,所以另起一篇:(27条消息) 运算符重载_晚风微凉稍稍寒的博客-CSDN博客
这里的aa会报错,原因是不兼容。就是因为在 A aa 前面加了const ,理解起来也简单,就是参数是const ,接收的时候this不是const,那this是隐性参数啊,他怎么加const呢。如下
具体使用呢 主要应用于函数
public里可以声明成员变量,main里可以定义对象,那么怎么定义对象里的单个变量呢,大家可能会想到直接在声明后面给个值就行了。可是那个叫缺省呢。于是我们有了初始化列表,可以定义每个变量。
class A
{
public:
A()
:_x(1)
, _a2(1)
{
_a1++;
_a2--;
}
private:
int _a1 = 1;
int _a2 = 1;
const int _x;
};
int main()
{
A aa;
return 0;
}
用法呢就是如代码所示,大括号里就是做一些相应操作,冒号开始,逗号结束,然后变量名后面的括号里是初始化值。需要注意的事呢,就是如果私有里的声明有缺省值,但还是以定义为主。只能定义一次。
还有一个就是这种情况:
class A
{
public:
A(int a)
:_a1(a)
,_a2(_a1)
{}
void Print()
{
cout<<_a1<<" "<<_a2<
如果是按照前面说的呢,输出的是1 1 ,但实际呢,是1 随机值,因为a2比a1先声明,也就比a1先定义,那个时候a1是随机值,所以就是咯。
需要初始化的变量有三种:引用成员变量,const成员变量,没有默认构造函数的自定义成员变量。
在两个类型不同的变量之间赋值的时候,比如把一个double给一个int,会发生一个叫隐式类型转化的事情,就是先自动创建一个const临时变量,然后把double转成int给这个临时变量,在把这个临时变量赋值到int变量上。
前景了解过了呢,这个指令就是禁止这个行为的,用法如下:
explicit A(int a)
:_a1(a)
,_a2(_a1)
{}
注意的就是只能适用单参数构造参数。
class A
{
public:A(int a = 0)
{
++count;
}
static int Getcount() //静态成员函数,没有this指针
{
//_a++;报错 没有this指针 无法直接访问非静态成员
return count;
}
private:static int cout; //静态成员变量 属于所有对象 整个类 和正常static一样理解
int _a = 0;
};
int A::count = 0; //初始化
int main()
{A aa[10]; // count是10
cout << A::Getcount() << endl; //没有this指针,可以直接调用
}
这里我就是想写这么一种如图所示的讲解,就算有什么需要注意的地方我也不知道了。
就是在创建对象的时候不给名字,直接类名和括号。声明周期只在那一行,一般用于即用即毁的东西。
A(); //比如class一个A 然后创建一个A的匿名对象
一般写在public里的一个函数,然后这个函数可以访问private里的值。用法还是,在函数重载用过
1.可以访问私有和保护,但不是成员函数
2.不能用const修饰
3.可以在类的任意地方声明,不受类访问限制符限制
4.一个函数可以是多个类的友元函数
5.和普通函数调用原理相同
6.被友元函数不是友元函数的友元
就是在类的里面再定义一个类。
class A
{
public:
class B
{
public:
int _a;
};
private:
int _b;
};
这里说下大小就明白了,sizeof一算还是4,说明A和B是独立的,只不过这个B受类域限制罢了。
这里B最大的优势就是创造出来就是A的友元。
最后说点细节:
当A=B=C时,理论上是先调用构造函数,再拷贝构造函数这么个顺序,但是实际上呢,编译器帮我们自动优化成了直接构造,但是在函数传值和传引用就不会优化(前面说的是直接传对象会优化)。所以
对象总结下就是:
1.接收返回值对象,尽量拷贝构造接收,不赋值接收
2.函数有返回对象时,尽量返回匿名对象
函数传参总结:
尽量使用const和 & 传参
wdm 这么多天 终于把这里写完了 2023.5.9 17点14分 芜湖
说是内存管理,其实就是这两个操作符(动态内存申请和释放),在C语言阶段,我们开空间用的是malloc和与之对应释放空间的free。c++新引进了一套:new和delete。用法:
int* p1 = new int; //开辟一个int 不会初始化
int* p2 = new int(0); //开辟一个int 初始化成0
int* p3 = new int[10]; //开辟十个int 不会初始化
int* p4 = new int[10]{1,2,3,4}; //开辟十个int 前四个按照顺序赋值,剩余初始化成0
delete p1;
delete[] p3;
以上就是new和delete的简单用法,需要特别注意的呢,就是二者不要交叉使用,因为delete可能会调用一些c++专有的像析构函数之类的,交叉就会引发一些 @..%xx$**^! 类似与这样的问题。
检查malloc是否错误需要判空,new错误会直接抛异常。
---------------------------------------------------------
有上面的理解就够了,以下说一些算是加深的把
上面说过这两个是操作符,为什么不是函数呢,operator new和operator delete 是系统自带的两个全局函数,在对应的操作符调动下与之运行。然而 operator new 呢,又是malloc的封装函数
(这句话可以理解成:如果没有自定义的operator new可以使用,就调用全局的,就是系统自带的,然后这个自带的就会调用malloc,再调用构造函数(总的来说operator new就是比malloc多了个构造))
stack st
stack* pst = new stack
这两个都是开空间嘛,区别呢,就是st是在栈上取名,在堆上开空间,pst呢,在栈上取名,new在堆上给他malloc一个空间,然后这块空间又在堆上构造一个空间,最后用的构造完的这块空间。
图示:
所以pst被清理时会先调用析构函数,再使用operator delete清理,相而free会直接清理第一次在堆上开辟的空间从而导致内存泄漏(c/c++以效率为优先,所以内存类的东西都是程序员自己搞定,要自己负责滴)。
第二个是开辟多个空间但直接delete的问题:
int* p1 = new int[10];
delete p1;
会直接报错,因为开十个空间的时候做了一个小动作,在整个数组前再开四个字节保存这块空间的大小,然后在正常使用delete时,会先减一,读取这个数字,就可以完美的delete了。
但是用free就可能不会报错,因为多开一个空间只是为了知道要析构的次数,当不使用析构时,就不会多开一个空间,就会正常运行。
---------------------------------------------------------
就是说一个函数因为功能一样但参数不一样,就根据这么一种使用状况,有了这么个东西。
template
void swap(T& x, T& y)
{
T tmp = x;
x = y;
y = tmp;
}
int mian()
{
swap(int a=1, int b=2);
swap(double a=1.1, double b=2.2);
return 0;
}
这就是个模板函数,temptlate就是他的关键字 用class也行,用typename也行,至于T就更随便了。
其实这里用的并不是同一个函数,表面上是一个,就像Linux的两个返回值问题一样,是两个。
调用的这个过程,这个具体创造函数的过程叫实例化。看着代码是减少了,实际还是那样。
这里传过去的参数类型必须是一致的,如果不一样可以强转。
还有一个就是显示实例化
add
(a,b);
这样就默认了两个参数都被强转成了int。
当模板和实例化后的函数一同出现的时候,编译器会考虑性能优先调用实例化后的(匹配的情况下)如果两者同时存在还只想用实例化后的就用显示实例化那套。
template
typedef n 10
class A
{
public:
T _a[n];}
int mian()
{
a
a1; a
a2; return 0;
}
在这个时候,确实是用模板可以完美的创建出两个不同类型对象,但是假如我想让a1等于10,a2等于20,该怎么做,好像一下就尬住了。(可能有些小聪明说直接赋值,那我要是再在类里定义一个量呢,这可是类啊),所以这个时候就有了新的玩法。
template
class A
{
public:
T _a[n];}
int mian()
{
a
a1; a
a2; return 0;
}
如上所示,这一类呢,叫做非类型模板参数(必须是整型),也是可以给缺省值的。
模板特化:
对于某一个特殊的,想对其类型特殊化处理,其实看着就是重载了一下费了个事,但是在某些场景有奇效,
template
bool less(T left, T right)
{
return left
}
//特化版本
template<>
bool less
(Date* left, Date* right) {
return *left < *right;
}int mian()
{
cout << less(1,2) << endl;
Date d1 (2022,7,7);
Date d2 (2022,7,8);
cout << less(d1,d2) << endl;
return 0;
}
上面这个特化版本属于是全特化,下面这个是偏特化,对全特化的范围缩小了
template
struct less
{
bool operator()(const T* l, const T* r) const
{
return *l < *r;
}
}
模板的分离编译:
简单点说就是声明和定义不在同一个文件下,调用时找不到链接,这个时候只需要显示实例化就可以了。
template
int add
(const int& left , const int& right);
但是都用模板了,类型就肯定不是一个了,用一个类型实例化一个,就很麻烦,不用这么麻烦也行,声明和定义在一起就可以了。
ps:模板问题一般都是在开头。
那么c++初阶就告一段落啦,其实三月末就该写完的QAQ。