目录
一、初始化列表
1、定义
2、注意事项
3、尽量使用初始化列表初始化
4、初始化顺序
二、 explicit关键字
1、定义
三、static成员
1、定义
2、特性
3、例题
一、初始化列表
下面这段代码可以正常编译:
class A {
private:int _a1;//成员声明int _a2;
};
int main()
{A a;//对象整体定义return 0;
}
如果加上一个const类型的成员变量_x,编译就无法通过。
class A {
private:int _a1;int _a2;const int _x;
};
int main()
{A a;return 0;
}
这是因为const变量必须在定义的位置初始化,否则编译不通过。
class A {
private:int _a1;//声明int _a2;const int _x;
};
在private作用域中,const变量和两个int变量都是成员变量的声明,如果我们声明const变量,一定要对它进行定义,那我们在哪定义呢?
C++11之后可以在声明位置为变量赋初值。
const int _x = 0;
那在C++11之前,也有解决方法,给每个成员变量找一个位置对其进行定义,这样就解决了变量初始化的问题,这个位置使用初始化列表进行初始化赋值。
1、定义
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
class A {
public:A():_x(1){}
private:int _a1;int _a2;const int _x;
};
int main()
{A a;return 0;
}
只要对象调用构造函数,初始化列表是它所有成员变量定义的位置。
不管是否显示在初始化列表写,那么编译器每个变量都会初始化列表定义初始化
class A {
public:A():_x(1),_a1(6){}
private:int _a1 = 1;int _a2 = 2;const int _x;
};
int main()
{A a;return 0;
}
在初始化列表中初始化的变量,不使用缺省值;没有使用初始化列表的变量,使用缺省值。
2、注意事项
- 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
- 类中包含以下成员,必须放在初始化列表位置进行初始化:
- 引用成员变量
- const成员变量
- 自定义类型成员(且该类没有默认构造函数时)
class B {
public:B():_b(0){cout << "B()" << endl;}
private:int _b;
};class A {
private:B _bb;
};
int main()
{A aa;return 0;
}
这里的aa的成员变量自定义类型_bb是可以调用它的默认构造函数的初始化列表进行初始化。
默认构造可以是无参或全缺省的。
class B {
public:B(int n) :_b(0)//会报错B(int n=9) :_b(0)//全缺省B( ) :_b(0)//无参
private:int _b;
};
3、尽量使用初始化列表初始化
下面看一个用两个栈实现的队列。
typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){cout << "Stack(size_t capacity = 10)" << endl;_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");exit(-1);}_size = 0;_capacity = capacity;}void Push(const DataType& data){_array[_size] = data;_size++;}Stack(const Stack& st){cout << "Stack(const Stack& st)" << endl;_array = (DataType*)malloc(sizeof(DataType)*st._capacity);if (nullptr == _array){perror("malloc申请空间失败");exit(-1);}memcpy(_array, st._array, sizeof(DataType)*st._size);_size = st._size;_capacity = st._capacity;}~Stack(){cout << "~Stack()" << endl;if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}private:DataType *_array;size_t _size;size_t _capacity;
};class MyQueue
{
public:MyQueue(int pushN, int popN):_pushST(pushN), _popST(popN){}private:Stack _pushST;Stack _popST;int _size = 0;
};int main()
{ MyQueue q(2, 3);return 0;
}
在调试中可以看到,这里的2
和3
分别作为参数传递给MyQueue
的构造函数,通过初始化列表对这两个成员变量进行初始化。
如果我们使用这种无参的构造函数对MyQueue对象初始化呢?
class MyQueue
{
public:MyQueue(){}private:Stack _pushST;Stack _popST;int _size = 0;
};
可以看到,如果我们不写初始化列表,MyQueue类也可以调用Stack的默认构造函数对两个Stack类的对象进行初始化,不写MyQueue的构造函数也会使用同样方式初始化,本质上一样。
4、初始化顺序
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。
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();
}
_a2比_a1先声明,所以_a2比_a1先在初始化列表中初始化,_a2初始化时_a1还没初始化,所以_a2是随机值。
二、 explicit关键字
class A {
public:A(int a):_a1(a){}
private:int _a2;int _a1;
};
int main()
{A aa1(1); //构造函数A aa2 = 1; //隐式类型转换int i = 1; double d = i;//隐式类型转换return 0;
}
默认情况下,这里的隐式类型转换都会借助额外创建的临时变量实现。
我们使用下面的程序验证一下:
class A
{
public:A(int a):_a1(a){cout << "A(int a)" << endl;}A(const A& aa):_a1(aa._a1){cout << "A(const A& aa)" << endl;}private:int _a2;int _a1;
};
输出结果发现没有调用引用类型的拷贝构造。
这是因为C++中编译器会对自定义类型的进行优化, 将构造+拷贝+优化的过程优化成一个构造。
那下面的代码中,为什么第一个会报错,第二个没问题呢?
A& ref = 10;
const A& ref = 10;
- 这是因为在C++中,当你声明一个引用(比如
A& ref
)并试图将其初始化为一个右值(比如一个临时对象或一个字面量),编译器通常会报错。这是因为非const引用不能绑定到右值上,防止对临时对象的非常量引用,因为这可能导致对临时对象的意外修改,从而导致不确定的行为。但是,当你声明一个常量引用(比如const A& ref
),编译器允许这种绑定,因为常量引用可以绑定到右值上。 - 在上述代码中,
const A& ref = 10;
这行代码中的10
是一个整数字面量,是一个右值。你尝试将这个右值绑定到引用ref
上。由于ref
被声明为const A&
,它是一个常量引用,所以编译器允许这种绑定,并调用A
类的构造函数A(int a)
来创建一个临时的A
对象,然后将ref
绑定到这个临时对象上。
1、定义
对于单参构造函数:没有使用explicit修饰,具有类型转换作用explicit修饰构造函数,禁止类型转换---explicit去掉之后,代码可以通过编译
对于刚刚的代码,如果在构造函数前加explicit程序会怎么样呢?
class A{
public:explicit A(int a):_a1(a){cout << "A(int a)" << endl;}A(const A& aa):_a1(aa._a1){cout << "A(const A& aa)" << endl;}private:int _a2;int _a1;
};
这两段代码会报错,程序禁止类型转换。
虽然有多个参数,创建对象时后两个参数可以不传递,没有使用explicit修饰,具有类型转换作用 ,explicit修饰构造函数,禁止类型转换。
class A
{
public://explicit A(int a)A(int a):_a1(a){cout << "A(int a)" << endl;}//explicit A(int a1, int a2)A(int a1, int a2):_a1(a1), _a2(a2){}private:int _a2;int _a1;
};
int main()
{// 单参数构造函数 C++98A aa1(1); //构造函数A aa2 = 1; //隐式类型转换// 多参数构造函数 C++11A aa2(1, 1);//A aa3= 2,2;//C98不支持A aa3 = { 2, 2 };//C++11支持return 0;
}
A aa2(1, 1);
- 这是直接初始化的一个例子。
- 在这种情况下,编译器会查找一个与提供的参数列表最匹配的构造函数。这里,它会匹配
A(int a1, int a2)
构造函数。 - 这种方式不会触发任何类型转换或隐式调用。
A aa3 = { 2, 2 };
- 这是列表初始化,在C++11及以后的标准中引入。
- 列表初始化的行为取决于多种因素,如构造函数的可用性、是否有可用的初始化列表构造函数、是否有需要进行隐式转换的场景等。
- 在这个特定的例子中,它将尝试使用
A(int a1, int a2)
构造函数,因为这是唯一可以从两个整数参数构造A
对象的构造函数。 - 如果类
A
有一个接受std::initializer_list
的构造函数,这个构造函数将会被优先使用。 - 列表初始化还有一个优点,它禁止了窄化转换。例如,如果你试图用一个浮点数来初始化一个整数,编译器将会报错。
总的来说,这两行代码在这种情况下的行为是相似的,因为 A 类没有提供接受 std::initializer_list 参数的构造函数,且构造函数 A(int a1, int a2) 能够接受两个整数参数。但是,列表初始化有时候会提供一些额外的安全性和灵活性,尤其是在处理聚合类型(如结构体和数组)时。
三、static成员
实现一个类,计算程序中创建了多少类对象
int count = 0;
class A
{
public:A(int a = 0){++count;}A(const A& aa){++count;}
};
void func(A a)
{}
int main()
{A aa1;A aa2(aa1);func(aa1);A aa3 = 1;cout << count << endl;return 0;
}
造成了命名冲突的问题,C++的xutility文件里有个函数count与我们定义的全局变量count冲突了。
我们可以不展开std,只调用需要用的流输入输出即可。
#include <iostream>
//using namespace std;
using std::cout;
using std::endl;
成功输出:
C++为了解决上述问题,同时可以将std展开,将count作为类的static修饰的成员即可实现。
1、定义
class A
{
public:A(int a = 0){++count;}A(const A& aa){++count;}
private:// 不属于某个对象,所于所有对象,属于整个类static int count; // 此处为声明int _a = 0;
};int A::count = 0; // 定义初始化void func(A a)
{}
当我们想输出时:
int main()
{A aa1;A aa2(aa1);func(aa1);A aa3 = 1;cout << A::Getcount() << endl;return 0;
}
如果想要输出,可以使用静态成员函数。
//静态成员函数 没有this指针static int Getcount(){// _a++; // 不能直接访问非静态成员return count;}
成功输出:
下面语句创建出了多少个类对象?
A aa4[10];
输出结果:
2、特性
- 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
- 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
- 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制
3、例题
求1+2+3+...+n_牛客题霸_牛客网 (nowcoder.com)
- 下面这段代码实现了一个类
Sum
和一个类Solution
,其中Sum
类用于计算从1到n的累加和,而Solution
类则使用Sum
类来计算给定整数n的累加和。- 这种设计利用了类的构造函数和静态成员变量的特性,实现了累加和的计算和获取。
class Sum{
public:Sum(){_sum+=_i;_i++;}static int Getsum(){return _sum;}
private:static int _sum;static int _i;
};
int Sum::_sum = 0;
int Sum::_i = 1;
class Solution {
public:int Sum_Solution(int n) {Sum a[n];return Sum::Getsum();}
};
首先,让我们逐步解释 Sum
类的实现:
Sum
类有两个静态成员变量_sum
和_i
,分别用于保存累加和和当前的计数器值。- 构造函数
Sum()
是一个无参构造函数,每次被调用时,它会将当前计数器值_i
加到累加和_sum
中,并将计数器_i
自增1。 - 静态成员函数
Getsum()
用于获取累加和_sum
的值。
接下来,我们来看 Solution
类的实现:
Solution
类中的成员函数Sum_Solution(int n)
接受一个整数n
作为参数,并返回从1到n的累加和。- 在
Sum_Solution
函数中,我们创建了一个名为a
的Sum
类型的数组,数组的大小为n
。 - 由于
Sum
类的构造函数会在创建对象时自动调用,因此创建数组a
的过程中,会依次调用Sum
类的构造函数,从而实现了从1到n的累加和的计算。 - 最后,我们通过调用
Sum::Getsum()
函数来获取累加和的值,并将其作为函数的返回值。