成员属性和成员函数分开储存
在c++中,成员属性和成员函数是分开储存的,只有成员属性属于对象。但在静态成员属性是不属于对象的,它和成员函数一样属于一个类而不属于某个对象。
静态成员属性在编译阶段分配内存,类内声明,类外初始化,并且所有对象共享同一个静态成员属性。
我们创建一个空类和一个测试用例
class person
{
};
void test01()
{
person p;
cout << "size of (p) = " << endl;
}
运行test01后我们可以知道空对象在内存中分配的空间为一字节,这是因为即使是空对象,不同的空对象也不能有相同的内存地址,所以编译器会分配一个字节的空间区分不同的空对象。
在类中添加一个非静态成员属性
class person
{
private:
int age;
};
这时再次运行test01可以得到p对象在内存中占用的内存为4个字节,即一个整型的大小。因为不同的对象的属性也是不同的,所以类中的属性属于每一个对象;
在类中添加一个静态成员属性
class person
{
private:
int age;
static int ID;
};
int person::ID = 10;
这时运行test01发现p的大小未发生改变,这是因为静态成员属性并不是属于某个对象的,而是属于整个类,所以也不会改变对象的大小。
为类中添加一个行为
class person
{
private:
int age;
static int ID;
public:
int showAge()
{
return age;
}
};
int person::ID = 10;
同样的,运行test01后发现p的内存并未改变,因为行为同样是属于一个类的,而不是属于某个对象。编译器会通过某些方式来区分哪一个对象正在调用成员函数,这与this指针有关。
this指针
引言
通过前面的学习我们知道,每一个非静态成员函数只会生成一份函数实例,在调用时会自己区分哪一个对象在调用它。这一步骤的实现依靠的就是this指针。
this指针的概念
this指针是一种特殊的指针,this指针包含在每一个非静态的成员函数中,它指向的是正在调用函数的对象。
this不需要我们定义,直接使用即可。
this指针的作用
避免名称冲突
class number
{
public:
number(int num)
{
num = num;
}
int num;
};
void test01
{
number n1(10);
cout << n1.num << endl;
}
如果我们运行test01会发现编译器并没有正确的输出10,而是输出了一个随机数。
这是因为编译器认为我们的有参构造函数中的形式参数num和等号左边的num即对象中的num是相同的,所以我们并没有为对象中的num正确的赋值。
使用this指针就可以解决这个问题;
class number
{
public:
number(int num)
{
*this->num = num;
}
int num;
};
将代码做以上修改之后test01就可以正确的输出。
在类的非静态成员函数中返回对象本身
我们在number对象中加入add操作
class number
{
public:
number(int num)
{
num = num;
}
void add(const number &p)
{
*this.num += p.num;
}
int num;
};
void test02()
{
number n1(10);
number n2(20);
n2.add(n1);
cout << n2.num << endl;
}
这时运行test02,可以正确的输出相加结果为30;
如果我们想通过链式操作进行连加将测试用例02改为:
void test02()
{
number n1(10);
number n2(20);
n2.add(n1).add(n1).add(n1);
cout << n2.num << endl;
}
我们发现编译器会报错,因为这时我们的add函数的返回类型为void,相当于除了第一次相加,后面执行的都是 “.add(n1).add(n1)”。这是错误的语句。
所以我们将add的返回类型改为number&,并利用this指针返回对象本身。
class number
{
public:
number(int num)
{
num = num;
}
number& add(const number &p)
{
*this.num += p.num;
return *this;
}
int num;
};
这样链式操作的每一次调用都可以正确的执行"n1.add(n2)"
为什么要返回引用类型? 有以下两个原因:
① 如果不返回引用类型,通过前面的对拷贝构造函数的调用时机的学习我们知道,直接通过值返回时,函数会拷贝一个对象返回,这样会增大程序运行时的内存占用。
② 只有返回引用类型才可以进行链式操作,因为返回引用类型返回的是对象的本身,那么每一次链式操作都是对对象本身操作。如果返回的不是引用类型,那么链式操作第一次执行之后的操作都是对对象的一个拷贝的操作,所以这是链式操作就只有第一次是有效的,其他都是无效的。 所以为了可以进行有效的链式操作,我们应该返回引用类型。
利用空指针访问成员函数
c++中空指针也可以直接访问类的成员函数,但是否可以成功访问则与成员函数是否只用了this指针有关。以下面的代码为例:
class person
{
public:
void showTest()
{
cout << "this is tset" << endl;
}
void showAge()
{
cout << "age:" << M_age << endl;
}
int M_age;
}
void test01()
{
person *p = NULL;
p->showTest;
p->showAge;
}
如果运行test01会发现在在 “p->showAge” 处报错说this指针为空指针,而上一行调用的函数不会报错,这是因为showAge函数中为了正确的输出M_age会使用this指针,而showTest函数不需要输出任何成员变量,不用使用this指针,所以不会出现上述错误。
在实际应用中,为了避免这种错误可以将使用了this指针的函数做如下的修改,增强程序的健壮性
void showAge()
{
if(this == NULL)
{
return;
}
cout << "name:" << M_age << endl;
}
const修饰:常函数和常对象
常函数:
① 被const修饰的成员函数叫常函数;
② 常函数不能修一般的改成员属性;
③ 被mutable关键字修饰的成员属性可以被常函数修改;
常对象:
① 声明对象时用const修饰得到常对象;
② 常对象只能调用常函数;
③ 常对象中只有被mutable修饰的属性才能被修改;
以以下代码为例:
class person
{
public:
void setAge(int age) const
{
m_age = age;
}
mutable int m_age;
}
void test02()
{
const person p;
p.setAge(17);
}
首先我们要知道this指针的实质:this指针是一个常量指针,即person * const this;this指针的指向不能修改,但是this指针指向的对象的属性可以修改。
再次用const修饰this指针后:const person* const p,this指针指向的对象将会变成常对象。
上述代码中setAge函数的实质是:
this->m_age = age;
当函数被const修饰后,位于函数内部的this指针也会被const再次修饰,所以this指针指向的对象也会变成常对象,所以此时只有m_age属性被mutable修饰后程序才能正确运行。
为什么常对象只能调用常函数:
首先我们知道,常对象中只有被mutable修饰的属性才能被修改,如果常对象可以调用常函数以外的函数,那么没有被mutable修饰的属性也可以被修改,所以常对象只能调用常函数。
注意静态成员属性和常对象的区别:
静态成员属性的值是可以被静态成员函数修改的,但是常对象中只有被mutable修饰的属性才能被常函数修改
并且常对象中的属性都是属于自己的,而静态成员属性不属于某个对象,而是属于这个类