初始化列表
class Stack
{
public:
//默认构造函数
Stack(int capacity)
{
_a = (int*)malloc(sizeof(int) * capacity);
if (_a == nullptr)
{
perror("malloc fail!\n");
exit(-1);
}
_capacity = capacity;
_size = 0;
cout << "Stack(int capacity = 4)" << endl;
}
~Stack()
{
if (_a)
{
free(_a);
_a = nullptr;
_size = _capacity = 0;
}
cout << "~Stack()" << endl;
}
private:
int* _a;
int _size;
int _capacity;
};
class MyQueue
{
public:
private:
Stack _pushst;
Stack _popst;
int _size;
};
int main()
{
MyQueue q;
return 0;
}
如上段代码,我们队列的类中声明了两个栈,那么当我们创建队列类对象的时候(没有显示定义构造函数),如果有自定义类型,那么我们会去调用自定义类型的默认构造函数。那倘若我们自定义类型没有默认构造函数呢?
默认构造函数: 无参数、全缺省、没有显示定义编译器自动生成的构造函数才被称为默认构造函数。
比如我们栈中的构造函数需要传参,那会出现什么情况呢?
Stack(int capacity)
{
_a = (int*)malloc(sizeof(int) * capacity);
if (_a == nullptr)
{
perror("malloc fail!\n");
exit(-1);
}
_capacity = capacity;
_size = 0;
cout << "Stack(int capacity = 4)" << endl;
}
此时我们运行代码:
那么编译都过不了,那么这个时候在队列类中,我们就需要显示定义构造函数了。但是我们此时报错的原因是栈没有默认构造函数。
这里要明确一个点,构造函数是我们类成员定义的时候调用的。那么此时我们需要自己调用栈的构造函数就需要在栈定义的时候传入参数。但是此时我们只有声明,那也不能再声明的地方传入参数。
在队列的构造函数传入参数也不行:
这时候就需要用到我们的初始化列表了。
初始化列表本质: 类中成员变量定义初始化的地方。
使用规则: 以冒号开头,逗号分割。
MyQueue()
:_pushst(4), //传入参数进行初始化
_popst(4),
_size(0) //内置类型初始化
{}
这里我们初始化必须使用括号,语法规则。那么此时我们运行的时候,就会去调用栈的构造函数。
当然这里我们也可以在队列构造函数的初始化列表和函数体内混合初始化。
注意: 这里我们有三种必须在初始化列表中初始化的情况。
- const 修饰
- 引用
- 没有默认构造函数的自定义类型
第三种就是上面所述。
const 修饰的变量必须初始化,引用类型也必须初始化(之前说过),所以这三种必须在我们在我们的初始化列表初始化(初始化列表的本质)。
两个注意事项:
Stack _pushst;
Stack _popst;
int _size = 1; //缺省值
int _aa = 2;
-
C++11新引入了类成员变量声明的时候可以给缺省值,其实这个缺省值是给初始化列表用的。所以在我们初始化的时候,尽量写在初始化列表中。
-
成员变量在声明时的声明顺序,就是初始化列表的初始化顺序。
如下图:运行后的结果是什么呢?
class A
{
public:
A(int a)
:_a1(a),
_a2(_a1)
{}
void Print()
{
cout << _a1 << " " << _a2 << endl;
}
private:
int _a2;
int _a1;
};
int main()
{
A aa(1);
aa.Print();
return 0;
}
在类成员变量声明时我们先声明了_a2,然后才是_a1,所以我们初始化列表初始化的时候,先初始化_a2,在初始化_a1,这也是为什么我们_a2是随机值的原因。
隐式类型转换
int main()
{
double i = 3.14;
int x = i;
return 0;
}
如上图,我们把double类型的 i i i 赋值给 x x x的时候,会产生临时变量,然后才是把临时变量的值给 x x x。
那么如果我们左边是自定义类型呢?是否可以成功?
对于单参数构造函数来说:
class A
{
public:
A(int a)
:_a(a)
{}
A(const A& _A)
{
_a = _A._a;
}
void Print()
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A aa = 3;
aa.Print();
return 0;
}
对于上面的代码,由于我们的A的构造函数是一个单参数的,所以3会先构造出一个临时变量,这个临时变量的成员变量会得到3,然后才会赋值拷贝给我们的
a
a
aa
aa对象。
对于多参数的构造函数:
我们需要用大括号扩起来进行传参。
A(int a, int b)
{
_a1 = a;
_a2 = b;
}
A(const A& _A)
{
_a1 = _A._a1;
_a2 = _A._a2;
}
小优化:
这里语法上我们是进行了 构造函数 − > 拷贝构造函数 构造函数 -> 拷贝构造函数 构造函数−>拷贝构造函数,但是编译器会进行一个优化,直接构造函数,把拷贝构造函数这一步骤优化掉了。
应有:
如果我们的栈现在数据并不是内置函数,而是像a这个自定义类型。
那么此时我们就可以非常方便的直接传入就可以了。
static成员
- static修饰成员变量。
被static修饰的成员变量此时就不存在类中了,它存在静态区中。
同时注意: 此时我们 a 2 a2 a2成员变量不能再类中初始化,因为我们初始化列表只能初始化类中的。而此时a2并不在类中而是在静态区中。初始化方式跟成员函数类似。
此时我们把我们在类中的叫做声明,而外面的叫做定义,同时声明域,跟我们成员函数的初始化类似。
- static修饰成员函数
static修饰的成员函数也是属于静态区,同时,它只能访问静态成员变量,普通的成员变量访问不了。
所以当成员函数被static修饰过后,只能访问静态成员变量。
但是非静态修饰的可以访问静态修饰的。
当然啦,由于我们是定义在公开的,我们也可以直接用类访问限定符进行访问。
内部类 (累不累?)
类中我们可以定义成员函数、成员变量。我们也可以定义一个类。
class A
{
public:
A()
:_a1(0)
{}
static int GetA()
{
return _a2;
}
class BB
{
public:
void fun(const A& _A)
{
_b1 = _a2;
_b2 = _A._a1;
}
private:
int _b1;
int _b2;
};
private:
int _a1;
static int _a2;
};
注意:
- 内部类可以直接访问外部类的静态变量
- 内部类天生就是外部类的友元函数 - 可以访问外部类的私有成员,但是外部类不是内部类的友元
- 内部类和外部类是独立的,sizeof(外部类)就只是外部类的大小。
匿名对象
class A
{
private:
int _aa = 0;
public:
void Print()
{
cout << "void Print()" << endl;
}
A(int x = 0) {};
~A()
{
cout << "~A() " << endl;
};
A(const A& s)
{
_aa = s._aa;
}
};
如上图,我们定义了一个类A,平常我们实例化对象的时候都会:
A
s
(
10
)
,
s
.
P
r
i
n
t
(
)
A s(10),s.Print()
As(10),s.Print()。我们实例化对象出来,用对象去调用成员函数。同时当main函数结束的时候,
s
s
s的生命周期也就结束了。
而匿名对象,我们不需要写对象的名字,同时还可以直接调用成员函数: A ( ) . P r i n t ( ) A().Print() A().Print(),而此时我们匿名对象的生命周期只在定义的这一行内有效,下一行直接就析构了。
小练习
这里我们再来看一个有趣的东西。
class A
{
public:
A(int n)
:_a1(n)
{
_a2++;
}
static int GetA()
{
return _a2;
}
A(const A& _A)
{
_a1 = _A._a1;
_a2++;
}
private:
int _a1;
static int _a2;
};
int A::_a2 = 0;
int main()
{
A _x1 = 0;
A _x2 = 3;
A _x3 = _x2;
A _x4 = _x3;
cout << A::GetA() << endl;
return 0;
}
由于自定义类型的创建都需要经过构造或者拷贝构造,所以我们设置一个静态变量(_a2),每次进入构造或者拷贝构造的时候都让((_a2)++,那么我们就可以得出一共创建了多少个A类对象了。
小练习 (提示:自定义类型创建数组,有多少个元素,就构造多少次。)