一.基本知识
1.命名空间
(1)C的命名缺陷
原因:rand与库函数中的rand()函数,名字冲突了
(2)C++提出的解决方案 ——命名空间
命名空间——命名空间域 :只影响使用,不影响生命周期,之前是全局还是全局变量
namespace AQueue //a的命名空间
{
struct Node
{
struct Node* next;
int val;
};
struct Queue
{
struct Node* head;
struct Node* tail;
};
int x = 0;
}
namespace BList
{
struct Node
{
struct Node* next;
struct Node* prev;
int val;
};
int x = 0;
}
这样a,b就不会因为都有Node而重命名了冲突了;
//命名空间与结构体不同之处在于不需要加";"; 命名空间可以嵌套;
int main()
{
struct AQueue::Node node1;
//AQueue 是命名空间, :: 域操作符 ,node1命名的名字 ,空代表全局域
struct BList::Node node2;
AQueue::x++;
BList::x++;
return 0;
}
using namespace std; //加到全局变量的操作,std是库,std是自己封起来的
//本来命名空间AQueue,这样之后就相当于把AQueue加到全局变量里
2.C++输入输出
操作符: << 流插入 ; >> 流提取
int main()
{
int n;
cin >> n; //流(输入)到n里去
double* a = (double*)malloc(sizeof(double) * n);
//C++可以自动识别类型
for (int i = 0; i < n; i++)
{
cin >> a[i]; //这里就不需要写"%d"之类的了
}
for (int i = 0; i < n; i++)
{
cout << a[i] << endl; //看成把a[i],endl流(输出)出去
//控制精度还是用c :printf("%.2f",a[i]);
}
return 0;
}
3.缺省参数
注意:缺省值必须是全局变量或常量
//void fc(int a)可以写成这样:
void fc(int a = 0)
{
cout << a << endl;
}
void fcc(int a = 1, int b = 2, int c = 3)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
int main()
{
fc(1); //当a传值时,用传的值
fc( ); //当a没值时,用自己一开始的值
fcc(4, 5, 6);
fcc(4, 5);
fcc(4);
fcc( );
//不能跳跃缺省: fcc( , ,6) 这样不可以
return 0;
}
函数的声明和定义不能同时缺省参数
void fcc(int a=10);
void fcc(int a=10)
{
cout << a << endl ;
}
一定要是声明的时候给数,定义的时候不给。
eg:
void fcc(int a=10);
void fcc(int a)
{
cout << a << endl ;
}
4.函数重载——允许同名函数(在同一作用域)
C++在编译的时候会修饰这两个重载函数,使其有差别,就不会重定义了。
*返回值不同,其他相同,不能构成重载,因为当不需要返回值时你也不知道调的哪个函数。
5. 引用——针对指针的缺陷
int main()
{
int i = 0;
int& k = i;
//相当于取别名,不会另开空间,(取别名k,指向i ),"int&" 是一个类型,同“int*”
int j = i; //直接复制,而且另开一个空间
int* p = &i;
int*& rp = p; //任何类型都可以取别名,包括指针
//只有在 int& 才是引用,其他时候都是取地址
cout << &i << endl;
cout << &k << endl;
cout << &j << endl;
//i,k 地址一样
k++; //i也++;
j++;
int& q = i; //可以继续给本名取别名
int& m = k; //也可以继续给别名取别名,都是这同一个空间的名字
return 0;
}
1.应用举例:
void Swap(int& x, int& y)
//这样弄得话,形参是实参的别名,形参的改变就可以影响实参了
{
int tmp = x;
x = y;
y = tmp;
}
int main()
{
int i = 0;
int j = 2;
Swap(i, j);
cout << i << endl;
cout << j << endl;
return 0;
}
2.引用返回:
1.减少拷贝;
2.调用者可以修改返回对象;
struct Ax
{
int a[10];
int i;
};
int& Posat(Ax& ax, int i)
{
return ax.a[i];
}
int main()
{
Ax ax;
for (int i = 0; i < 10; i++)
{
Posat(ax, i) = i * 10; //引用的返回值可以被修改
}
for (int i = 0; i < 10; i++)
{
cout << Posat(ax, i) << " ";
}
cout << endl;
return 0;
}
错误案例:
这个地方的 “&” ,相当于又给c取了别名ret,所以ret并不是的可以维持的变量,一出add函数就会被销毁掉;如果不用引用,而是int ret = Add(1,2) ,那么也不一定是对的,因为出函数的时候,可能这个栈帧会被销毁,c的拷贝保不保留不确定,所以最好的是int Add(int a,int b)。
3.引用的权限问题
4.与指针的区别
6.内联函数——针对宏的缺点
1.宏的缺点:
· 不能调试;
· 没有类型安全的检查
· 有些场景下非常复杂,容易出错,不容易掌握
2.代码:
//继承了宏的优点:不需要栈帧;而且还能调试
inline int Add(int x, int y)
{
int z = x + y;
return z;
}
int main()
{
int ret = Add(6,2);
Add(3, 4);
cout << ret << endl;
return 0;
}
3.内联会导致编译后空间变大(相当于宏的直接展开,而普通的函数调用就用一行)
7. auto类型——自动确定类型
int main()
{
int a = 0;
auto b = a; //自动确定类型
auto c = &a; //自动确定了类型
cout << typeid(b).name() << endl; //typeid(b).name() 可以返回括号内变量的类型
cout << typeid(a).name() << endl;
}
可以用来简化代码:当类型名很长的时候可以用
(1)范围for:
int main()
{
int array[] = { 1,2,3,4,5,6 };
//范围for——语法糖
//意思:自动依次取数组中的数据赋值给e对象,自动判断结束
for (auto e : array) //for(int x : array) 也可以,不过一般都是auto
{
e = e * 2;
cout << e << endl;
}
//现在数组是{1,2,3,...}
//要改变数组内容则要加引用,因为这个e只是数组中元素的拷贝,改变e没有用
for (auto& e : array)
{
e = e * 2;
cout << e << endl;
}
//现在数组是{2,4,6,...}
return 0;
}
(2)注意:
1.auto不能做形参: test(auto a);
2.auto不能声明数组: auto a[10];
8.nullptr与NULL的区别:
因为某些历史遗留问题,C++中的NULL被定义为0(#define NULL 0),在某些场景下,NULL可能会被识别成整数0(int类型)。
出了这个BUG之后C++11打入了新补丁,用nullptr来表示空指针。所以最好空指针用nullptr。
二.类和对象
1.类与结构体的区别
① 类里面可以有数据,这个数据叫做成员变量
②类里面还可以定义函数
2.类
(1)类的定义
//类 ——长得很像结构体
struct Stack
{
//成员变量
int* a;
int size;
int capacity;
//成员函数
void Init(int n=4) //缺省参数
{
a = (int*)malloc(sizeof(int) * n);
capacity = n;
size = 0;
}
void Push(int x)
{
a[size++] = x;
}
};
int main()
{
Stack st;
st.Init(); //类中函数的调用
st.Push(1);
return 0;
}
//虽然struct可以定义类,但是C++最好还是用class来定义
若声明与定义分开,则需要在定义时加上命名空间
头文件:
struct Stack
{
int* a;
int size;
int capacity;
void Init(int n = 4);
void Push(int x);
};
.cpp文件
void Stack::Init(int n = 4) //需要加 “Stack::”这样的命名空间
{
a = (int*)malloc(sizeof(int) * n);
capacity = n;
size = 0;
}
void Stack::Push(int x)
{
a[size++] = x;
}
(2) 访问限定符
class Stack
{
private:
int* a;
int size;
public:
int capacity; //公有
};
(3)封装
(4)类的实例化
//类就像是一副设计图,实例化才是建造的成果
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//private:
int _year; //这些都是声明,因为并没有开空间
int _month;
int _day;
};
int main()
{
//类对象实例化——给他开辟空间
Date d1;
Date d2;
类和结构体一样,也要考虑内存对齐的规则:
d1.Init(2023, 2, 2);
d1._year++;
cout << sizeof(d1) << endl;
//结果是12字节,结论:成员变量存在类中,成员函数不是
原因:每个对象成员变量是不一样的,所以需要独立储存;而每个对象调用成员函数是一样的,需要放到共区域(代码段)
return 0;
}
class A2
{
public:
void f1()
{}
};
int main()
{
A2 aa1;
A2 aa2;
return 0;
}
只有成员函数的类大小是一字节,这一个字节不存储有效数据,仅用来占位,表示他被实例化过;
(5)this指针——编译器对函数对象指向的处理
1.this指针存在哪里? ——存在栈上,因为this是隐含的形参 /在vs下,存在ecx寄存器中
2.空指针问题:
(1)func()正常运行是因为空指针并没有被解引用,因为func()只是个普通的函数,不需要到指向的对象中去找什么东西,也就不需要解引用了。
(2)而第二个会去解引用,因为有"this->_year"等这些操作,而this指针接收的地址是空指针,所以空指针被解引用了。
(3)这个虽然有解引用*,但是func()并不是类里的成员变量,而是成员函数,而与成员变量无关的成员函数存在公共代码段中,所以不会报错,这里的指针是传给this指针的
(6)与C的区别
1.C语言数据与方法是分离的;C++的数据与方法是封装到一个类里面
2.C语言数据访问控制是自由的,不受限制;C++的数据可以控制访问方式(public),可以防止不能改的被改了
(7)静态成员变量与静态成员函数
class A
{
public:
A(int a = 0)
{
++count;
}
//静态成员函数——没有this指针
static int GetCount()
{
//_a++; //静态成员函数不能访问非静态成员,也是因为没有this指针
return count;
}
private:
//static并不是属于某个对象,而是属于所有对象,属于整个类
static int count; //声明
};
int A::count = 0; //定义,初始化
int main()
{
A aa;
cout<< A::GetCount() << endl; //类能找到count
cout << aa.GetCount() << endl; //类的实例也能找到count
return 0;
}
(8)匿名对象
class Solution
{
public:
Solution(int a = 0)
{
++count;
}
static int GetCount()
{
//_a++;
return count;
}
private:
static int count; //声明
};
int main()
{
Solution s; //普通对象
Solution s1(); //可能爆错,因为与函数太像
//匿名对象,生命周期只有这一行
Solution();
cout << Solution().GetCount() << endl; //一次性的,随用随释放,很方便
}
(9)友元——可以突破封装
1.友元函数(在类外面写,在类里面用)
2.友元类
(10) 内部类
这里的这个B类, 跟A的空间是独立的,只是受A的类域限制,B只是写在A的里面,但是实际上跟写在全局当中是没有区别的。
性质:内部类天生是外面类的友元:可以直接在B中访问A的私有对象
(11)关于编译器的优化
1.接受返回对象的时候,尽量用拷贝构造的方式接收,不要赋值接收。
A fun3()
{
A aa;
return aa;
}
int main()
{
//拷贝构造接收
A aa1 = fun3();
//赋值接收
A aa2;
aa2 = fun3();
}
2.函数中返回对象时,尽量返回匿名对象。
3.传参尽量用" const& "引用传参。
三.特殊的类函数
注意:无论什么类型的指针,都是内置类型,总之就是个指针(int* 与 stack* 都是内置类型)
1.构造函数
*当实例化后会自动调用构造函数
初始化链表(特殊的构造函数)
有三类变量必须在初始化列表进行初始化
1.const类型 const int _x;
2.引用类型 int& ref;
3.没有默认构造的自定义类型成员对象 B _bb;(但是B里没有bb对应的构造函数)
*初始化初始值的顺序与初始化顺序无关,与类的对象的声明顺序有关
class A
{
//1.那个对象调用构造函数,初始化列表就是它所有成员变量定义的位置
//2.不管是否显示在初始化列表,那么编译器每个变量都会初始化列表定义初始化
A() :_x(1), _a2(1),_ref(1),_bb(0)//优先初始化链表,其次是缺省值,最后还没定义就是随机值了
{
_a1++;
_a2++;
}
private:
int _a1 = 1;
int _a2 = 2;
const int _x;
int& _ref;
B _bb;
}
int main()
{
A aa; //这是个对象整体的定义
//而对于const这样类型的对象,必须要在定义的时候初始化,所以要给每个成员变量找一个定义的位置
return 0;
}
2.析构函数
class stack
{
public:
stack() //构造函数名字必须跟类名字相同
{
_a = nullptr;
_size = _capacity = 0;
}
stack(int n) //构成函数重载
{
_a = (int*)malloc(sizeof(int) * n);
_capacity = n;
_size = 0;
}
//这样写可以把初始化函数省掉,免得每次都忘
~stack()
{
free(_a);
_a = nullptr;
_size = _capacity = 0;
}
private:
int* _a;
int _size;
int _capacity;
};
//
int main()
{
stack st; //实例化后自动调用构造函数
stack stt(4); //当构造函数需要传参的时候这个样写
return 0;
}
3. 拷贝构造函数
这里是浅拷贝,像这里假如stack直接拷贝,那么a指针会直接拷贝给新的stack,这样两个栈都指向同一块空间,就出错了。
class date
{
public:
date(int y = 1, int m = 1,int d = 1) //构造函数
{
_year = y;
_month = m;
_day = d;
}
date(const date& d) //拷贝构造函数
{
_year = d._year;
_month = d._month;
_day = d._day;
}
date GetAfterXDay(int x) //返回类型是date ,所以return *this
{
//date tmp(*this);
date tmp = *this; //这里的*this就是d1,因为this指针指向d1;
//拷贝构造,先产生一个tmp的中间变量,这样就不会影响d1了
tmp._day = tmp._day + x;
while (tmp._day > GetMonthDay(tmp._year, tmp._month)) //如果天大于当前月,就进位
{
//进位
tmp._day = tmp._day - GetMonthDay(tmp._year, tmp._month);
tmp._month++;
if (tmp._month == 13)
{
tmp._year++;
tmp._month = 1;
}
}
//return *this;
//this是这个对象的指针,*this就是指的这个对象,而这里就是需要返回date类型,返回自己
return tmp;
}
void print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
int GetMonthDay(int year, int month)
{
int monthArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
{
return 29;
}
return monthArray[month];
}
private:
int _year;
int _month;
int _day;
};
int main()
{
date d1(2023, 2, 3);
date d2 = d1.GetAfterXDay(100);
d1.print();
d2.print();
return 0;
}
应用:日期加法:
class date
{
public:
date(int y = 1, int m = 1,int d = 1) //构造函数
{
_year = y;
_month = m;
_day = d;
}
date(const date& d) //拷贝构造函数
{
_year = d._year;
_month = d._month;
_day = d._day;
}
date GetAfterXDay(int x) //返回类型是date ,所以return *this
{
//date tmp(*this);
date tmp = *this; //这里的*this就是d1,因为this指针指向d1;
//拷贝构造,先产生一个tmp的中间变量,这样就不会影响d1了
tmp._day = tmp._day + x;
while (tmp._day > GetMonthDay(tmp._year, tmp._month)) //如果天大于当前月,就进位
{
//进位
tmp._day = tmp._day - GetMonthDay(tmp._year, tmp._month);
tmp._month++;
if (tmp._month == 13)
{
tmp._year++;
tmp._month = 1;
}
}
//return *this;
//this是这个对象的指针,*this就是指的这个对象,而这里就是需要返回date类型,返回自己
return tmp;
}
void print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
int GetMonthDay(int year, int month)
{
int monthArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
{
return 29;
}
return monthArray[month];
}
private:
int _year;
int _month;
int _day;
};
int main()
{
date d1(2023, 2, 3);
date d2 = d1.GetAfterXDay(100);
d1.print();
d2.print();
return 0;
}
4. 运算符重载
class date
{
public:
void print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
//d1==d2
//相当于 d1.operator==(d2)
bool operator==(const date& d) //因为是成员函数,所以只需要传别人的那个就行了
{
return _year == _year //this->_year==d._year (this指向d1,d就是d2)
&& _month == _month
&& _day == _day;
}
//d1<d2
bool operator<(const date& d)
{
if (_year < d._year) //这里实际是if (this->_year < d._year)
{
return true;
}
else if(_year==d._year&&_month<d._month)
{
return true;
}
else if (_year == d._year && _month < d._month && _day < d._day)
{
return true;
}
else
{
return false;
}
}
//d1<=d2
bool operator<=(const date& d)
{
return *this < d || *this == d;
}
//d1>d2
bool operator>(const date& d)
{
return !(*this <= d); //大于是小于等于的逻辑取反
}
//d1>=d2
bool operator>=(const date& d)
{
return !(*this < d); //大于等于是小于的逻辑取反
}
//d1!=d2
bool operator!=(const date& d)
{
return !(*this == d);
}
//只要实现小于等于和小于就可以了,其他的都可以直接取反
date& operator+=(int day) //日期加等天数
{
if (day < 0)
{
*this -= -day; //当d1 += -100时
return *this;
}
_day = _day + day;
while (_day > GetMonthDay(_year, _month))
{
_day = _day - GetMonthDay(_year, _month);
_month++;
if (_month == 13)
{
_year++;
_month = 1;
}
}
return *this;
}
//d1+100 日期加天数,由加等推加
date operator+(int day)
{
date tmp(*this); //拷贝一个tmp出来
tmp += day; //让tmp去加等,d1不改变
return tmp;
}
//d1+=100
date& operator+=(int day) //由加推加等
{
*this = *this + day;
return *this;
}
//++d1 先++,后使用
date& operator++()
{
*this += 1;
return *this;
}
//d1++ 先使用,后++
date operator++(int) //int为了构成重载
{
date tmp(*this);
*this += 1;
return tmp; //返回之前的值
}
//d1 -= (_day会改变)
date& operator-=(int day)
{
if (day < 0) //针对d1 -= -100
{
*this += -day;
return *this;
}
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
--_year;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
//d1-day
date operator-(int day)
{
date tmp = *this;
tmp -= day;
return tmp;
}
//--d1
date& operator--()
{
*this -= 1;
return *this;
}
//d1--
date operator--(int)
{
date tmp = *this;
*this -= 1;
return tmp;
}
//d1-d2
int operator-(const date& d)
{
date max = *this;
date min = d;
int flag = 1;
if (*this < d)
{
max = d;
min = *this;
flag = -1;
}
int n = 0;
while (min != max)
{
++min;
++n;
}
return n * flag;
}
private:
int _year = 1;
int _month = 1;
int _day = 1;
};
//全局函数(类外面的)的运算符重载写法
bool operator==(const date& d1, const date& d2) //有几个参数,就有几个操作符
{
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
int main()
{
date d1;
date d2;
cout << (d1 == d2) << endl; //最后也会被转换成调 d1.operator==(d2);
cout << (d1 < d2) << endl;
cout << d1.operator<(d2) << endl;
//自定义流输出
ostream& operator<<(ostream& out, const date & d) //这里的out相当于cout
{
out << d._year << "年" << d._month << "月" << d._day <<"日"<< endl;
return 0;
}
//自定义流输入
istream& operator>>(istream& in, date& d) //这里的in相当于cin
{
in >> d._year >> d._month >> d._day;
return in;
}
return 0;
}
要不要加const:
class date
{
public:
void print()const
//这里的const修饰的是*this,const只能加在外面,this类型变成const this*
//所以内部不改变成员变量的成员函数,最好都加上const,变成const this*
{
cout<<_year<<endl;
}
private:
int _year = 1;
int _month = 1;
int _day = 1;
};
int main()
{
return 0;
}
赋值运算符:
class date
{
public:
//赋值运算符 “=”
//d1=d2 ,把d2赋给d1
//这里的&:出了作用域,*this还在,如果是传值返回(date),那么会返回一个他的拷贝,会浪费空间
//而如果是引用(date&),就不会浪费空间,而且出了作用域*this还在,也不会丢
date& operator=(const date& d)
{
if (*this != d) //防止d1=d1这种
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
//返回*this是为了支持连续赋值,如:d3=d1=d2,从右往左赋值,d1=d2这个操作符的返回值是d1
}
private:
int _year = 1;
int _month = 1;
int _day = 1;
};
int main()
{
date d1;
date d2;
date d4(d1);
date d3 = d1; //这些是拷贝构造,d3是刚定义的
d2 = d1; //这个是赋值重载,两个都是已经定义好的对象
return 0;
}
cin与cout就是运算符重载:
5.默认成员函数
1.默认构造函数:
当你不写构造函数的时候,C++会自己编写一个无参数的默认构造函数,把成员变量都初始化为0,但是这里针对的成员变量只有自定义类型(node,queue,link等),而不会对基本(内置)类型初始化(int,char,等)。
//例:两个栈构成队列
class queue
{
public:
//默认生成了构造函数
void push(int x) //其他函数
{
//...
}
stack _a_stack;
stack _b_stack; //这里的a栈和b栈就会被初始化,*a=NULL,size=capacity=0
};
2.默认析构函数:
跟默认构造函数一样,C++会自己编写一个无参的默认析构函数,而这个函数只会对自定义类型(node,queue,link等)产生影响,而不会对改变(内置)类型。
注意:在C++11之后打了补丁,只要在类对象的声明的时候给缺省值,就可以和自定义类型一起被默认构造/析构函数初始化或改变了。
class date
{
public:
private:
//这就是在声明位置给缺省值
int _year = 1;
int _month = 1;
int _day = 1;
//如果你没写构造函数,那么就会用这个缺省值来给成员变量初始化
};
后定义的数据/函数,先析构
3.默认拷贝构造函数——(值拷贝)浅拷贝(一个字节一个字节的拷贝过来)
默认拷贝构造函数只可以处理内置基本数据类型,即你不自己编写,也可以实现(int,char等的拷贝)
class date
{
public:
date(int y = 1, int m = 1,int d = 1) //构造函数
{
_year = y;
_month = m;
_day = d;
}
//date(const date& d) //拷贝构造函数
//{
// _year = d._year;
// _month = d._month;
// _day = d._day;
//}
void print()
{
cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
date d1(2023, 2, 3);
date d2 = d1;
date d3(d1);
d1.print();
d2.print();
d3.print();
return 0;
}
4.默认赋值重载
取地址重载——自定义类型的取地址可以不应编写,直接用:
四.动态分配内存管理
1.C/C++内存分布
局部变量在栈上,malloc,new开的在堆,每次程序结束都会清理栈上的内存,不会清理堆上的内存。
2.C++动态内存管理
int main()
{
//int* p1 = (int*)malloc(sizeof(int));
int* p1 = new int; //C++的new是个操作符
int* pp1 = new int(0); //可以这样进行初始化 //如果这里是自定义类型,那么需要写构造函数
delete p1; //也是操作符,不需要加括号
//多个对象
//int* p2 = (int*)malloc(sizeof(int)*10);
int* p2 = new int[10]; //就开了10个int空间
int* pp2 = new int[10]{ 1,2,3,4 }; //初始化数组
delete[] p2;//要加方括号
//A* p3 = (A*)malloc(sizeof(A));
A* p3 = new A(1);
delete p3;
return 0;
}
new除了会开辟空间还会调用构造函数,而且new失败了不会返回空,会直接抛异常
delete会在销毁空间前调用析构函数
C++/C不要交叉使用
3.operator new与operator delete函数
4.定位new—— 对一块已有的空间进行初始化
int main()
{
A aa;
A* p1 = (A*)malloc(sizeof(A));
//对一块已有的空间进行初始化
new(p1)A(1); //A(1)是调用了构造函数
return 0;
}
五.模板初阶——泛型编程
1.模板的原理
//template<class T>
template<typename T> //没有“;”,用class和typename都行
void swap(T &x, T &y) //T是一个总定义的类型名
{
T tmp = x;
x = y;
y = tmp;
}
template<class X,class Y> //定义多个参数
int main()
{
int a = 1, b = 2;
swap(a, b);
double c = 1.1, d = 2.22;
swap(c, d);
//ab,cd调用的不是同一个函数,是模板实例化出的两个不同函数
return 0;
}
2.显示实例化
一个是手动类型转换,一个是隐式类型转换
eg:
template<class T>
class stack
{
public:
stack(int capacity = 4)
{
_a = new T[capacity];
_top = 0;
_capacity = capacity;
}
~stack()
{
delete[] _a;
_capacity = _top = 0;
}
private:
T* _a; //这里的T是下面的 int或double
size_t _top;
size_t _capacity;
};
int main()
{
stack<int> st1; //int
stack<double> st2; //double
//自定义类型必须要显示实例化
return 0;
}
六.其他库函数
1.大小写
大小写判断
#include<bits/stdc++.h>
//#include<cctype>
using namespace std;
int main()
{
char ch1='a';
char ch2='A';
if(islower(ch1)) cout<<"小写"<<endl;
if(isupper(ch2)) cout<<"大写"<<endl;
return 0;
}
大小写转换
#include<bits/stdc++.h>
using namespace std;
int main()
{
char ch1='a';
char ch2='A';
char ch3=toupper(ch1);
cout<<ch3<<endl;
cout<<toupper(ch1)<<endl;
cout<<tolower(ch2)<<endl;
return 0;
}
如果是其他字符则不进行操作
2.二分查找
int main()
{
vector<int> nums={1,3,5,7,9};
int target=5;
bool binary_search=(nums.begin(),nums.end(),target); //二分查找函数
return 0;
}
3.sort排序
4. unique去重
注意:因为unique只能去重 相邻元素,所以必须先排序