1.static成员
1.1概念
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态的成员变量一定要在类外进行初始化。
1.2特性
- 静态成员变量必须在类外定义,定义时不添加static关键字。
- 类的静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问。
- 静态成员和类的普通成员一样,也有public、protected、private 3 种限定,也可以具有返回值,const修饰符等。
- 静态成员为所有类对象所共享,不属于某个具体的实例(对象),不占用类空间(在全局区存储)即这个类所实例的所有对象都可以操作静态成员(即在一个类定义好之后, 静态成员就已经在全局区存储了)。
- 静态成员函数没有隐藏的this指针,所以不能访问任何非静态成员。但要如果想访问非静态变量, 传个参数就可以, 一般传个对象的引用, 例如这样:
static void test(Test& a){ a.a=10;}//传对象的引用访问非静态变量
静态成员函数是为了直接操作静态成员变量的, 因为为了保证封装性。
//实现一个类,计算中程序中创建除了多少个类对象。
class A {
static int m_s_count;//静态成员 m_s_count
int m_n;
public:
A()
{
++m_s_count;
}
A(const A& t)
{
++m_s_count;
}
static int GetACount()
{
return m_s_count;
}
};
int A:: m_s_count= 0;//类外定义静态成员
void TestA()
{
cout<< A::GetACount()<< endl;//0
A a1, a2;
A a3(a1);
cout << A::GetACount() << endl;//3
cout << a1.GetACount() endl;//3
}
- 静态成员函数可以调用非静态成员函数吗?
大多数时候不可以, 但当静态成员函数参数有本类对象或是本类对象的引用(或指针)时, 也可以调用,但这严格意义上来说并不是静态成员函数调用的。
- 非静态成员函数可以调用类的静态成员函数吗?
可以, 静态成员属于类不属于对象, 静态成员在类内部相当于一个局部的全局变量。
2.C++11 的成员初始化新玩法
C++11支持非静态成员变量在声明时,直接初始化。
#include<iostream>
using namespace std;
class A
{
public:
int m_c;
B(int c = 0) :m_c(c)
{
}
};
class B
{
// 非静态成员变量,可以在成员声明时,直接初始化。
int m_a = 10;
B m_b = 20;
int* m_p = (int*)malloc(4);
static int m_n;
public:
void PrintA();
};
int A::m_n = 0;
void A::PrintA()
{
cout << m_a << endl;
cout << m_p << endl;
cout << m_n << endl;
}
int main()
{
A a;
a.PrintA();
system("pause");
return 0;
}
3.explicit关键字
如下代码:
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 0) :_year(year)
{
cout << "调用构造函数" ;
cout << "year的值为:" << _year << endl;
}
Date& operator=(Date&& t)//拷贝构造函数
{
_year = t._year;
cout << "调用拷贝构造函数"<<endl;
return *this;
}
void Print_Date();
private:
int _year;
};
void Date::Print_Date()
{
cout << _year << endl;
}
int main()
{
Date d1;
d1 = 2019;
cout << "打印year" << endl;
d1.Print_Date();
system("pause");
return 0;
}
我们只定义了一个类的对象d1, 结果却调用了两次构造函数, 第一次构造函数调用时形参year=0, 可以看出应该是定义对象a 时所调用, 第二次调用构造函数形参值为100, 是谁调用的呢?
构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用。在a = 100;这个语句运行时,编译器将int 型的100隐式转化成了自定义的Date类型, 强转时调用了构造函数。然后调取编译器生成的赋值运算符重载函数给对象a赋值 。
这样直接用等号给对象赋值让人很别扭, 且可读性不好, 所以我们可以用C++中的关键字explicit修饰构造函数,此时将会禁止单参构造函数的隐式转换 ,我们给构造函数加上explicit再来看。
public:
explicit Date(int year = 0) :_year(year)
{
cout << "调用构造函数" ;
cout << "year的值为:" << _year << endl;
}
4.友元
友元分为:友元函数和友元类,友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
不管是友元函数或友元类, 在声明是前用关键字friend 来修饰。
4.1友元函数
友元函数可以直接访问类的所有成员(包括私有成员),但它是类外部的普通函数,不属于类,但需要在类的内部声明, 在类内声明, 类外实现时不需要类的作用域限定(也不加friend, 这是因为本质上友元函数就是一个普普通通的函数而已 ) , 都是因为友元函数不属于类。
重载operator<<
#include<iostream>
using namespace std;
class A
{
int m_a;
public:
explicit A(int a = 0) :m_a(a)
{}
void PrintA();
ostream& operator<<(ostream& os)
{ //第一个形参是隐藏的this指针
os << m_a << endl;
return os;
}
};
void A::PrintA()
{
cout << m_a << endl;
}
int main()
{
A a(28);
cout << "打印a\n";
a << cout;
//或者
a.operator<<(cout);
system("pause");
return 0;
}
现在我们尝试去重载operator<<,然后发现我们没办法将operator<<重载成成员函数。因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。
this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以我们要将operator<<重载成全局函数。
但是这样的话,又会导致类外没办法访问成员,那么这里就需要友元来解决。operator>>同理。
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。
#include<iostream>
using namespace std;
class A
{
int m_a;
public:
explicit A(int a = 0) :m_a(a)
{}
friend ostream& operator<<(ostream& os, A& t);//友元函数在类内部声明
friend istream& operator>>(istream& os, A& t);
void PrintA();
};
void A::PrintA()
{
cout << m_a << endl;
}
//重载为友元函数时,参数个数=原操作数个数,且至少应该有一个自定义类型的形参
ostream& operator<<(ostream& os, A& t) //类外定义 不受类访问限定符限制
{
os << t.m_a << endl;
return os;
}
istream& operator>>(istream& os, A& t)
{
os >> t.m_a;
return os;
}
int main()
{
A a;
cout << "输入a的数据\n";
cin >> a;
cout << "打印a\n";
cout << a;
operator<<(cout, a);
system("pause");
return 0;
}
1.友元函数可访问类的私有成员,但不是类的成员函数。
2.友元函数不能用const修饰。
3.友元函数可以在类定义的任何地方声明,不受类访问限定符限制。
4.一个函数可以是多个类的友元函数。
5.友元函数的调用与普通函数的调用和原理相同。
4.2友元类
#include<iostream>
using namespace std;
class Date;//前置声明
class Time
{
friend class Date;//声明Date为Time的友元类,Date类中可以直接访问Time类的私有成员
public:
Time(int hour = 11, int minutes = 28, int second = 34)
:_hour(hour), _minutes(minutes), _second(second)
{}
void Print_Time();
private:
int _hour;
int _minutes;
int _second;
};
void Time::Print_Time()
{
cout << _hour << "时" << _minutes << "分" << _second << "秒" << endl;
}
class Date
{
public:
Date(int year=2019,int month=04,int day=07)
:_year(year),_month(month),_day(day)
{}
void Print_Date();
void SetTimeofDate(int hour, int minutes, int second);
private:
int _year;
int _month;
int _day;
Time _t;
};
void Date::SetTimeofDate(int hour, int minutes, int second)
{
_t._hour = hour;
_t._minutes = minutes;
_t._second = second;
}
void Date::Print_Date()
{
cout << _year << "年" << _month << "月" << _day << "日";
_t.Print_Time();
}
int main()
{
Date d1(2019, 11, 11);
d1.SetTimeofDate(9, 30, 20);
d1.Print_Date();
system("pause");
return 0;
}
- 友元类的所有成员函数都可以是一个类的友元函数,都可以访问另一个类中的非公有成员。
- 友元关系是单向的,不具有交换性。
比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。- 友元关系不能传递 , 如果B是A的友元,C是B的友元,则不能说明C时A的友元。
4.2内部类
如果一个类定义在另一个类的内部,这个内部类就叫做内部类。注意此时这个内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去调用内部类。外部类对内部类没有任何优越的访问权限。
内部类就是外部类的友元类,但是外部类不是内部类的友元。
#include<iostream>
using namespace std;
class Time
{
public:
Time(int hour = 11, int minutes = 28, int second = 34)
:_hour(hour), _minutes(minutes), _second(second)
{}
void Print_Time(const Time& t)const;
class Date//Date类为Time类的内部类(友元类) 可以访问Time类中的成员
{
public:
Date(int year = 2019, int month = 04, int day = 07)
:_year(year), _month(month), _day(day)
{}
void Print_Date(const Time& t);//函数传外部类引用
void SetTimeofDate(Time& t,int hour, int minutes, int second);
private:
int _year;
int _month;
int _day;
//Time t 不能在内部类中定义外部类的对象, 所以内部类访问外部类必须要以函数传外部类引用的方式进行
};
private:
int _hour;
int _minutes;
int _second;
};
void Time::Print_Time(const Time& t)const
{
cout << _hour << "时" << _minutes << "分" << _second << "秒" << endl;
}
void Time::Date::SetTimeofDate(Time& t, int hour, int minutes, int second)
{
t._hour = hour;
t._minutes = minutes;
t._second = second;
}
void Time::Date::Print_Date(const Time& t)
{
cout << _year << "年" << _month << "月" << _day << "日";
t.Print_Time(t);
}
int main()
{
Time::Date d1(2019, 11, 11);
Time t;
d1.SetTimeofDate(t,9, 30, 20);
d1.Print_Date(t);
system("pause");
return 0;
}
特性
- 内部类可以定义在外部类的public、protected、private都是可以的。受访问限定符的限定。
- 注意内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名。
- sizeof(外部类)=外部类,和内部类没有任何关系。
5.理解封装
C++是基于面向对象的程序,面向对象有三大特性即:封装、继承、多态。
C++通过类,将一个对象的属性与行为结合在一起,使其更符合人们对于一件事物的认知,将属于该对象的所有东西打包在一起;通过访问限定符选择性的将其部分功能开放出来与其他对象进行交互,而对于对象内部的一些实现细节,外部用户不需要知道,知道了有些情况下也没用,反而增加了使用或者维护的难度,让整个事情复杂化。