类和对象小知识点
一、static成员
1. 概念
类的静态成员:声明为static的类成员。
- 静态成员变量:用static修饰的成员变量。
- 静态成员函数:用static修饰的成员函数。
注:静态成员变量要在类外进行初始化
eg:计算程序创建对象数量
class A
{
public:
A(){++_scount;}
A(const A& t){++_scount;}
~A(){--_scount;}
//写这个静态成员函数是因为_scount是私有的,类外不能访问,可以调用该静态成员函数获取
static int GetCount()
{
return _scount;
}
private:
//静态成员变量是属于类本身的,而不属于类的具体对象实例
//所以不能在初始化列表对静态成员变量进行初始化(每次实例化都要调用初始化列表)
static int _scount;
};
//全局定义,不能像成员变量一样在初始化列表定义
int A::_scount = 0;
int main()
{
cout << A::GetCount() << endl;
A a1, a2;
A a3(a1);
cout << A::GetCount() << endl;
return 0;
}
//运行结果:0 3
2. 特性
- 静态成员被所有类对象所共享,不属于某个具体的对象,存放在静态区。
- 静态成员变量必须在类外定义,定义不添加static关键字,类中只是声明。
- 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问。
- 静态成员函数没有隐藏的this指针 ,不能访问任何非静态成员(成员变量和成员函数)。
- 静态成员也是类成员,所以受访问限定符限制。
eg1:设计一个类,在类外面只能在栈和堆上创建对象
class A
{
public:
//在私有化构造函数的同时,在类中创建了两个静态成员函数,用来调用创建在栈和堆上的对象
//栈
static A GetStackObj()
{
A aa;
return aa;
}
//堆
static A* GetHeapObj()
{
return new A;
}
private:
//把构造函数私有化,因为创建对象就要调用构造函数
A(){}
private:
int _a1 = 1;
int _a2 = 2;
};
int main()
{
//static A aa1; //静态区
//A aa2; //栈区
//A* ptr = new A; //堆区
A* ptr = A::GetHeapObj();
cout << ptr << endl;
A::GetStackObj();
return 0;
}
3. 小结
- 静态成员函数不能调用非静态成员。原因:没有this指针,无法在内部调用成员操作。
- 非静态成员函数可以访问类的静态成员。
- 静态变量也可以是自定义类型成员
成员变量和静态成员变量的区别:
成员变量:属于每一个类对象,存储对象里
静态成员变量:属于类,属于类的每个对象共享,存储在静态区。(特性第一条)
二、explicit
1. 引入
eg1:单参构造
class A
{
public:
A(int a)
:_a(a)
{}
private:
int _a;
};
int main()
{
A aa1(1);
A aa2 = 2; //ok,如果构造函数加上explicit(explicit A(int a))这里就运行error
}
代码分析:
eg2:多参构造,但除第一个参数别的参数都有缺省值
class A
{
public:
explicit A(int a, int b = 10)
:_a(a)
{}
// A(int a, int b = 10)
// :_a(a)
// {}
private:
int _a;
int _b;
};
int main()
{
A aa1(1);
A aa2 = 2; //error explicit修饰构造函数,禁止了类型转换
return 0;
}
2. explicit作用
- 构造函数不仅可以构造和初始化对象,还具有类型转换的作用。对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数
- 无论单参还是除第一个参数无默认值其余均有默认值的 构造函数,被explicit修饰,该构造函数就不具备隐式转换的作用
三、友元
提供了一种突破封装的方式,提供了便利的同时,破坏了封装增加了耦合度。不宜多用
友元分为:友元函数和友元类
1. 友元函数
①引入
- 问题:在重载 <<(流插入)和 >>(流提取)运算符时,发现把该运算符重载成成员函数,不符合正常使用规则。
- 原因:cout输出流对象和隐含的this指针抢占第一个参数的位置。this指针默认是第一个参数(左操作数),但使用时cout需要是第一个形参对象,这样才能正常使用。
- 解决方式:重载成全局函数,但是引出另一个问题就是类外没办法访问类中的私有成员,所以就使用友元解决,这样就可以访问类里的私有成员
eg1:类内重载operator<<
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{}
//因为成员函数第一个参数一定是this,所以已经在第一个参数的位置
//但是这样不符合调用流插入操作符的常规使用
//难道调用时这样使用吗 -> d << cout; -> d.operator<<(&d, cout);
//所以要重载成全局的
ostream& operator<<(ostream& _cout)
{
_cout << _year << "-" << _month << "-" << _day << endl;
return _cout;
}
private:
int _year;
int _month;
int _day;
};
将 <<(流插入)和 >>(流提取)运算符重载成全局函数。但是会产生另一个问题,在类外没办法访问类里的成员。这时出现了友元函数。
eg2:重载成全局的——使用友元解决问题
class Date
{
//friend关键字+函数声明,就可以访问类里的私有成员
friend ostream& operator<<(ostream& _cout, const Date& d);
friend istream& operator>>(istream& _cin, Date& d);
public:
Date(int year = 1900, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{
_cin >> d._year >> d._month >> d._day;
return _cin;
}
int main()
{
Date d;
cin >> d;
cout << d << endl;
return 0;
}
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要+关键字friend
②小结
- 友元函数可以访问类的私有成员和保护成员,但不是类的成员函数
- 友元函数不能用const修饰。原因:没有this指针
- 友元函数可以在类的任何地方声明,不受访问限定符限制
- 一个函数可以是多个类的友元
- 友元函数的调用和普通函数的原理一致
2. 友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类的中的非公有成员。
class Time
{
friend class Date; //声明日期类是时间类的友元类。
public:
Time(int hour = 0, int minute = 0, int second = 0)
:_hour(hour)
,_minute(minute)
,_second(second)
{}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
void SetTimeOfDate(int hour, int minute, int second)
{
//访问时间类私有成员变量
_t._hour = hour;
_t._minute = minute;
_t._second = second;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
小结:
- 友元关系是单向的。eg:C类中声明B类为友元类,所以B类可以直接访问C类的私有成员,但是C类不能访问B类的私有成员
- 友元关系不能传递。eg:C类是B类的友元,B类是A类的友元,但是不能说C类是A类的友元
- 友元关系不能继承
四、内部类
1. 概念
内部类: 一个类定义在另一个类内部,这个在内部的类就是内部类。
注:
- 内部类是一个独立的类,不属于外部类,不能通过外部类的对象去访问内部类的成员,外部类对内部类没有任何优越的访问权限。
- 内部类是外部类的友元类,内部类可以通过外部类的对象参数来访问外部类的所有成员。但外部类不是内部类的友元
2. 特性
- 内部类可以定义在外部类的public、protected、private。但是内部类受外部类访问限定符限制。
- 内部类可以直接访问外部类的static成员,不需要外部类的对象或类名。
- sizeof(外部类) = 外部类。和内部类没有关系
eg:
class A
{
public:
class B //B天生就是A的友元
{
public:
void F(const A& a)
{
cout << k << endl;
cout << a.h << endl;
}
private:
int b;
};
private:
static int k;
int h = 10;
};
int A::k = 1;
int main()
{
//静态变量不被计算在类的大小中。
//静态变量属于类不属于实例,无论创建多少个类的实列,静态变量始终只占用一块内存
cout << sizeof(A) << endl; //4
//B b; 不能直接这样定义
A::B b;
b.F(A()); //A() ——> 匿名对象
return 0;
}
五、匿名对象
1. 引入
匿名对象特征:
- 匿名对象不用起名字。
- 匿名对象的声明周期只有定义的一行,下一行他就会自动调用析构。
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a = 0)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
int main()
{
A aa1(1); //有名对象
A(2); //匿名对象
return 0;
}
2. 使用
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a = 0)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
class Solution
{
public:
int Sum_Solution(int n)
{
return n;
}
};
int main()
{
//匿名对象使用
Solution().Sum_Solution(10);
//A& ra = A(1); //err 匿名对象具有常性
//使用const引用会延长匿名对象的生命周期,生命周期变成当前对象的局部域,
//相当于引用的生命周期
const A& ra = A(1);//ok
return 0;
}
六、拷贝对象是的编译器优化
在传参和传返回值的过程中,一般编译器会做优化,减少拷贝提高效率
class A
{
public:
// 构造
A(int a = 0)
: _a(a)
{
cout << "A(int a)" << endl;
}
// 拷贝构造
A(const A &aa)
: _a(aa._a)
{
cout << "A(const A& aa)" << endl;
}
// 赋值运算符重载
A &operator=(const A &aa)
{
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa)
{
_a = aa._a;
}
return *this;
}
// 析构
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
void f1(A aa)
{
}
A f2()
{
A aa;
return aa;
}
int main()
{
// 传值传参
A aa1;
f1(aa1);
cout << endl;
// 传值返回
f2();
cout << endl;
// 隐式类型,连续构造+拷贝构造->优化为直接构造
f1(1);
// 一个表达式中,连续构造+拷贝构造->优化为一个构造
f1(A(2));
cout << endl;
// 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造
A aa2 = f2();
cout << endl;
// 一个表达式中,连续拷贝构造+赋值重载->无法优化
aa1 = f2();
cout << endl;
return 0;
}
总结
面向对象的理解:类是对某一类实体(对象)来进行描述的,描述该对象具有那些属性,那些方法,描述完成后就形成了一种新的自定义类型,才用该自定义类型就可以实例化具体的对象