默认成员函数
即使在写类的时候,用于不写编译器也会自动生成的函数,(或者说叫没有显式显示)。
下面介绍下
构造函数
c++的类会具有构造函数。名字与类名相同。如果你没写构造函数,编译器会生成一个默认构造函数。(里面实际上一般没什么内容,主要是初始化对象)
特点:无返回值,可以重载,会在对象实例化(动态内存开辟后)自动由编译器调用一次,并且在整个对象声明周期只调用一次。
默认构造函数:全缺省值和无参的构造函数都成为默认构造函数。
因此默认给构造函数只能存在一个,否则就有二义性问题
初始化列表:构造函数后面,会有一个初始化列表,主要用于定义声明的在该变量里面的对象。其定义的顺序取决于你声明的顺序,不取决于你在初始化列表出写的顺序,一般来说初始化列表顺序和声明顺序一致是比较合适的。
注意:因为是可以重载构造函数的,所以不能同时存在全缺省值和不给值的构造函数,否则就会有二义性。
class Date{
public:
Date(int a = 1,int b = 0)//例如这个date就是构造函数
//初始化列表,在构造函数的作用域前加一个冒号
:_a = a//彼此之间用逗号隔开
,_b = b//如果这里没有写b,c++那就默认的是给
//_b = 10
//也就是初始化成声明时给的缺省值
//初始化列表按照声明的顺序一次定义声明的内置类型
//也就是说即使初始化列表一个变量定义你都不写,这个编译器也会帮你完成,完成变量得定义。
{
//在这个函数提里面再进行操作就是赋值了
}
private:
int _a;
int _b = 10;//c++11支持给缺省值,这个缺省值就是在初始化列表里面默认初始化的值
}
C++在构造时有一种特殊的现象,就是隐式类型转换,会将你给的合适的参数构造成一个临时对象,再用这个临时对象进行拷贝构造。
这和C++11的列表初始化有区别(注意,不是指的初始化列表)
class A{
public :
A(int a)
:_a(a)
{
cout<<"A(int a)"<<endl;
}
A(const A& a1)
:_a(a1._a)
{
cout<<"A(const A& a1)"<<endl;
}
~A()
{
cout<<"~A"<<endl;
}
private :
int _a = 0;
};
int main()
{
A aa(1);//初始化构造
A aaa = 2;//c++的一种隐式类型转换,先对1调用A的构造函数,生成一个临时对象,调用拷贝构造把临时对象赋值给aaa,但是编译器会进行优化直接用2进行构造
//证据:
//A& aaa2 = 1;//这里编译器会报错,原因是一个临时对象(不可修改)的赋值给引用,权限就被放大了,
const A& aaa2 = 1;//这里加上const就能编译过去
//假如不想隐式类型转换发生, explicit加在构造函数前就可以,就不会允许这种转换
return 0;
}
看看如下的例子,理解一下C++的隐式类型转换,将其和C++11的列表初始化区分。
- 注意,C++11才支持多个参数的隐式类型转换
class C{
public:
C(int a,int b)
{
}
};
int main()
{
C c = {1,2};
C cc[2] = {{1,2},{3,4}};
return 0;
}
如果你想不要隐式类型转换,可使用explicit关键字。 explicit
拷贝构造函数
拷贝构造函数:是一种特殊的构造函数,会在类进行拷贝时调用(或者调用其进行拷贝),如果定义类时没用定义,那么编译器会自动生成。
class Date{
public:
Date(const Date & st)//拷贝构造参数必须是类型的引用,否则引发无穷递归
//实际编译器会报错,防止这点
{
}
~Date()
{
cout<<"~Date()"<<endl;
}
private:
int a = 10;
};
//以下两种写法都是同样的效果,都表示对d1的浅拷贝。
Date d1;
Date da(d1);
Date db = d1;
上面提到了浅拷贝,编译器默认生成的拷贝构造,只是纯粹的浅拷贝,如果类里面有动态内存开辟的情况,需要去自己写拷贝构造函数完成深拷贝。
赋值运算符重载
有下面的写法
class A{
};
int main()
{
A a;
A b = a;//这里是对b的初始化,调用了拷贝构造。
A c;
c = a;//这里这个等号实际就是赋值运算符重载
}
这里值得关注的点就是拷贝构造和赋值重载时不一样的,注意运用。
其实一般的变量和赋值的时候确实差不很多。但是以下情况就不一样了
class stack{
stack(int capacity = 10,int sz = 0)
:capacity(capacity)//初始化列表
,sz(sz)
,a((int*)malloc(sizeof(int)*10))
{
}
stack(stack& ST)//这种情况拷贝构造就需要深拷贝
{
this->a = (int*)malloc(ST.capacity*sizeof(int));
this->capacity = ST.capacity;
this->sz = ST.sz;
}
stack operator=(const stack& ST)
{
//赋值的话,就需要考虑是否重开空间,是否装的下原来的值。
//能装的下就不重开,把值拷贝一份
//转不下就要delete[] a,重新开空间赋值
this->capacity = ST.capacity;
this->sz = ST.sz;
return *this;
}
private:
int *a ;
int capacity;
int sz;
};
析构函数
c++的析构函数就比较简单,没有返回值,参数,会造对象销毁时自动调用,清理下资源空间等。
1.析构函数名是在类名前加上字符 ‘~’。
2.无参数无返回值类型。
3.一个类只能有一个析构函数。若未显式定义;系统会自动生成默认的析构函数。注意:析构函数不能重载
4.对象生命周期结束时,C++编译系统系统自幼调用析构函数.
取地址操作符的重载
这里面涉及const指针,加在函数括号后面,用来修饰this指针,表示const (*this)。
class B{
public:
const B* operator&()const//当是const型,就匹配这个函数
{
return this;
}
B* operator&()//非const型取地址就返回这个
{
return this;
}
};
C++的的成员函数
C++的成员函数分为静态成员函数和非静态成员函数。
非静态成员函数
非静态成员函数:其参数带了一个隐式的this指针。这也是为什么在非静态成员函数可以直接访问类的实例化对象的成员。
const 修饰的非静态成员函数,实际const修饰的成员函数修饰的就是this指针,也就是说你使用的这个函数不能对类进行修改,Test且在const成员函数里面,也不能使用非const的成员函数。
如下面的例子;你不给自定义hash函数加const的话,编译器就会报错,原因就是C++的调用hash函数时,调用的时const成员函数调用的。
#include <bits/stdc++.h>
using namespace std;
class A{
public:
bool operator==(const A &a)
{
return 1;
}
};
struct myhash{
public:
int operator()(A a)
{
return 1;
}
};int main()
{
std::unordered_set<A,myhash> a;
a.insert(A());
return 0;
}
编译器就可能出现如下报错:
static assertion failed: hash function must be invocable with an argument of key type
静态成员函数
静态成员函数时没有this指针的,因此时不能调用C++的非静态成员变量和非静态成员函数,因为是没有this指针的。
静态成员函数,与静态成员变量,都是不需要成员指针进行访问的,而是直接封在类域里面,如果公有在类外使用域作用限定符就可以访问。
- 静态成员函数不能直接访问非静态成员函数和非静态成员变量
- 非静态成员函数可以访问静态成员函数和静态成员变量。
成员函数的取地址和封装进function
显示成员函数的地址
- 举这个例子是为了在使用回调时,该如何写明函数地址。
class A{
public:
void func(){}
};
int main() {
void (A::*p)() = (&A::func);
//打印成员函数地址
std::cout<<std::showbase << std::hex <<(void*)p<<"\n";
return 0;
}
在使用C++11所提供的bind来封装成员函数时,非静态成员需要传入对象的指针,静态成员函数就不需要。
#include <bits/stdc++.h>
class A{
public:
void func(){}
};
int main() {
A a;
auto func = std::bind(A::func,&a);
return 0;
}