代码模板一:
bool Nocase::operator()(const string& x,const string& y)const{}
1、指针和引用:
(1)使用:
string& x
中,此处&
不是取地址,表示引用,引用符写在变量类型后面。作用看下面例子:
直接使用int
型作为参数:
//普通int 计算
#include <iostream>
using namespace std;
void func( int a ,int b ,int c,int d)
{
d=a+b+c; //改变的是局部变量 d 的值
}
int main() {
int p;
func(1,2,3,p);
cout<<p;
return 0;
}
//输出结果 0
//这样计算修改的是局部变量d的值,作为外部变量的p则不会被修改
指针作为参数运算:
//指针计算
#include <iostream>
using namespace std;
void func( int a ,int b ,int c,int *d)
{
*d=a+b+c;
}
int main() {
int d=1;
int *p= &d;
func(1,2,3,p);
cout<<d;
return 0;
}
//输出结果 6
//函数中的d指向p的地址
引用计算:
//引用计算
#include <iostream>
using namespace std;
void func( int a ,int b ,int c,int &d)
{
d=a+b+c;
}
int main() {
int p;
func(1,2,3,p);
cout<<p;
return 0;
}
//输出结果 6
//引用相当于形参作为实参的一个别名,实际是使用实参p加入计算
所以想要使用函数修改全局的变量,需要使用指针或引用。引用通过形参来修改变量实参,而指针则是直接修改实参地址指向。当有大量的数据运算时,使用引用代替指针可以避免大量的值拷贝。
参数中使用引用后,如果不希望改变变量的值,可以结合const
使用。
(2)变量引用:
类似指针与指针函数,引用也是一种值传递的方式。
以下两段代码来自:添加链接描述
int main() {
int a = 5;
int& b = a;
cout << &a << endl; // 取a的地址
cout << &b << endl; // 取b的地址
return 0;
}
//输出:
// 0x61fe14
// 0x61fe14
此处表示创建一个对a
的引用b
,b
不占用内存,等于a
的一个别名,因此对a
和b
进行取址时,二者地址相同。
int main(int argc, const char * argv[]) {
int a = 5;
int& b = a; //a的别名
int c = 20;
b = c;
cout << a << endl; // 输出a修改后的值
cout << b << endl; // 输出b修改后的值
return 0;
}
//输出:
// 20
// 20
(3)函数引用返回值:
以下来自:https://www.jianshu.com/p/4f0a892c2f89
- 返回引用:当函数返回引用类型的时候,没有复制返回值,而是返回对象的引用(即对象本身)。实际上是一个变量的内存地址,既然是内存地址的话,那么肯定可以读写该地址所对应的内存区域的值,即就是“左值”,可以出现在赋值语句的左边。
函数引用可直接使用全局变量:
#include <iostream>
using namespace std;
int aa=11;
int &a()
{
int aaa=1;
cout<<"aa="<<aa<<" "<<"aaa="<<aaa;
}
int main()
{
a();
return 0;
}
//输出 aa=11 aaa=1
语法:
类型 &函数名(形参列表){ 函数体 }
- 不使用引用时:
#include <iostream>
using namespace std;
int add(int aa){
return aa+1;
}
int main(){
int a=1;
cout<<add(a);
}
add
函数中产生了临时变量aa
。
- 使用引用时:
#include <iostream>
using namespace std;
int& add(int& a){
a=a+1;
return a;
}
int main(){
int a=1;
cout<<add(a);
}
引用项都相当于一个别名,真正存在的只有变量a
。比如,如下操作:
#include <iostream>
using namespace std;
int& add(int& a){
a=a+1;
return a;
}
int main(){
int a=1;
cout<<&a<<endl;
cout<<&add(a)<<endl;
}
运行结果:
可以得出,实际上这个引用函数的内存是不存在的,自始至终存在的只有a
。
函数只要有形参,只要有返回值,都需要申请一个临时内存来存储,而引用就不需要。就像输出的内存地址一样,不管是引用变量,还是引用返回值的函数,都是一个别名而不占用内存。把函数中的元素都替换成引用参数和返回值引用,在大量的数据中使用引用来替换指针,能尽可能的缩减内存占用。
(4)右值引用:
上面的引用中都是作用于左值,当想要作用于右值(即作用于一个具体的变量值上时),使用int &a = 1;
编译是不能通过的,使用右值引用的方法为双&
符号号:
#include <iostream>
using namespace std;
int main()
{
int &&a = 11;
cout<<a;
return 0;
}
//输出 11
2、重载:
资料来自:https://www.runoob.com/cplusplus/cpp-overloading.html
operator()(const string& x,const string& y)
表示对运算符()
进行重载。
(1)函数重载:
在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。
#include <iostream>
using namespace std;
class printData
{
public:
void print(int i) {
cout << "整数为: " << i << endl;
}
void print(double f) {
cout << "浮点数为: " << f << endl;
}
void print(char c[]) {
cout << "字符串为: " << c << endl;
}
};
int main(void)
{
printData pd;
// 输出整数
pd.print(5);
// 输出浮点数
pd.print(500.263);
// 输出字符串
char c[] = "Hello C++";
pd.print(c);
return 0;
}
输出:
整数为: 5
浮点数为: 500.263
字符串为: Hello C++
(2)运算符重载:
对运算符重载,运算符就可以具备一些特殊功能。重载的运算符是带有特殊名称的函数,函数名是由关键字 operator
和其后要重载的运算符符号构成的。
#include <iostream>
using namespace std;
class Box
{
public:
double getVolume(void)
{
return length * breadth * height;
}
void setLength( double len )
{
length = len;
}
void setBreadth( double bre )
{
breadth = bre;
}
void setHeight( double hei )
{
height = hei;
}
// 重载 + 运算符,用于把两个 Box 对象相加
Box operator+(const Box& b)
{
Box box;
box.length = this->length + b.length;
box.breadth = this->breadth + b.breadth;
box.height = this->height + b.height;
return box;
}
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
int main( )
{
Box Box1; // 声明 Box1,类型为 Box
Box Box2; // 声明 Box2,类型为 Box
Box Box3; // 声明 Box3,类型为 Box
double volume = 0.0; // 把体积存储在该变量中
// Box1 详述
Box1.setLength(6.0);
Box1.setBreadth(7.0);
Box1.setHeight(5.0);
// Box2 详述
Box2.setLength(12.0);
Box2.setBreadth(13.0);
Box2.setHeight(10.0);
// Box1 的体积
volume = Box1.getVolume();
cout << "Volume of Box1 : " << volume <<endl;
// Box2 的体积
volume = Box2.getVolume();
cout << "Volume of Box2 : " << volume <<endl;
// 把两个对象相加,得到 Box3
Box3 = Box1 + Box2;
// Box3 的体积
volume = Box3.getVolume();
cout << "Volume of Box3 : " << volume <<endl;
return 0;
}
输出:
Volume of Box1 : 210
Volume of Box2 : 1560
Volume of Box3 : 5400
代码中的Box3 = Box1 + Box2;
相当于执行了 Box3 = Box1.operator+(Box2);
,即+
号重载后的运算。
下面是可重载的运算符列表:
类型 | 符号 |
---|---|
双目算术运算符 | + (加),- (减),* (乘),/ (除),% (取模) |
关系运算符 | == (等于),!= (不等于),< (小于),> (大于),<= (小于等于),>= (大于等于) |
逻辑运算符 | || (逻辑或),&& (逻辑与),! (逻辑非) |
单目运算符 | + (正),- (负),* (指针),& (取地址) |
自增自减运算符 | ++ (自增),-- (自减) |
位运算符 | | (按位或),& (按位与),~ (按位取反),^ (按位异或),<< (左移),>> (右移) |
赋值运算符 | = , += ,-= , *= ,/= , % = , &= , |= , ^= , <<= , >>= |
空间申请与释放 | new , delete , new[ ] , delete[] |
其他运算符 | () (函数调用),-> (成员访问),, (逗号),[] (下标) |
下面是不可重载的运算符列表:
类型 | 运算符 |
---|---|
成员访问运算符 | . |
成员指针访问运算符 | .* ,->* |
域运算符 | :: |
长度运算符 | sizeof |
条件运算符 | ?: |
预处理符号 | # |
3、函数最后的const:
operator()(const string& x,const string& y)const
位于函数参数前的const
表示这个参数不能被函数体修改,只读。
位于整个函数后的const
表示禁止函数内部的成员在函数体中被修改,只读。
4、operator作用:
其一就是操作符的重载,上面已经写过了。还有就是通过operator
完成隐式转换。
转换格式:
operator 类型T () //T是要转换到的类型。
operator double() const ; //当前所属类隐式的转换为double()
5、补充:
(1)形参和实参:
形参:函数名或函数体中的参数,在函数被调用时,用于接收实参的值。
实参:是在调用时传递给函数的参数。 实参可以是常量、变量、表达式、函数等。无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值, 以便把这些值传送给形参。
如:
#include <iostream>
using namespace std;
void add(int& b); //b 形参
int main(){
int a=1;
add(a); //a 实参
cout<<a;
return 0;
}
void add(int &b)
{
b+=1;
}
想要通过形参来改变实参,就需要用到引用。
(2)深拷贝和浅拷贝:
浅拷贝:创建一个指向被拷贝对象内存地址的指针,类似创建文件的快捷方式。源内存地址内容发生改变时,浅拷贝指针指向内容也发生改变。
深拷贝:创建一个指针,并申请新的内存空间存放深拷贝对象的所有内容,指针指向新开辟的内存。类似文件的复制,源文件删除时,复制的文件不受影响。
代码模板二:
#include <iostream>
using namespace std;
int add(int a,int b)
{
return a+b;
}
using addFunc=int(*)(int,int);
//定义一个未命名的函数指针类型,指向int (int,int); 指针的别名为addFunc
int main(){
addFunc dd = add; //dd为函数指针,函数指针指向函数 add
int j = dd(1,2); //使用指针代替函数
cout<<"j="<<j;
}
//输出 j=3
int(*)(int,int);
表示定义了一个int
型的,未命名的函数指针,它的形参为两个int
型。using addFunc
表示为这个函数指针定义的别名为addFunc
。addFunc dd = add;
使用别名声明一个上述类型的指针dd
,相当于int(*dd)(int,int);
,dd
指向add
函数。dd(1,2);
由于dd指向add,实际执行的是add(1,2);
。
1、using和typedef:
typedef别名:部分参考自:http://c.biancheng.net/view/298.html
(1)为基本类型创建别名:
typedef int i;
i a = 1;
//或
typedef double dou;
dou b = 1.01;
(2)为结构体创建别名:
typedef struct tagPoint
{
double x;
double y;
double z;
} Point;
tagPoint
为结构体名,Point
为别名,但是Point
别名在使用的时候还是要与tagPoint
一样的。
还可以使用指针来定义:
struct tagNode
{
char *pItem;
struct tagNode *pNext;
};
typedef struct tagNode *pNode;
(3)为数组定义简洁的类型名称:
使用方法与其他的不太一样:
typedef int INT_ARRAY_100[100];
INT_ARRAY_100 arr; //arr是100个元素的数组
(4)为指针定义简洁的名称:
typedef char* PCHAR;
PCHAR pa;
接着即可使用PCHAR
定义char
型指针,这里等同于char* pa
。
(5)用来定义函数指针:
与数组类似,别名会取自自己定义的指针名:
typedef void (*pfun)(void) //类型别名
pfun a;
即可使用pfun
快速定义一个返回值和形参都为void
的函数指针了。
参考资料中还有一个函数指针的指针的别名声明,记录一下:
//直接声明
int *(*a[5])(int,char*);
//typedef别名声明
typedef int *(*PFun)(int,char*);
// 使用定义的新类型来声明对象,等价于int*(*a[5])(int,char*);
PFun a[5];
(6)陷阱:
在函数形参中使用typedef
的别名时,如果使用:
typedef char* PCHAR;
int strcmp(const PCHAR,const PCHAR);
程序运行下来,const PCHAR
的效果并不是const char*
,而是char* const
。
char* const p
: 定义一个指向字符的指针常数
const char* p
: 定义一个指向字符常数的指针
要得到想要的结果,如下定义:
typedef const char* PCHAR;
int strcmp(PCHAR, PCHAR);
using别名:参考:https://blog.youkuaiyun.com/shift_wwx/article/details/78742459
(1)基本数据类型别名:
using i=int;
using dou=double;
i a=1;
dou b=1.11;
(2)结构体:
using Point = struct tagPoint
{
double x;
double y;
double z;
} ;
Point p;
以及:
struct tagNode
{
char *pItem;
struct tagNode *pNext;
};
using Point = struct tagNode;
(3)数组别名:
using arr= int [10];
int main(){
arr a;
a[1] = 10;
}
(4)char型指针别名:
using PCHAR = char* ;
int main(){
PCHAR pa;
pa = "asss";
cout<<pa;
}
(5)函数指针:
int add(int a,int b)
{
return a+b;
}
using addFunc=int(*)(int,int);
int main(){
addFunc dd = add;
int j = dd(1,2);
cout<<"j="<<j;
}
2、函数指针和指针函数:
(1)函数指针:
即指向和返回是函数的一个指针,函数指针在使用时,可以使其指向已定义的函数,using
声明函数指针上面有了,下面我又写了直接声明函数指针和typedef
声明函数指针的方式。
函数指针直接定义:
#include <iostream>
using namespace std;
int add(int a,int b)
{
return a+b;
}
int (*tt)(int,int);
int main(){
tt=add;
int k=tt(3,3);
cout<<"k="<<k;
}
//输出 k=6
typedef
定义:
#include <iostream>
using namespace std;
int add(int a,int b)
{
return a+b;
}
typedef int (*pfun)(int ,int);
pfun aaaa;
int main(){
aaaa=add;
int k=aaaa(3,3);
cout<<"k="<<k;
}
(2)指针函数:
即返回值为指针的函数,与函数指针含义相反。下列代码来自:https://blog.youkuaiyun.com/luoyayun361/article/details/80428882
typedef struct _Data{
int a;
int b;
}Data;
//指针函数
Data* f(int a,int b){
Data * data = new Data;
data->a = a;
data->b = b;
return data;
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//调用指针函数
Data * myData = f(4,5);
qDebug() << "f(4,5) = " << myData->a << myData->b;
return a.exec();
}
代码模板三:
System::Math::Sqrt()
//使用成员函数,相当于
System.Math.Sqrt()
int *p1 = static_cast<int*>( malloc(10 * sizeof(int)) );
申请的10倍int
长度的void
指针转为int
指针。
1、单/双冒号用法:
以下的内容来自:https://blog.youkuaiyun.com/dajiadexiaocao/article/details/81776364
(1)单冒号:
- 构造函数后面的冒号起分割的作用,是类给成员变量赋值的方法,初始化列表,更适用于
const
成员变量。
struct foo{
foo()
:Node("node"), _temp(temp)
{}
};
public:
和private:
后面的冒号,表示后面定义的所有成员都是公有或私有的,直到下一个public:
或private:
出现为止。private:
为默认处理。
public:
int foo(int a){}
private:
int a;
std::string s;
- 类名冒号后面的是用来定义类的继承。
class 派生类名 : 继承方式 基类名
{
派生类的成员
};
继承方式:public
,private
和protected
,默认处理是public
。
- 表示机构内位域的定义(即该变量占几个bit空间)。
typedef struct _foo{
int a:4;
int b;
};foo
(2)双冒号:
- 表示 “域操作符”,声明了一个类
A
,在类A
中声明一个成员函数void foo()
,但没有在类A
的声明中给出函数foo
的定义,那么在类外定义foo
时,就要写成
void A::foo(int a,int b){cout<<a<<b;}
类似于Qt
中定义槽函数功能:
void MainWindow::on_PushButton_clicked(){}
- 表示引用成员函数和变量,作用域成员运算符。引用某个功能库下的函数或变量:
System::Math::Sqrt()
//相当于
System.Math.Sqrt()
2、static_cast和dynamic_cast用法:
以下的内容来自:https://blog.youkuaiyun.com/u014624623/article/details/79837849
(1)static_cast:
用法:static_cast< typename>(expression)
new_type为目标数据类型,expression为原始数据类型变量或者表达式
static_cast
相当于传统的C语言
里的强制转换,该运算符把expression
转换为new_type
类型,用来强迫隐式转换如non-const
对象转为const
对象,编译时检查,用于非多态的转换,可以转换指针及其他,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:
- 用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。
进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。 - 用于基本数据类型之间的转换,如把
int
转换成char
,把int
转换成enum
。 - 把空指针转换成目标类型的空指针。
- 把任何类型的表达式转换成
void
类型。
注意:static_cast
不能转换掉expression
的const
、volatile
、或者__unaligned
属性。
基本数据类型转换:
char a = 'a';
int b = static_cast<int>(a);//正确,将char型数据转换成int型数据
double *c = new double;
void *d = static_cast<void*>(c);//正确,将double指针转换成void指针
int e = 10;
const int f = static_cast<const int>(e);//正确,将int型数据转换成const int型数据
const int g = 20;
int *h = static_cast<int*>(&g);//编译错误,static_cast不能转换掉g的const属性
类上行和下行转换:
class Base
{};
class Derived : public Base
{}
Base* pB = new Base();
if(Derived* pD = static_cast<Derived*>(pB)) //转派生类
{}//下行转换是不安全的(坚决抵制这种方法)
Derived* pD = new Derived();
if(Base* pB = static_cast<Base*>(pD)) //转为基类
{}//上行转换是安全的
(2)dynamic_cast:
用法:dynamic_cast< typename >(expression)
new_type为目标数据类型,expression为原始数据类型变量或者表达式
转换方式:
dynamic_cast< type* >(e)
type
必须是一个类类型且必须是一个有效的指针dynamic_cast< type& >(e)
type
必须是一个类类型且必须是一个左值dynamic_cast< type&& >(e)
type
必须是一个类类型且必须是一个右值
e
的类型必须符合以下三个条件中的任何一个:
e
的类型是目标类型type
的公有派生类e
的类型是目标type
的共有基类e
的类型就是目标type
的类型。
如果一条dynamic_cast
语句的转换目标是指针类型并且失败了,则结果为0
。如果转换目标是引用类型并且失败了,则dynamic_cast
运算符将抛出一个std::bad_cast
异常(该异常定义在typeinfo
标准库头文件中)。e
也可以是一个空指针,结果是所需类型的空指针。
dynamic_cast
主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换(cross cast
)。
在类层次间进行上行转换时,dynamic_cast
和static_cast
的效果是一样的;在进行下行转换时,dynamic_cast
具有类型检查的功能,比static_cast
更安全。dynamic_cast
是唯一无法由旧式语法执行的动作,也是唯一可能耗费重大运行成本的转型动作。
- 指针类型
举例,Base
为包含至少一个虚函数的基类,Derived
是Base
的共有派生类,如果有一个指向Base
的指针bp
,我们可以在运行时将它转换成指向Derived
的指针,代码如下:
if(Derived *dp = dynamic_cast<Derived *>(bp)){
//使用dp指向的Derived对象
}
else{
//使用bp指向的Base对象
}
值得注意的是,在上述代码中,if
语句中定义了dp
,这样做的好处是可以在一个操作中同时完成类型转换和条件检查两项任务。
- 引用类型
因为不存在所谓空引用,所以引用类型的dynamic_cast
转换与指针类型不同,在引用转换失败时,会抛出std::bad_cast
异常,该异常定义在头文件typeinfo
中。
void f(const Base &b){
try{
const Derived &d = dynamic_cast<const Base &>(b);
//使用b引用的Derived对象
}
catch(std::bad_cast){
//处理类型转换失败的情况
}
}