C++初阶类和对象
Posted Huang_ZhenSheng
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++初阶类和对象相关的知识,希望对你有一定的参考价值。
目录
开篇首先看一道题
设已经有A,B,C,D 4个类的定义,程序中A,B,C,D析构函数的调用顺序为:
class A
{
public:
A()
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
};
class B
{
public:
B()
{
cout << "B()" << endl;
}
~B()
{
cout << "~B()" << endl;
}
};
class C
{
public:
C()
{
cout << "C()" << endl;
}
~C()
{
cout << "~C()" << endl;
}
};
class D
{
public:
D()
{
cout << "C()" << endl;
}
~D()
{
cout << "~D()" << endl;
}
};
C c;
int main()
{
A a;
B b;
static D d;
return 0;
}
C是一个全局对象,D是一个局部静态对象,对象在整个程序运行的期间都存在,在main函数结束的时候才会销毁
构造顺序:
C(一定在main函数之前被定义出来,全局对象在main函数之前初始化)
A
B
D(静态的局部对象在第一次调用当前域函数的时候初始化的。第二次调用的时候不会初始化了)
析构顺序:
C最先定义,最后析构
A B D呢?
D是第一次调用当前域函数的时候初始化的 ,D虽然生命周期是全局的,但毕竟是一个局部的,局部的静态对象还是要比C先析构的
B ——> A ——> D ——> C
再谈构造函数
class A
{
public:
A(int a = 0)
{
_a = a;
cout << "A(int a = 0)" << endl;
}
A& operator = (const A& aa)
{
cout << "A& operator = (const A& aa)" << endl;
if (this != &aa)
{
_a = aa._a;
}
return *this;
}
private:
int _a;
};
class B
{
public:
B(int a, int b)
{
/*A aa(a);
_aa = aa;*/
_aa = A(a);
_b = b;
}
private:
int _b = 1;//内置类型
A _aa;//自定义类型
};
int main()
{
B b(10,20);
return 0;
}
笔者暂时没太懂这里的输出,后续理解后在进一步完善,也请各位大佬多多指教!
从上面可以看出初始化_aa代价很大,调用了以上那么多的函数
初始化列表
对于自定义类型成员,改用初始化列表初始化,可以提高效率
将上面代码改成初始化列表
class B
{
public:
B(int a, int b)
:_aa(a)
{
_b = b;
}
private:
int _b = 1;//内置类型
A _aa;//自定义类型
};
只调用了一个构造函数,调用了带参的构造
尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。
哪些情况必须使用初始化列表初始化呢?
1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2. 类中包含以下成员,必须放在初始化列表位置进行初始化:
引用成员变量
const成员变量
自定义类型成员(该类没有默认构造函数)
下面看这段代码的输出结果?
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中出现的先后次序无关,实际中,建议声明的顺序和初始化的列表的顺序保持一致!!!
class A
{
public:
A(int a)
:_a(a)
{
cout << "A(int a)" << endl;
}
A(const A& aa)
{
cout << "A(const A& aa)" << endl;
}
private:
int _a;
};
int main()
{
A aa1(1);
A aa2 = 2;
return 0;
}
下面本质是一个隐式类型转换
A aa2 = 2
1,先用2去构造临时对象
2,再用这个临时对象去拷贝构造aa2
3,最终vs编译又进行了优化,优化会用2作为参数,直接去构造aa2
static成员
int countC = 0;
int countCC = 0;
class A
{
public:
A()
{
countC++;
}
A(const A& a)
{
countCC++;
}
};
A f(A a)
{
A ret(a);
return ret;
}
int main()
{
A a1 = f(A());
A a2;
A a3;
a3 = f(a2);
cout << countC << endl;
cout << countCC << endl;
return 0;
}
————>以上代码容易被改动,如下代码进行改造:
int countC = 0;
int countCC = 0;
class A
{
public:
A()
{
_count++;
}
A(const A& a)
{
_count++;
}
private:
//声明
int _a; //存在定义出的对象中,属于某个对象
static int _count;//存在静态区,属于整个类,也属于每个定义出来的对象共享
//跟全局变量比较,他受类域和访问限定符限制,更好的体现封装,别人不能轻易的修改
};
//定义初始化
int A::_count = 0;
A f(A a)
{
A ret(a);
return ret;
}
int main()
{
A a1 = f(A());
A a2;
A a3;
a3 = f(a2);
cout << sizeof(A) << endl;
return 0;
}
当声明为公有的时候,可以用一下几种方式进行访问:—>(具体的说明详见代码块里的注释)
int countC = 0;
int countCC = 0;
class A
{
public:
A()
{
_count++;
}
A(const A& a)
{
_count++;
}
//private:
//声明
int _a; //存在定义出的对象中,属于某个对象
static int _count;//存在静态区,属于整个类,也属于每个定义出来的对象共享
//跟全局变量比较,他受类域和访问限定符限制,更好的体现封装,别人不能轻易的修改
};
//静态成员变量不能在构造函数初始化,在全局位置定义初始化
int A::_count = 0;
A f(A a)
{
A ret(a);
return ret;
}
int main()
{
A a1 = f(A());
A a2;
A a3;
a3 = f(a2);
cout << sizeof(A) << endl;
//属于整个类,也属于每个定义出来的对象共享
cout << A::_count<< endl;
cout << a1._count << endl;
cout << a2._count << endl;
return 0;
}
下面给一个静态成员函数————>(具体的说明详见代码块里的注释)
int countC = 0;
int countCC = 0;
class A
{
public:
A()
{
_count++;
}
A(const A& a)
{
_count++;
}
//静态成员函数
//静态成员函数没有this指针,无法访问_a
static int GetCount()
{
return _count;
}
private:
//声明
int _a; //存在定义出的对象中,属于某个对象
static int _count;//存在静态区,属于整个类,也属于每个定义出来的对象共享
//跟全局变量比较,他受类域和访问限定符限制,更好的体现封装,别人不能轻易的修改
};
//静态成员变量不能在构造函数初始化,在全局位置定义初始化
int A::_count = 0;
A f(A a)
{
A ret(a);
return ret;
}
int main()
{
A a1 = f(A());
A a2;
A a3;
a3 = f(a2);
cout << sizeof(A) << endl;
//单纯的只想用一个对象去调用
A ret;
cout << ret.GetCount()-1 << endl;
//下面就是匿名对象的一种使用场景
cout << A().GetCount() - 2 << endl;//A()匿名对象
cout << A::GetCount()-2 << endl;//使用静态的成员函数突破类域进行调用
return 0;
}
总结:
static的作用:
C
1.修饰全局变量和全局函数,改变链接属性,只在当前文件可见
2.修饰局部变量,改变声明周期
C++
静态成员变量不能在构造函数初始化,在全局位置定义初始化
————》》》
1. 静态成员函数可以调用非静态成员函数吗
2. 非静态成员函数可以调用类的静态成员函数吗
友元
问题:现在我们尝试去重载operator<<,然后发现我们没办法将operator<<重载成成员函数。因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以我们要将operator<<重载成全局函数。但是这样的话,又会导致类外没办法访问成员,那么这里就需要友元来解决。
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。
(详见注释)
class Date
{
//友元函数
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);//这里不能加const
public:
Date(int year = 0, int month = 0, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
//为了让cout在第一个参数,左左操作数,我们就只能写成全局的
//其次就是operator<<搞成友元,可以在类中访问私有,但是operator<<不是必须友元,还有其他方式
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "/" << d._month << "/" << d._day << endl;
return out;
}
istream& operator>>(istream& in, Date& d)//这里不能加const
{
in >> d._year >> d._month >> d._day;
return in;
}
int main()
{
Date d1,d2;
//cin >> d1;//流提取
//cout << d1;//流插入
//运算符重载,运算符有几个操作数,重载函数就有几个参数
//如果是两个操作数,左操作数是第一个参数,右操作数是第二个参数
//operator<<写成成员函数,this指针默认占据了第一个位置,对象就要做做操作数
//那么用起来就不符合流特性,虽然可以用,但是不符合运算符原来的用法和特性
/*d1.operator<<(cout);
d1 << cout;*/
cin >> d1 >> d2;
cout << d1 << d2;
return 0;
}
友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
额外的补充点:
class A
{
public:
A(int a = 0)
:_a(a)
{}
private:
int _a;
};
class B
{
public:
B()
: _p(nullptr)
, _aa(100)
{}
private:
//给的是缺省值,这里不是定义
//这里只是声明,所以这里不是初始化
int _b = 0;
int* _p = (int*)malloc(sizeof(int)* 10);
A _aa = 10;
//静态变量不能在这里给缺省值,因为静态成员不在构造函数初始化
//要在类外面全局位置定义初始化
//static int _n = 10;
};
int main()
{
B bb;
return 0;
}
内部类
class A
{
private:
static int k;
int h;
public:
//B天生就是A的友元
class B
{
public:
void foo(const A& a)
{
cout << k << endl;//OK
cout << a.h << endl;//OK
}
};
};
int A::k = 1;
int main()
{
A::B b;
b.foo(A());
return 0;
}
以上是关于C++初阶类和对象的主要内容,如果未能解决你的问题,请参考以下文章