static类成员
- static数据成员的类型可以是该成员所属类类型,但是非static成员的类型只能是该成员所属类类型的指针或引用。原因:static成员不属于任何对象,在构建对象时不需要计算static变量大小,可以构建成功。构建对象时需要计算非static成员的大小,但显然无法计算(每个类对象都包含类对象,递归了)。
class Bar{
private:
static Bar mem;//ok
Bar* men1;//ok
Bar& mem2;//ok
Bar mem3;//error
}
explicit
- explicit被用来禁止隐式转换,下面是隐式转换的例子。
class String {
public:
String(size_t size) {
std::cout <<"parameter is size"<< std::endl;
size_ = size;
pstr_ = (char*)malloc(size + 1);
memset(pstr_, 0, size + 1);
}
String(const char* pstr) {
std::cout <<"parameter is pstr"<< std::endl;
pstr_ = (char*)malloc(strlen(pstr) + 1);
strcpy(pstr_, pstr);
size_ = (size_t)strlen(pstr);
}
private:
size_t size_;
char* pstr_;
};
int main() {
// 预期成功的初始化
String string1(24); // 这样是OK的, 为CxString预分配24字节的大小的内存
std::cout << "---------------"<<std::endl;
String string4("aaaa"); // 这样是OK的
std::cout << "---------------"<<std::endl;
// 非预期成功的初始化
String string2 = 10; // 这样是OK的, 为CxString预分配10字节的大小的内存
std::cout << "---------------"<<std::endl;
String string5 = "bbb"; // 这样也是OK的, 调用的是CxString(const char *p)
std::cout << "---------------"<<std::endl;
String string6 = 'c'; // 这样也是OK的, 其实调用的是CxString(int size),
// 且size等于'c'的ascii码
std::cout << "---------------"<<std::endl;
string1 = 2; // 这样也是OK的, 为CxString预分配2字节的大小的内存
std::cout << "---------------"<<std::endl;
string2 = 3; // 这样也是OK的, 为CxString预分配3字节的大小的内存
std::cout << "---------------"<<std::endl;
}
- 为什么会与有这么多非预期的构造函数成功呢?
在c++中,如果是单参构造函数,在编译时就会有一个隐式的转换过程,将构造函数参数类型的数据转换为该类对象。也就是说,
String string2 = 10;
在这句代码中,编译器自动调用参数类型为size_t
的构造函数将参数10转化为类对象。
- 使用
explicit
来避免编译器的隐式构造,只需要在构造函数前面加上explicit
即可。
构造函数
- 默认构造函数。
只要定义一个对象没有提供初始化,如:
String string;
,就会使用默认构造函数。为所有形参提供默认实参的构造函数也是定义默认构造函数,如:String string(std::string str=" ")
- 合成默认构造函数
1.该函数使用与变量初始化相同的规则来初始化成员,类类型的成员运用各自的默认构造函数进行初始化。
2.只有当类对象定义在全局作用域内,类内的内置类型和复合类型变量才会被初始化为0,当类对象定义在局部作用内不会被初始化,所以有内置和复合类型成员的类,应该自己定义构造函数来初始化这些成员。内置类型:int, bool, double 复合类型:指针, 数组, 字符串, 结构体等,基于其他类型定义的类型。
3.只有当一个类没有定义构造函数时,编译器才会根据需要去判断是否自动生成默认构造函数,这里的关系是必要非充分。
c++代码中局部的内置类型变量都是不会默认清零的,全局变量可以。理由是,局部变量在栈中,若在栈中增加清零操作会使函数调用等操作变慢,
编译器合成默认构造函数的条件
- 需要调用对象类成员或基类的默认构造函数
- 对对象初始化虚表指针或虚基类指针
析构函数
- 在堆上创建的对象,需要手动删除,否则不会调用析构函数,导致内存泄漏,对象内部的任何资源也不会被释放。
实践
- 类中有虚函数,链接时发现类名未定义,需要给虚函数后面=0。