1. 再谈构造函数
在创建对象时,编译器会调用构造函数,给对象中的成员变量初始值。
class Date
{
public:
//构造函数
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
虽然上述构造函数调用后,对象中已经有了初始值,但这并不能将其称作类对象成员的初始化,,构造函数中的语句只能称为赋值。因为初始化只能初始化一次,但构造函数内可以多次赋值。
class Date
{
public:
//构造函数
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_year = 2024;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
构造函数初始化还有另一种方式,就是初始化列表。
1.1 初始化列表
初始化列表:以一个冒号开始,接着是以一个逗号分隔的数据成员列表,每个成员变量后跟一个放在括号中的初始值或表达式。
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
,_month(month)
,_day(day)
{}
private:
int _year;
int _month;
int _day;
};
注意事项:
1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)。
2. 对于引用成员变量,const成员变量以及没有默认构造的类类型变量,必须在初始化列表中进行初始化。
class Time
{
public:
Time(int hour)
:_hour(hour)
{}
private:
int _hour;
};
class Date
{
public:
Date(int& x, int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
,_t(12)
,ret(x)
,_n(1)
{}
void Print() const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
Time _t; //没有默认构造
int& ret; //引用
const int _n; //const
};
注意:默认构造函数是指不用传参就可以调用的构造函数,主要包括:1. 编译器自动生成的构造函数 2. 无参的构造函数 3. 全缺省的构造函数
class A
{
public:
//这个不叫默认构造函数
A(int val)
{
_val = val;
}
private:
int _val;
};
class B
{
public:
B()
:_a(2024) //必须用初始化列表进行初始化
{}
private:
A _a; //自定义成员,A无默认构造函数
};
3. C++11支持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显示在初始化列表初始化的成员使用的,同时尽量用初始化列表初始化。
class Time
{
public:
Time(int hour)
:_hour(hour)
{}
private:
int _hour;
};
class Date
{
public:
Date()
:_month(7)
{}
void Print() const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
//声明 缺省值->初始化列表
int _year = 2024;
int _month = 7;
int _day;
Time _t = 1;
int* ptr = (int*)malloc(12);
const int _n = 1;
};
int main()
{
Date d1;
d1.Print();
return 0;
}
在初始化时,每个成员都要走初始化列表,分为两种情况:一是在初始化列表中的成员,则直接进行初始化;二是不在初始化列表中的成员,如果有缺省值,直接用缺省值初始化;如果没有缺省值,如果是内置类型,要看编译器是否初始化,这个不确定;如果是自定义类型,会调用默认构造,如果没有,就会编译报错。
4. 初始化列表按照成员变量在类中的声明顺序进行初始化,建议声明顺序和初始化顺序一样。
class A
{
public:
A(int a)
:_a1(a)
, _a2(_a1)
{}
void Print() {
cout << _a1 << " " << _a2 << endl;
}
private:
int _a2 = 2;
int _a1 = 2;
};
int main()
{
A aa(1);
aa.Print();
}
看到上面的代码,可以猜一下,输出结果是什么?
最后结果是1和随机值。
因为初始化列表按照成员变量在类中的声明顺序进行初始化,可以看到,_a2先声明,_a1后声明,所以_a2先进行初始化,因为此时_a1是随机值,所以_a2是随机值,之后用1初始化_a1。
通过调试我们可以更直观的看到这个过程。
2. 类型转换
class A
{
public:
A(int a)
:_a1(a)
{}
void Print() {
cout << _a1 << " " << _a2 << endl;
}
private:
int _a1 = 2;
int _a2 = 2;
};
int main()
{
A aa1(1);
aa1.Print();
//隐式类型转换
A aa2 = 2;
//A tmp(2);
//A aa2(tmp);
aa2.Print();
return 0;
}
在语法中,代码中A aa2 = 2等价于下面两句代码:
A tmp(2);
A aa2(tmp);
对于隐式类型转换,编译器会先用2构造一个A的临时对象,再用这个对象拷贝构造aa2. 编译器在遇到连续构造+拷贝构造时会优化成直接构造。
其实,隐式类型转换我们很早就遇到过。
int a = 2;
double b = a;
C++11之后支持多参数传参。
class A
{
public:
A(int a)
:_a1(a)
{}
A(int a1,int a2)
:_a1(a1)
,_a2(a2)
{}
void Print()
{
cout << _a1 << " " << _a2 << endl;
}
private:
int _a1 = 2;
int _a2 = 2;
};
int main()
{
A aa1(1);
aa1.Print();
//隐式类型转换
A aa2 = 2;
//A tmp(2);
//A aa2(tmp);
aa2.Print();
A aa3 = { 5.6 };
aa3.Print();
return 0;
}
若我们想禁止单参数构造函数的类型准换,可以用关键字explicit来修饰构造函数。
explicit A(int a)
:_a1(a)
{}
3. static成员
用static修饰的成员变量,称为静态成员变量。用static修饰的成员函数,称为静态成员函数。静态成员变量必须在类外进行初始化。
特性:
1. 静态成员变量为所有类成员对象共享,不属于某个具体的对象,不存放在对象中,存放在静态区。
class A
{
public:
A()
{
++_scount;
}
private:
//类里面声明
static int _scount;
};
//类外面初始化
int A::_scount = 0;
int main()
{
A a1;
cout << sizeof(a1) << endl;
return 0;
}
计算类的大小或类类型对象的大小时,静态成员不计入其总大小之和中。
2. 静态成员也是类的成员,受访问限定符的限制。但突破类域就可以访问静态成员。
class A
{
public:
private:
static int _n;
};
int A::_n = 0;
3. 静态成员函数没有this指针,可以访问静态成员,不能访问非静态成员。
class A
{
public:
//默认构造
A()
{
++_scount;
}
//拷贝构造
A(const A& a)
{
++_scount;
}
//析构
~A()
{
--_scount;
}
static int GetACount()
{
_a++;
return _scount;
}
private:
//类里面声明
static int _scount;
int _a = 1;
};
无法访问_a.
//计算程序中创建出了多少个类对象
class A
{
public:
//默认构造
A()
{
++_scount;
}
//拷贝构造
A(const A& a)
{
++_scount;
}
//析构
~A()
{
--_scount;
}
static int GetACount()
{
//_a++;
return _scount;
}
private:
//类里面声明
static int _scount;
//int _a = 1;
};
//类外面初始化
int A::_scount = 0;
int main()
{
cout << A::GetACount() << endl;
A a1, a2;
A a3(a1);
cout << A::GetACount() << endl;
return 0;
}
这里有一道题可以练手:求1+2+3+...+n_牛客题霸_牛客网 (nowcoder.com)
4. 友元
友元提供了一种突破类访问限定符封装的方式,主要分为友元函数和友元类。虽然有时提供了便利,但友元会增加耦合度,破坏封装,不宜多用。
class A
{
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
private:
int _b1 = 3;
int _b2 = 4;
};
void fun(const A& a, const B& b)
{
cout << a._a1 << endl;
cout << a._a2 << endl;
cout << b._b1 << endl;
cout << b._b2 << endl;
}
int main()
{
A aa;
B bb;
fun(aa, bb);
return 0;
}
当我们写了这样一段代码,会发现编译错误,无法访问私有成员变量,为了解决这个问题,我们需要加上友元声明。
// 前置声明,否则A的友元函数声明编译器不认识B
class B;
class A
{
//友元声明
friend void fun(const A& a, const B& b);
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
//友元声明
friend void fun(const A& a, const B& b);
private:
int _b1 = 3;
int _b2 = 4;
};
void fun(const A& a, const B& b)
{
cout << a._a1 << endl;
cout << a._a2 << endl;
cout << b._b1 << endl;
cout << b._b2 << endl;
}
int main()
{
A aa;
B bb;
fun(aa, bb);
return 0;
}
上述代码还可以将其中一个类声明为另一个类的友元类,这样就可以访问另一个类的私有和保护成员。
class A
{
//友元声明
friend class B;
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
public:
void fun1(const A& a)
{
cout << a._a1 << endl;
cout << _b1 << endl;
}
void fun2(const A& a)
{
cout << a._a2 << endl;
cout << _b2 << endl;
}
private:
int _b1 = 3;
int _b2 = 4;
};
int main()
{
A aa;
B bb;
bb.fun1(aa);
bb.fun2(aa);
return 0;
}
注意:
1. 友元类中的关系是单向的,比如A是B的友元,但B不是A的友元。
2. 友元类关系不能传递,比如A是B的友元,B是C的友元,但A不是C的友元。
5. 内部类
如果一个类定义在另一个类的内部,那么这个类就是内部类。内部类是一个独立的类,跟定于在全局相比,他只受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类。
内部类默认是外部类的友元类。
class A
{
public:
//B默认是A的友元
class B
{
public:
void fun(const A& a)
{
cout << _k << endl;
cout << a._h << endl;
}
};
private:
static int _k;
int _h = 1;
};
int A::_k = 2;
int main()
{
cout << sizeof(A) << endl;
A::B b;
A aa;
b.fun(aa);
return 0;
}
6. 匿名对象
用类型(实参)定义出来的对象叫匿名对象。
匿名对象生命周期只在当前一行。
class A
{
public:
A(int a = 0)
:_a(a)
{}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
class Solution
{
public:
int Sum_Solution(int n)
{
return n;
}
};
int main()
{
//匿名对象
A();
A(1);
cout << Solution().Sum_Solution(10) << endl;
return 0;
}