1、引用:(相当于给变量取了个别名)
int i = 1;
int &j = i; //定义引用j,并且把i绑定给j,则相当于j是i的另一个名字,两者等价
2、指针:(就是存储地址)
int i = 1;
int *j = &i; //定义指针变量j,取出i的地址给j
指针j是存着i变量地址的一块空间,所以还可以取出j这个指针的地址赋给另一个指针,
而引用没有地址。
(注:对于初始化,c++中可以写int i = 1也可以写int i(1),即在赋初值时什么等于什么等价于什么括号什么)
(可以对指针进行加减法,代表指针的前后移动)
3、注:*j==i ,通过对j用解引用符,得到是j所指的变量,所以*j=2,相当于执行了i=2
4、注:int &j表示j引用的变量的类型是int,同理int *j即表示j所指的变量类型为int。
5、指针不能指向一个引用(因为引用没有地址,不是对象),而引用可以引用一个指针(指针有地址,是对象),引用也可以引用一个引用
int i = 1;
int *j = &i;
int *&m = j; // int &m表示m引用的变量是int类型的,而int *&m表示m引用的变量是int指针类型的
6、c++中定义一个常量的方式是:const int i = 1;
const int *j = &i; //j是一个指向常量int类型的指针,i是个常量int,所以可以指向i。因为指向的是个常量,所以不能*j=2来改变常量i 值,而j这个指针不是常量,所以可以让它指向别的对象
const int *const k = &i; //k是一个指向常量int类型的常量指针。因为现在指针k也是常量,所以不能改变k的值,即不能改变k中存的地址,即不能让k指向其它对象
7、类型别名:
typedef int zhengshu; //将类型int取了个别名叫zhengshu
zhengshu i = 1; //相当于int i = 1;
typedef int *zhengshudizhi; //将类型int*(指向int的指针)取了个别名叫zhengshudizhi
zhengshudizhi j = &i; //相当于int *j = &i;
(c++11中也可以这样定义类型别名:using zhengshu = int)
8、struct:
一般一个struct定义在一个.h文件中,文件名为struct类型名:
People.h
#include <string>
struct People{ //定义People类,成员变量有id、名字、性别、年龄
long id = "";
std::string name = "";
unsigned int age = 0;
};
(注意struct最后要加分号)
main.cpp
#include <iostream>
#include <string>
#include "People.h"
//要用到某个你定义的类,就把它include进来就好,但是自己的类是双引号
using namespace std;
(or using std::cin; using std::cout;)
int main (){
People people; or class People people;
//这就搞出来一个people对象了,
//然后people.xxx就可以对这个对象的成员变量进行取出或赋值操作
}
注:编译前,有个预处理阶段,会将所有的#include替换为对象的.h文件,那么在上面例子中,main.cpp include了string,然后People.h中也include了string,那么替换后,main.cpp中就会有两段string的定义产生错误,所以一般在写头文件时,都会加上头文件保护符:
#ifndef PEOPLE_H_INCLUDED
#define PEOPLE_H_INCLUDED
#endif
框在你内容的前后,这样就能确保头文件不会被重复包含,上例中在main.cpp中包含了string,然后在People.h中,又遇到include <string>时,因为string头文件中肯定也写了保护符,所以第二次include时,就不会再包含进来。
9、注:头文件中不应该用using来声明命名空间,因为头文件会被其他文件包含过去,如果在头文件中using namespace std则别人include你的头文件时会不经意的也using了std,可能造成难以预料的名字冲突,在代码中用到命名空间的名字都要写全std::cin std::cout std::string。
(命名空间的作用即保证名字间不冲突,比如你引入的iostream中有cin cout对象,可能你include的别的包里也有叫cin cout的对象,标明是std的cin cout可避免冲突)
1、 string:(#include <string>)
和java不同的是 在c++中,字符串不是string类型,它可以用来初始化string但并不是string。
2、 可以使用加法将两个string类或者一个string类一个字符串拼接起来(但不能用加法将两个字符串拼接,因为字符串不是string类,加法没有拼接功能)
3、 string相当于是char数组。s[i]可以访问第i个字符,s.size()是字符串长度
4、 vector:(#include <vector>)
vector是对象的集合,也称为容器。vector<int> v即存着int类型的集合,vector<People> p就是存着People类型的集合,我觉得就是类似于java中用了泛型的ArrayList。
p.push_back(people);即向vector p中添加一个people对象,p[i]可取出p中第i个对象
5、迭代器:
对于容器(除了vector还有其它容器)的遍历,一般使用迭代器的方式(因为并不是所有容器都支持下标访问):
#include <vector>
vector<People> p;
vector<int>::iterator it;
for(auto i=p.begin(); i!=p.end(); i++){
(*i).id=0; //将容器p中所有People对象的id赋0。注意*i要加括号
}
可以看出,其实迭代器可以当作就是指针,p.begin就是指在p容器的第一个元素上的指针,end是指在最后一个元素的后面(不指向某个元素)
另:c++11中还有p.cbegin和p.cend,他们的区别是用cbegin end操作对象是,不允许修改对象,只能读取
5、 箭头运算符-> :
上例中(*i).id等价于i->id,可以看出箭头运算符取出了箭头左边那个指针指的对象,并访问这个对象在箭头右边的成员
6、抛一个异常:throw runtime_error(“some information about the exception”);
捕获异常:try{}catch(runtime_error e){}
7、 int i = 1;
auto j = i; //则auto会通过赋值的类型自动识别,变成int,相当于int j = i
decltype(i) k; //decltype会将i的类型作为这里的类型,这里定义了一个int k;
1、可以将你的函数写在一个单独的cpp文件中,然后将你函数的声明写一个h文件中,当在main中需要调用你写的函数时,只需要将h文件包含进来,即可使用:
#####################################################
my_print.cpp:
//这里写了个类似java的print函数
#include <string>
#include <iostream>
using namespace std;
void my_print(string s){
cout<<s<<endl;
}
#####################################################
my_print.h:
//在h文件中声明了my_print函数
#ifndef MY_PRINT_H_INCLUDED
#define MY_PRINT_H_INCLUDED
#include <string>
void my_print(std::string s);
#endif // MY_PRINT_H_INCLUDED
#####################################################
main.cpp:
//在main中调用写在另一个文件中的my_print函数,将.h文件包含进来即可
#include "my_print.h"
int main (){
my_print("hi");
}
#####################################################
在实际运行时的具体过程:
首先编译main.cpp和my_print.cpp,生成main.o和my_print.o:
g++ -c main.cpp my_print.cpp
(注意:这里在main.cpp中,main根本不知道它调用的my_print函数的定义,只是在调用前包含了下,其实也相当于在main前声明了my_print函数,因为include相当于将.h文件复制进来,.h文件中声明了my_print函数)
至此,生成的是两个完全独立的.o文件,各自不知道对方的存在。
此时进行链接,生成一个名字为xxx的可执行文件:
g++ main.o my_print.o –o xxx
这里将两个.o文件链接在了一起,此时main才知道它要调用的函数具体是什么,才能真正的调用,才能最终生成一个完整的可执行文件
在生成main.o文件时,即使不存在my_print函数的定义,也依旧可以正常编译。但是在运行前的链接阶段,找不到my_print.o,就会报undefined reference错误
1、 函数传值:当函数的形参是引用时,则传过去的实参是直接绑定在这个引用上,此时函数内部对引用的操作就是等同于直接操作了实参。而对于其他形式的参数(不是引用的),如指针或者变量,则参数传递时,是值拷贝的形式,函数中用的和传进去的是两个独立的东西。(传递指针,则复制一个指针,指向同一个对象,传给函数,但函数中对这个指针的移动,不会影响到你传进去的那个指针的位置)(函数传值可以理解为是实参对形参的初始化)
2、 尽量用引用传递,而不要用值拷贝,这样对于大的对象可以节省拷贝的时间。
3、 如果在函数中不需要改变你拿到的引用对应的实参,则在形参声明时,最好将这个引用声明为const类型,表示我只是拿你的引用,节省拷贝的时间,但是实际上只会对你进行查看,而不会修改。声明为常量的引用后,则在函数中不能对该引用进行修改,即不能改变这个引用对应的实参
注:是将引用的类型声明为常量,即让这个引用认为它引用的是个常量类型,而不是声明一个常量的引用(有常量的指针,但是没有常量的引用)
4、 const string &method_name(); 这个函数的意思是返回一个常量string类型的引用。同理const string *method_name(); 即返回一个指向常量string类型的指针,const string method_name() 即返回常量的string类型,其中第一种是返回的引用,后两种都是用拷贝的值返回。(函数的返回也可以像函数传值那样理解为一种初始化)
(注!!!若函数需要返回一个数组,返回的方式是,多接收一个参数用来装返回的数组,即这个数组是在这个函数外就已经定义好分配好空间了的,不能将函数内部定义的数组返回,因为函数一结束,这个数组空间就回收了)
5、 main函数是唯一一个写了返回类型为int却可以不返回任何值的函数,当没有写return时,在main函数结尾处会自动加上return 0表示main函数的执行状态是成功的,若返回的不是0则表示执行失败。
6、 函数的定义和声明时还可以这么写:
auto method_name()->int{…} //定义了一个返回类型为int的函数
auto method_name()->int; //声明了一个返回类型为int的函数
7、 函数参数默认值的指定:在声明和定义时可以这么写
void method(parameter a,parameter b=1,parameter c=2)
则在调用时给了默认值的参数可以不传值过去,则会用默认值。注:定义和声明时,有默认值的参数一定要放在最右
8、 函数指针:
void my_print(string s){…} //定义一个函数
void (*p)(string s) = my_print;
//定义函数指针p,指针左边是函数的返回类型,右边是形参列表,返回类型和形参列表共同确定了函数指针的类型,而my_print函数的返回类型和形参列表和该函数指针类型相匹配,所以可以&my_print将函数地址取出赋给这个指针,(或者直接赋也行,编译器会将函数名看为指针)则这个指针指向该函数。(形参的名字无所谓)
p(“hello”);
//则可以取出该指针所指的函数,并调用,(*p)(“hello”),或者直接p(“hello”)
9、 void method(void p(string s)) or void method(void (*p)(string s)) is the same
这个函数接收一个返回类型为void形参列表为string的函数作为参数,第一个里面的
void p(string s)是一种函数类型,p只是相当于函数类型的变量名,而第二个里面的
void (*p)(string s)则是指向某种函数类型的指针,这两种声明方式是等价的,因为将函数作为参数传递进来时都会当成函数指针使用。调用该函数:method(my_print) ,直接将函数名传递过去,则相当于把指向该函数的指针传递了过去
10、可以给函数类型定义别名,使其看上去更简洁:
typedef void hanshuleixing(string s) or using hanshuleixing = void (string s)
//给返回值是void形参是string的函数类型定义别名hanshuleixing
typedef void (*hanshuzhizhen)(string s) or using hanshuzhihen = void (*)(string s)
//给指向返回类型为void形参为string的函数类型的指针定义别名hanshuzhizhen
则9中的函数可以写成void method(hanshuleixing p) or void method(hanshuzhizhen p)
11、当返回一个函数指针类型时:
void (*method())(string s)
//从内往外看,(*method())表示函数method返回一个指针类型,一个指向返回类型为void形参为string的指针类型
用上面定义的别名表示:
hanshuzhizhen method() //注:这里只能返回函数指针,不能返回函数类型,不像之前几种,函数类型编译器会自动看成函数指针
还可以用之前讲过的这种方式表示也很明了:
auto method() -> void (*)(string s)
1、People.h(省略了头文件保护符和include等信息)
struct People{
std::string name = ""; //定义成员变量
std::string get_name() const;//声明了类的成员函数,分别用来get set成员变量
void set_name(std::string name);
};
void print(std::ostream &os,const People &p);
//声明了与类相关的非成员函数,用来get出People的相关信息到打印到传进来的ostream
###############################################################################
People.cpp
#include “People.h”
string People::get_name() const{
return name;
}
void People::set_name(std::string name){
this->name = name;
}
void print(ostream &os,const People &p){
os<<p.get_name()<<endl;
}
###############################################################################
main.cpp
#include “People.h”
int main (){
People p;
p.set_name("xmr");
print(cout,p); //打印出来该People对象的信息到cout
}
###############################################################################
//People类定义了一个成员变量,两个成员函数和一个与类相关的非成员函数,其相关声明都写在了People.h中,而其函数的定义写在了People.cpp中,main里面包含了People的声明,所以可以单独编译成一个.o,People.cpp也可以编译成一个.o,最后两者链接便可执行
//成员函数和非成员函数区别在于,成员函数需要用一个具体对象去调用,如p.get_name,而非成员函数其实和普通函数一样,只不过声明在类的h文件中,表示和这个类相关,包含的时候一起包含进来
//void set_name(string name); 等同于 void set_name(People *const this,string name);
即对于成员函数,其隐式的自带一个常量指针this作为形参,指针类型是该成员函数所在的类。 调用时:
//p.set_names(“xmr”); 等同于People *const this = &p; p.set_name(this,”xmr”);
即将指向调用者p的一个常量指针传过去。这样在函数内,函数才知道是操作哪个对象的name
//而string get_name() const; 等同于 string get_name(const People *const this);
即声明时括号后的那个const表示this指针指的类型是常量的。调用时:
p.get_names(); 等同于People *const this = p; p.get_name(this);
(get_name需要一个指向const People的常量指针,你传过去的是指向People的常量指针是可以的,会将你传的指针的值复制一份赋给那个那个指向const People的指针,则在函数执行过程中,使用的这个this,因为是指向const People类型,所以不能改变指针所指的对象(因为指针认为它指的是一个常量),所以如果你的成员函数不需要改变对象的内容时,将其加const声明为常量成员函数,这里get_name就是)
(另一方面,当你声明了一个常量对象const People p;调用p.get_name(),此时传过去的指针类型应该是const People *const this = p (因为p是const People类型,this指向它,则this是一个指向const People类型的指针),如果你的get_name没有声明为常量成员函数的话,它需要的参数是People *const this(同set_name),则你无法将一个指向类型为const People的指针赋给指向类型为People的指针,但是常量对象也应该可以拿到自己的名字,只是不能修改名字,所以将其声明为常量成员函数是有必要的)
(综上:当成员函数不修改对象内容时,将其声明为常量成员函数)
//注:在People类外部定义成员函数时要在函数名字前加People:: ,这样你在定义函数时,编译器会等同于你在类的内部定义,你才能访问到类内部的变量。但这只是相当于将你的函数内容放在了类的作用域内,对于函数的返回类型,它的作用域仍在外面,如果返回类型是类内部定义的,也需要在返回类型前加上相应作用域运算符::
2、类的构造函数:当像上面那样没有显示定义构造函数时,会用默认构造函数初始化类的成员变量,将它们赋为给的初始值或者没给初始值就赋为默认初始值。自己写构造函数如下:
struct People{
string name;
unsigned int age = 10;
People(string s){
name = s;
}
People(string s):name(s) { }
People() = default;
};
C++构造函数的规则大体和java类似,上面是几种构造函数的方式,第一种和java完全类似,第二种在冒号和大括号之间的东西叫初始值列表,可以用类似name(s)这样来给多个成员变量初始化,用逗号隔开,第3个就是默认的构造函数再显式的定义了一下。
--注意第一二种构造函数的形式是有所区别的,第一种构造的方法是去将收到的值去给成员变量赋值,而第二种构造方法是用收到的值去初始化成员变量。初始化和赋值的区别在哪?
int i = 1; 等同于 int i(1); 是定义并将i初始化为1
int i; i = 1; // int i定义并将i默认初始化为0,i = 1是将1赋值给i,经过了两个步骤
--对于第一种构造函数,在执行构造函数体之前,编译器就会完成对成员变量的初始化,对于支持类内初始化的编译器,会将name默认初始化为空字符串,将age初始化为10,初始化完成后才进入构造函数体内执行赋值操作
--对于第二种构造函数,编译器会直接将name初始化为s,而将age用类内初始化为10
--但对于不支持类内初始化的编译器来说,你必须将所有成员变量的初始化都写在冒号和大括号之间
--建议使用第二种构造函数的方式去显示的初始化所有成员变量
因为对于成员变量有常量、引用或某个成员变量所属类没有默认构造函数时,使用第一种构造函数会出错:
--对于常量,若你没给它初始值,则在执行构造函数体之前,它就已经被默认初始化为0了,那么常量一经初始化就不能修改了,此时你再在构造函数体内给常量赋值是错误的。
--对于引用类型的成员变量,如果你在类内没给它初值(绑定它),则必须在每个构造函数的初始值列表中给它初始化值。(引用类型必须要人为初始化)
--对于没有默认构造函数的类成员变量,因为在执行构造函数体之前,就已经完成所有初始化了,而对于这个类成员变量又没有默认构造函数,编译器不知道该这么初始化它,就会报错,所以要在构造函数初始值列表中自己给它初始化。
--冒号和花括号间除了可以写初始值列表外,还可以在里面写委托构造函数:
People(string s):People(){...}
//即先调用另一个构造函数去初始化,执行完People()构造函数后再执行本构造函数
--调用构造函数的方法:People p(“xmr”);则它会自动匹配相应的构造函数去初始化People
当要使用默认初始化时,写People p;而不能写People p();
(我上面写的那第一二个构造方法为了举例,是重复的,会报错的,不是重载)
同样也可以将构造函数的定义写在类外部或者另一个文件中,则同样也要在定义时函数名前加上类名:: 如在外部定义People类的构造函数 People::People(string s){...}
3、可以在类的成员变量或者成员函数前加上public:或者private:修饰:
public:void set_name(string name); private:name; 或者
private:
long id;
string name;
unsigned int age; //声明private之后的内容都是private,直到下一个修饰符出现
4、注:在定义类时,用struct或者class关键字都是一样的,唯一不同在于struct默认访问权限是public,class默认是private
5、与类相关的非成员函数如果也想直接拿到私有的类成员变量,那么可以在类内部将非成员函数声明为友元:friend void print(...);类内部只是声明为友元,并没声明函数,所以在外部依旧要再声明一次。(友元函数可以直接定义在类的内部,但也要在外面声明下)
--除了可以将类相关的非成员函数声明为友元外,还可以将其它类声明为本类的友元:
friend class class_name; 则该类的所有成员函数都可以访问到本类的私有成员变量
当然也可以只将其它类的某个成员函数声明为本类友元:
friend void class_name::method_name(param);//即要声明是哪个类的哪个成员函数为友元
6、private的成员变量只能被成员函数和友元函数直接使用,private的成员函数只能被成员函数和友元函数调用。public无限制
7、可以对类的数据成员修饰一个mutable,表示该成员永远可被修改:
mutable string name; //本来对于常量成员函数来说,因为它拿到的是指向常量类型的指针,所以该函数不能修改对象的内容,而对于声明了mutable的成员函数,它永远可以不会是常量,永远可以被修改,即使是常量成员函数,也可以修改它
8、类里面还可以定义属于自己这个类的类型别名。一般要在类开头的地方定义:
struct People{
public:typedef string name_type; //在类开头定义公共的类型成员(类型别名)
name_type name = ""; //后面的string都改用这个类型别名
name_type get_name() const;
void set_name(name_type name);
};
这样可以向类的使用者封装隐藏自己类的一些实现细节。使用者使用这个类型别名的方法是:
People::name_type my_name = “xmr”; p.set_name(my_name);
(string::size_type就是一个string类下的类型别名,它本质就是unsigned int)
9、People类有构造函数People(string s);
还有一个非成员函数void print(ostream &os,People &p)
--则可以这样调用:string s = “xmr”; print(cout,s);
print函数需要一个People类,你给了一个string类,则它会自动调用相关构造函数去将你的string类转为People类来调用(注,不能直接写print(cout,”xmr”); 因为”xmr”不是string)
--也可以这样:People p = s; 构造了一个名字为xmr的People对象,也是用过上述方式进行了隐式的转换
--若不想这种隐式的转换发生,则可以explicit People(string s) 用explicit修饰构造函数来阻止使用该构造函数进行隐式转换
--这种隐式转换只会发生于只接收一个参数的构造函数,而不会使用那些接收多个参数的构造函数进行转换,所以接收多个参数的构造函数可不必声明explicit
--explicit只用在类内声明构造函数时写
10、聚合类:当一个类中没有定义任何构造函数,有一堆public的没有类内初始值的成员变量,则这个类为聚合类,如:
struct People{
string id;
string name;
unsigned int age;
void some_methods(...);
};
则可以这样初始化这个聚合类: People p={“123”,”xmr”,22};
11、类的静态成员:静态的意思即,该静态成员变量或静态成员函数,是与类相关联的,而非与对象关联。
--静态成员变量:即整个类只有独一无二的一份该静态成员,所有对象都共享这一份,而不同于非静态的,是每个对象都有属于自己的成员变量,大家访问的都是自己的。
--静态成员函数:因为静态就是与类关联,与对象无关,所以对于静态成员函数来说,它也不同与非静态的成员函数,会隐式的包含一个常量指针作为参数,用来指向调用该函数的对象,那么对于静态成员函数来说,它则没有这个this指针
--静态成员的声明:在类内对变量或函数修饰static就行,和explicit一样,只用在类内声明这个修饰符
--静态成员的初始化:静态成员变量(除了常量静态)不能在类内初始化,也不能在构造函数中初始化(因为类内初始化和构造函数的初始化都是对对象的的初始化操作,而静态的是和对象无关的)要在People.cpp中int People::i = 1;这样去初始化静态成员变量i 。 静态成员函数的定义则和普通没区别
--静态成员的使用:可以直接用作用域运算符访问静态成员,类名::静态成员变量 or
类名::静态成员函数.(param) 当然用对象直接去访问也可以
1、
基类A:
class A{
public: type a;
protected: type b;
private: type c;
public: void method_a(..);
protected: void method_c(..);
private: void method_b(..);
}
派生类B继承A:
class B: public A{
type d;
void method_d(..);
}
继承后的B相当于:
class B{
public: type a; //继承自A
protected: type b; //继承自A
type d; //自己的
public: void method_a(..); //继承自A
protected: void method_c(..); //继承自A
void method_d(..); //自己的
}
--之前介绍过public和private修饰的成员变量和成员函数,这里再介绍protected修饰符 。public修饰的成员谁都能直接访问或调用,private修饰的成员只有类内成员和友元可以直接访问或调用,也正因为private只能类内和友元访问或调用,所以在继承中,privte修饰的成员不能继承下来,不然在类B内部也可以访问或调用了,而protected修饰的成员除了类内部和友元外还允许该类的派生类内部直接访问或调用,所以protected修饰的成员被继承进B中,在B的内部和B 的友元也可以直接访问或调用
--在继承时,在继承的类的前面加public、protected、private,表示继承的类型,上例中,在继承时使用public修饰继承类A,则A中的public成员继承为B中的public成员,A中的protected成员继承为B中的protected成员(private成员不能被继承),而如果用protected修饰继承类A,则A中的public和protected成员都会被继承为B中的protected成员,如果用private修饰继承类A,则A中的public和protected成员,都会被继承为B中的private成员
--注:对于B从A继承下来的成员变量,应该要在B的构造函数中,委托A的构造函数对B中继承自A的变量初始化,如果在B中的构造函数没有委托任何A的构造函数,则编译器会自动调用A的默认的构造函数将B中的type a,type b初始化
--另:c++中允许多继承,class C: public M,public N{...},则类C继承了类M和N,继承的形式和方法和上面单继承一样
2、
基类A:
class A{
type a;
void method_a(..);
}
派生类B继承A:
class B: public A{
other_type a;
void method_a(..); //重写了父类的method_a
}
继承后的B相当于:
class B{
other_type a; //自己的
void method_a(..); //自己的
}
--当B中自己定义的成员变量或函数和A继承下来的同名时,将会覆盖掉A的,如果此时想使用A的,要用作用域运算符::
--因为B继承了A,所以B类也是A类
B b;
A *a_point = &b; //所以指向类型为A的指针也可以指向B
A &a_ref = b; //也可以将B绑定给引用类型为A的引用
a_point->method_a(..); //此时调用的是类A中的method_a
--要将A中的method_a声明为虚函数virtual void method_a(..); 才会动态绑定,调用这个引用或指针实际所指或所引用的对象类型(即B)的方法,而不是只看指针或引用的类型来调用
--若A中声明了method_a为虚函数,则B中重写的那个method_a也默认是虚函数,可供B的子类再去重写
--纯虚函数:若基类A中的method_a,只是想让子类去实现,让子类去重写,则可以将其声明为纯虚函数 virtual void method_a(..)=0; 则自己可以不用对它给出任何定义和实现
--抽象类(c++的接口):只要类中有纯虚函数,则这个类就是一个接口或者抽象类,它不能实例化,只能用来被继承(实现)
1、函数模板:
假如你要写一个函数,要能比较int、float、string之间的大小,则你得这样写:
int compare(int i,int j){
if ( i < j ) return -1;
if ( i > j ) return 1;
if ( i = j ) return 0;
}
int compare(float i,float j){
if ( i < j ) return -1;
if ( i > j ) return 1;
if ( i = j ) return 0;
}
int compare(string i,string j){
if ( i < j ) return -1;
if ( i > j ) return 1;
if ( i = j ) return 0;
}
你得重载很多个几乎一样的方法。并且假如用户自己定义了一个可以比较的类型,则你这个compare方法就不能提供功能了,因为你不知道用户自定义的是什么类型。
那么此时使用函数模板:
template <typename T>
int compare(T i , T j ){
if ( i < j ) return -1;
if ( i > j ) return 1;
if ( i = j ) return 0;
}
函数模板提供给了编译器一个自己生成函数的方式,假如用户此时调用
int i =1; int j = 2;
compare( i , j );
则编译器会按着模板的样式,将模板中的T都换成int,生成一个函数:
int compare(int i,int j){ //和1中第一个函数一样
if ( i < j ) return -1;
if ( i > j ) return 1;
if ( i = j ) return 0;
}
无论你用什么类型去调用这个函数,编译器都会按着你给的类型去匹配T,生成一个相应的函数给你调用。
所以函数模板它并不是一个函数,是在调用时,编译器根据你的调用,照着模板生成的那个,才是一个函数。
模板中也可以不止有一个类型T,也可以template <typename T,typename U>这样定义多个类型,另:typename关键字也可以写成class关键字
类模板:
vector类是类模板的一个很好的例子,这里模仿写一个vector:
template <typename T>
struct my_vector{
private:T i[100] = {}; //一个数组,用来存放进来的东西
public:T *my_begin = i; //模仿vector的begin和end指针
public:T *my_end = i;
public:void my_push_back(T t){ //模仿vector的push_back函数
*my_end = t;
my_end++;
}
};
int main(){
my_vector<string> v;
v.my_push_back("haha");
v.my_push_back("123");
v.my_push_back("xmr");
for(auto i=v.my_begin; i!=v.my_end; i++){
cout<<*i<<endl;
}
}
和函数模板一样,类模板也是一个模板,是让编译器照着这个模板去生成相应的类,本来你要自己写一个vector_string类、vector_int类、vector_xxx类等等,但是你用类模板,则编译器会根据你vector<...>尖括号里面的类去帮你生成相应的类,所以上面使用vector<string>时,此时编译器就用string类代替模板中的T生成了一个类,当你写vector<int>时,它又会帮你生成另一个类