彻底搞懂static成员和友元函数
1)static成员
在C语言里static可以修饰变量和函数:
函数: 改变函数的链接属性,表明该函数只能在当前文件中使用
全局变量: 该变量具有文件作用域 局部变量: 该变量变为全局变量
C++里static修饰成员变量之后:该变量变成类的属性
下面看一段代码:如果想求程序中到底创建出了多少个类对象?
class A
{
public:
A() //构造函数
{
_count++;
}
A(const A& tmp) //拷贝构造
{
_count++;
}
static int _count; //static修饰的成员变量
};
int A::_count = 0; //static修饰的成员变量在类外进行初始化,且初始化时不带static
int main()
{
cout << A::_count << endl;
A a1, a2;
A a3(a1);
cout << A::_count << endl;
cout << a1._count << endl;
return 0;
}
运行结果:
0
3
3
在类中我们定义了一个被static修饰的成员变量_count,每创建出一个新的对象_count都++,那为什么对象a1里的_count也变为了3呢?
static修饰成员变量的细则:
1.静态成员(_count)为所有类对象所共享,不属于某个具体的对象,因此a1中_count也变成3
2.静态成员变量必须在类外进行初始化,没有包含在具体的对象中且初始化时不加static。例:int Date::_count = 0;
3.是类的属性因此不影响sizeof的值,sizeof(A)=1,没有成员变量,为什么是1之前博客有讲空类大小
4.static修饰的成员变量在程序开始执行识别类时就创建出来了,生命周期和程序一致
上述代码中_count静态成员变量一般设置为private,但这样就无法在主函数中输出_count的值。下面介绍static修饰的成员函数
static修饰的成员函数:
class A
{
public:
A()
{
_count++;
}
A(const A& tmp)
{
_count++;
}
static int GetACount() //static修饰的成员函数,没有隐藏的this指针
{
return _count;
}
private:
static int _count; //static修饰的成员变量
};
int A::_count = 0;
int main()
{
cout << A::GetACount() << endl;
A a1, a2;
A a3(a1);
cout << A::GetACount() << endl;
cout << sizeof(A);
return 0;
}
运行效果和上面一样,_count的值通过静态成员函数static int GetACount()返回,注意必须是静态成员函数。
static修饰成员函数的细则:
1.静态成员函数没有隐藏的this指针,因此不能访问任何非静态成员
2.静态成员函数内不能访问非静态的函数,因为静态成员函数没有this指针,而非静态有
3.非静态成员函数内可以访问静态的成员函数,例:void print(){cout<<GetACount()<<endl;}
2)运算符重载的左右操作数
先看个例子:重载操作符>>
class A
{
public:
A(int data) //构造函数
{
_data = data;
}
int GetData()
{
return _data;
}
ostream& operator<<(ostream& _cout) //在类内重载<<操作符,cout是右操作数,隐藏的this为左操作数
{
cout << _data;
return _cout;
}
private:
int _data;
};
int main()
{
A a1(1);
cout<<a1; //这条语句会直接报错,没有与这些操作数匹配的<<操作符
a1<<cout; //这样可以正常输出data的值
return 0;
}
运行结果: 1
cout<<a1; 这条语句编译器会直接报错,因为ostream& operator<<(ostream& _cout);成员函数中默认第一个操作数是this指针,所以cout要放在对象a1的后面
a1<<cout; 可以正常输出,但我们更习惯用第一种,把操作符重载在类外定义可以实现
在类外实现操作符<<重载:
class A
{
public:
A(int data) //构造函数
{
_data = data;
}
int _data;
};
ostream& operator<<(ostream& _cout,const A& tmp) //类外重载<<
{
cout << tmp.;
return _cout;
}
int main()
{
A a1(1);
cout<<a1<<endl; //可实现和上面一样的效果
return 0;
}
这里需要注意:
1.ostream& operator<<(ostream& _cout,const A& tmp);前面返回类型必须是ostream&,不然无法做到连续输出,例:cout<<a1<<endl<<a2;
2.这里是对参数为类对象进行了重载,也就是说 cout<<a1<<1<<endl;中1和endl还是调用原来系统封装的
3.在类外定义的函数没有办法直接访问类的私有成员,所以data设置成了public,或者你也可以在类中写GetData函数,但这样浪费时间空间
下面引出友元可以使类外函数,引用类内私有成员
3)友元
概念:
友元分为:友元函数和友元类
友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
class A
{
friend ostream& operator<<(ostream& _cout, const A& tmp); //声明了操作符重载为友元函数
public:
A(int data) //构造函数
{
_data = data;
}
private:
int _data; //_data被设置成私有
};
ostream& operator<<(ostream& _cout,const A& tmp) //可以直接访问类内的私有成员
{
cout << tmp._data;
return _cout;
}
int main()
{
A a1(1);
cout << a1 << endl;
return 0;
}
运行结果: 1
关于友元函数需要注意的是:
1.友元函数可以直接访问类的私有成员和受保护成员
2.友元函数不是类的成员函数,所以也就没有this指针
3.一个函数可以是多个类的友元
4.友元可以在类的任何地方声明,不受类的访问限定符限制
5.友元类不是相互的,也不具有传递性。例:声明Date类为Time类的友元类,在Date类中可以访问Time类的私有成员,但Time不能访问Date的私有
内部类:
是一种特殊的友元类,一个类定义在一个类的内部,则内部类就是外部类的友元类。
注意:他们不具有相互性,也就是内部类可以访问外部类私有,反之外部类就不行。
class A {
private:
static int k;
int h;
public:
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;
}
运行结果:
1
0
这里内部类直接访问输出了外部类的成员
需要注意的是:
1.内外部类友元不具有相互性
2.外部类的大小跟内部类没有关系。例:sizeof(A);结果是4
3.内部类可以定义在外部类的public、protected、private都可以
4)对象的初始化
在创建一个对象时,编译器会调用构造函数给成员变量一个合适的初值,但这个操作并不是初始化,而是赋值!
下面这个代码可以解释:
class Date
{
public:
Date(int year, int month,int day,int time)
{
_year = year;
_month = month;
_year = month; //给_year赋值两次
_day = day; //const修饰的
_time = time; //引用类型
}
private:
int _year;
int _month;
const int _day;
int& _time;
};
int main()
{
Date A(2020, 10, 30 , 10);
return 0;
}
这里会直接报错,构造函数内的并不是初始化!
初始化列表: 以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
Date(int year, int month)
:_year(year)
,_month(month)
//_month(year) //这里初始化两次直接会报错
,_day(day) //对const修饰的进行初始化
,_time(time) //对引用类型进行初始化
{
_year=15;
_month=14; //在构造函数体内
_day=5; //这是赋值
}
初始化规则:
1.必须在构造函数初始化列表位置进行初始化的成员:引用类型的成员,const类型的成员,A中包含B的对象,并且B只具有非全缺省构造函数
2.初始化顺序必须和声明时,成员变量的顺序一致。若定义和初始化顺序不同,则会按定义顺序来进行初始化。
5)总结
1.static修饰的成员变量一定在类外初始化!!
2.const类型,引用类型的成员变量必须在初始化列表初始化
3.再次理解封装,C++通过类,将一个对象的所有属性打包到一起,通过访问限定符选择性的将其部分功能开放出来与其他对象进行交互,外部用户不需要知道实现细节,只需要知道怎么去用就好了