1、const修饰类的成员函数
有如下场景
class A
{
public:
void print()
{
printf("%d\n", _a);
}
private:
int _a;
};
void func(const A& obj)
{
obj.print();
}
注意在func内对象obj调用print是会发生编译错误的,这是因为obj被const修饰,是一个只读的对象,obj.print()执行时,将obj的地址传给print,也就是print的第一个参数this,而这个this没有被const修饰,*this是可读可写的,发生了权限的扩大,这是不被编译通过的。
怎么解决呢?在print函数后加上const关键字,这个关键字虽然表面上用于修饰成员函数,本质上是为了修饰成员函数的第一个参数this,构成隐式的const *this,让*this只能读不能写,同时也外部的const A& obj相匹配。下面这种写法是不会发生报错的。
class A
{
public:
void print()const
{
printf("%d\n", _a);
}
private:
int _a;
};
void func(const A& obj)
{
obj.print();
}
我们的自定义类型的成员函数,如果不对自身成员变量进行写操作,都推荐在函数后边加上const关键字,这样外部的不论是带const还是不带const修饰的变量都可以调用这个成员函数。
2、初始化列表
我们自定义类型的初始化,可以调用自己定义的普通的构造函数或者拷贝构造函数(如果不自己实现,编译器也会默认生成,不过是浅拷贝)。如下。
class letter
{
public:
letter(int a, int b, int c)
{
_a = a;
_b = b;
_c = c;
}
letter(const letter& L)
{
_a = L._a;
_b = L._b;
_c = L._c;
}
private:
int _a;
int _b;
int _c;
};
除此之外c++还为我们提供了另一种写法,初始化列表。
class letter
{
public:
letter(int a, int b, int c)
:_a(a), _b(b), _c(c)
{}
letter(const letter& L)
:_a(L._a), _b(L._b), _c(L._c)
{}
private:
int _a;
int _b;
int _c;
};
这种写法除了提升我们代码的可读性。
需要注意的几点:
有三类成员变量是必须在初始化列表里进行初始化的:(1)引用成员变量(没错,成员变量可以是引用);(2)const修饰的成员变量;(3)没有默认构造函数的自定义类型成员变量(默认构造函数是指不需要传参的构造函数)。为什么?以上三类成员变量共性都是在定义时必须初始化,利用初始化列表就可以在定义时初始化,而在函数体内的动作属于赋值,已经是定义之后的动作了。
成员变量初始化的顺序与初始化列表里的顺序无关,取决于成员变量在类内声明的顺序。有如下例子。
class A
{
public:
A(int a)
:_a1(a),_a2(_a1)
{}
void print()
{
printf("%d %d\n", _a1, _a2);
}
private:
int _a2;
int _a1;
};
int main()
{
A aa(1);
aa.print();
return 0;
}
类A内的成员变量_a2和_a1先后声明,当我们构造对象aa时,先用_a1初始化成员变量_a2,此时_a1是随机值,因此_a2被初始化为随机值,随后用1初始化_a1,因此_a1为1。代码运行结果如下。
3、explicit关键字修饰成员函数
我们实例化一个类是有这种写法的。
class A
{
public:
A(int a)
:_a1(a)
{}
private:
int _a1;
};
int main()
{
A aa = 1;
return 0;
}
在初始化aa时,右值1发生了隐式类型转化,生成了一个A类型的临时对象,我们姑且将其命名为tmp,随后用tmp初始化了aa。
如果我们不希望发生隐式类型转换,可以在类的构造函数前加上explicit关键字。
这样编译器会提示不存在将int转换为A的适当构造函数,编译不通过。
4、static关键字修饰成员变量
class A
{
public:
explicit A(int a)
:_a1(a)
{}
//private:
int _a1;
static int _a2;
};
int A::_a2 = 1;
int main()
{
printf("%p\n", &A::_a2);
printf("%d\n", sizeof A);
return 0;
}
这个static变量必须在外部定义,类内的static int _a2;语句只是声明。定义了之后这个静态变量的生命周期不依赖与对象的创建与销毁。它不是在存储在栈上的,而是存储在数据段上的,因此不论是否创建对象,它都是存在的,我们可以在不创建对象的情况下取到这个静态变量的地址(前提是这个静态变量的访问限定符时public,如果是private或者protected就不行)。所以可以理解为这个静态变量是属于类的,而不是属于对象的。当我们通过sizeof操作查看A类型的大小时,可以看到是4,而不是8,进一步说明自定义类型内声明的静态变量不会放在每一个对象内。我们可以通过创建对象访问这个静态变量,注意不论创建多少个对象,访问这个静态变量访问的都是同一块空间。上面一段代码的运行结果如下。
5、static关键字修饰成员函数
成员函数前面如果加上了static关键字,那么参数里将没有隐含的this指针,即我们的对象在调用这个函数时,没有将自己的地址传给static函数,这个函数自然就不能访问我们类内的非static成员变量以及非static成员函数(可以访问static变量和static成员函数,因为static的存在不依赖于对象的创建)。
总结一下static关键字修饰的成员变量和成员函数,与普通的静态变量和全局函数没有本质的差别,存储的空间都是相同的,只是通过类中的访问限定符来收到了类域的访问限制。