1、问题:类中是否可以定义const成员?
- 下面的类定义是否合法?
— 如果合法,ci 的值是什么,存储在哪里?
class Test
{
private:
const int ci;
public:
int getCI()
{
return ci;
}
};
程序:
#include <stdio.h>
class Test
{
private:
const int ci;
public:
int getCI()
{
return ci;
}
};
int main()
{
Test t1;
printf("t1.ci = %d\n", t1.getCI());
return 0;
}
分析:从上面我们可以得出结论。在类里面我们是可以定义const 成员变量的,但是当我们通过这个类来定义对象的时候,编译器就会报错,报错的原因是在这里面有未初始化的const 成员,我们面临的问题就是怎么来初始化类里面的 const 成员。那我们能否通过构造函数来初始化const 成员变量呢?
程序:
#include <stdio.h>
class Test
{
private:
const int ci;
public:
Test()
{
ci = 10;
}
int getCI()
{
return ci;
}
};
int main()
{
Test t1;
printf("t1.ci = %d\n", t1.getCI());
return 0;
}
分析:报错的原因在于它说 ci 是一个只读变量,不能出现在赋值符号的左边。
在C++里面,const 这个关键字如果作用于类的成员变量之后,其实得到是一个只读变量。从错误当中我们看出,如果我们要对 ci 进行初始化,必须要在第8行进行。
同时从这里也可以看出,构造函数不能对成员变量进行初始化,它的本质就是对成员变量进行赋值,在后面我们学到的的初始化列表才能对成员变量进行真正的初始化
2、类成员的初始化
- C++中提供了初始化列表对成员变量进行初始化
- 语法规则
ClassName::ClassName() :m1(v1), m2(v1, v2), m3(v3) //其实这就是在构造函数
{
//some other initialize operation
}
初始化的位置在:构造函数之后,函数体之前
#include <stdio.h>
class Test
{
private:
const int ci;
public:
Test():ci(10)
{
//ci = 10;
}
int getCI()
{
return ci;
}
};
int main()
{
Test t1;
printf("t1.ci = %d\n", t1.getCI());
return 0;
}
- 注意事项
— 成员的初始化顺序与成员的声明顺序相同
— 成员的初始化顺序与初始化列表中的位置无关
— 初始化列表先于构造函数的函数体执行
大概意思就是说:初始化顺序按照的是你在成员变量的定义顺序,而不是按照你在初始化列表中的排列位置
当我们的构造函数开始执行的时候,我们的对象已经创建完毕了。所以说初始化列表既然是用来初始化,那么它必须在类对象创建的时候来进行执行,而不是对象都创建好了,才来进行初始化的工作。这个差异就是初始化和赋值的差异。
#include <stdio.h>
class Value
{
private:
int mi;
public:
Value(int i)
{
mi = i;
printf("i = %d\n",i);
}
int getI()
{
return mi;
}
};
class Test
{
private:
Value m2;
Value m3;
Value m1;
public:
Test()
{
}
};
int main()
{
return 0;
}
意思就是说m1,m2,m3没有合适的构造函数,所以我们可以先对它用初始化列表进行初始化,使它接下来能正常进入构造函数来运行里面的输出函数。
#include <stdio.h>
class Value
{
private:
int mi;
public:
Value()
{
}
Value(int i)
{
mi = i;
printf("i = %d\n",i);
}
int getI()
{
return mi;
}
};
class Test
{
private:
Value m2;
Value m3;
Value m1;
public:
Test():m1(1),m2(2),m3(3)
{
}
};
int main()
{
Test t1;
return 0;
}
从结果我们可以论证之前的结论,初始化列表无法决定成员的初始化顺序,初始化顺序只与成员的声明顺序一致成我们声明的顺序是2 3 1,结果可以论证
#include <stdio.h>
class Value
{
private:
int mi;
public:
Value()
{
}
Value(int i)
{
mi = i;
printf("i = %d\n",i);
}
int getI()
{
return mi;
}
};
class Test
{
private:
Value m2;
Value m3;
Value m1;
public:
Test():m1(1),m2(2),m3(3)
{
printf("Test::Test()\n");
}
};
int main()
{
Test t1;
return 0;
}
从这个程序的结果我们可以看出程序运行的先后顺序。
首先对 t1 对象里面的成员进行初始化,由于它是另一个类的对象,调用另一个类的构造函数,再调用自己类的构造函数。
3、类中的 const 成员
- 类中的 const 成员会被分配空间的
- 类中的 const 成员的本质是只读变量
- 类中的 const 成员只能在初始化列表中指定初始值
编译器无法直接得到 const 成员的初始值,因此无法进入符号表成为真正意义上的常量
类中 const 成员它分配的空间是和整个类对象分配的空间一致。如果类对象分配的空间在栈上,那么 const 成员所分配的空间就在栈上;如果类对象分配的空间在堆上,那么const 成员所分配的空间就在堆上。
程序:证实通过指针可以改变const 成员变量的值
#include <stdio.h>
class Value
{
private:
int mi;
public:
Value()
{
}
Value(int i)
{
mi = i;
printf("i = %d\n",i);
}
int getI()
{
return mi;
}
};
class Test
{
private:
const int ci;
Value m2;
Value m3;
Value m1;
public:
Test():m1(1),m2(2),m3(3),ci(10)
{
printf("Test::Test()\n");
}
int getCI()
{
return ci;
}
void setCI(int v)
{
int* p = const_cast<int*>(&ci);
*p = v;
}
};
int main()
{
Test t1;
printf("t1.ci = %d\n",t1.getCI());
t1.setCI(5);
printf("t1.ci = %d\n", t1.getCI());
return 0;
}
从最后的结果我们可以知道通过指针(setCI函数)可以改变const 成员变量的值,也就是类中的const 成员不是常量,而是只读变量。
小结:
- 类中可以使用初始化列表对成员进行初始化
- 初始化列表先于构造函数体执行
- 类中可以定义 const 成员变量
- const 成员变量必须在初始化列表中指定初值
- const 成员变量为只读变量
分析:我来分析一下第二节的那个一个类调用另一个类的程序。
首先,一般我们都是在主函数定义对象。但是这里我们 Value 的成员变量就是 对象,所以我们这里是在类里面定义成员变量。所以就有一连串的初始化问题。其实不用初始化列表没关系,如果是我下面的程序:
#include <iostream>
using namespace std;
class Value
{
private:
int m_value;
public:
Value()
{
m_value = 0;
cout << "Value()" << endl;
}
Value(int v)
{
m_value = v;
cout << "Value(int v),v = " << v << endl;
}
int getValue()
{
return m_value;
}
};
class Test
{
private:
Value v1;
Value v2;
Value v3;
public:
Test() //:v1(1), v3(3), v2(2)
{
cout << "Test()" << endl;
}
};
int main()
{
Test t;
return 0;
}
因为这里我们是默认调用无参构造函数。但是如果我们要参数,我们必须要利用初始化列表,因为我们的成员变量只能是这样定义
Value v1;
Value v2;
Value v3;
不能像主函数那样定义:
Value v1(1);
Value v2(2);
Value v3(3);
这就是我们要利用初始化列表的原因。
主要原因就是这里的成员变量是对象,和以往我们的成员变量不一样,我们的任务是要完成初始化。