声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化
例题:统计A类型创造了多少个对象
class A
{
public:
A()
{}
A(const A& a)
{
}
private:
};
A Func()
{
A aa;
return aa;
}
int main()
{
A aa1;
A aa2;
Func();
return 0;
}
我们的方法之一就是统计构造函数调用了多少次,我们就需要全局变量
int n = 0;
class A
{
public:
A()
{
++n;
}
A(const A& a)
{
++n;
}
private:
};
A Func()
{
A aa;
return aa;
}
int main()
{
A aa1;
A aa2;
Func();
cout << n << endl;
return 0;
}
但是这个n是不好控制的,能不能把n封装到类里面,避免别人随意修改呢?
class A
{
public:
A()
{
++n;
}
A(const A& a)
{
++n;
}
private:
int n =0;
};
但是由于对象的不同,n就不是同一个n,那么如果我想要使同一个n++呢?
这里就需要设置静态变量
private:
static int n;
这个n就不属于某一个对象,而是属于所有对象,属于整个类,所以它的初始化不能放在初始化列表执行,那么它的初始化应该在哪里呢?所以需要在类外面定义:
class A
{
public:
A()
{
++n;
}
A(const A& a)
{
++n;
}
private:
static int n;
};
int A::n = 0;
这个n则受到类的限制,无法随意访问,如果想访问n,有几种办法:
class A
{
public:
A()
{
++n;
}
A(const A& a)
{
++n;
}
static int Getn()
{
return n;
}
private:
static int n;
};
讲解一下这里的静态成员函数static int Getn()
静态成员函数在类中有特殊的作用和行为。在上面的代码示例中,Getn
函数是一个静态成员函数,它的主要特点和用法包括:
A::Getn()
可以在没有创建 A
类对象的情况下调用,它提供了一种访问类静态成员(如 n
)的方式
A::getCreationCount()
),也可以通过类的对象调用
静态成员函数通常用于提供一些与类的任何特定实例无关的功能,或者访问静态成员变量,而不依赖于类的对象。在设计类时,如果某个函数的行为不需要依赖于对象的状态,那么就应该将其声明为静态的
类名::静态成员
或者 对象.静态成员
来访问public
、protected
、private
访问限定符的限制友元(Friend)在C++中是一个重要的概念,它允许某些特定的外部函数或类访问另一个类的私有(private)或受保护(protected)成员
我们在前面讲到各种运算符重载,以及赋值运算符重载,那么能不能直接重载<<
和>>
,实现输入和输出呢?
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
如果我们想用这两个运算符,就得自己实现重载
cout是ostream类的一个对象,运算符重载可以这样写:
void operator<<(ostream& out)
{
out << _year << "-" << _month << "-" << _day;
}
我们现在调用这个重载发现报错:
因为这里左操作数是d1,所以调用是这种方式:
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
void operator<<(ostream& out)
{
out << _year << "-" << _month << "-" << _day;
}
private:
int _year;
int _month;
int _day;
};
意味着我们必须这样调用:
d1<<cout;
不符合常规调用
作为成员函数重载,this指针占据了第一个参数,意味着Date必须是左操作数
所以,这个这个函数只能写为全局函数,而不能是成员函数
但是这里访问不了私有成员,我们先将其置为公有
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
//private:
int _year;
int _month;
int _day;
};
void operator<<(ostream& out,Date &d)
{
out << d._year << "-" << d._month << "-" <<d._day;
}
int main()
{
Date d1(2005, 6, 23);
cout << d1;
return 0;
}
这下可以进行访问
但是,这里并不能进行连续的输出,比如以下形式:
Date d1(2005, 6, 23);
Date d2(2024, 4, 2);
cout << d1<<d2;
我们可以这么理解,cout是从左向右进行的,与连续赋值相反的顺序,cout<<d1
,返回cout,返回值作为左操作数,再进行流插入,所以我们得增加ostream的返回值:
ostream& operator<<(ostream& out,const Date &d)
{
out << d._year << "-" << d._month << "-" <<d._day;
return out;
}
同理可以完成流提取:
istream& operator>>(istream& _cin, Date& d)
{
_cin >> d._year;
_cin >> d._month;
_cin >> d._day;
return _cin;
}
现在尝试去重载operator<<,然后发现没办法将operator<<重载成成员函数。因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以要将operator<<重载成全局函数。但又会导致类外没办法访问成员,此时就需要友元来解决。operator>>同理
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字
class Date
{
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;
};
特点:
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员
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;
};
如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限
内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元
class A
{
private:
static int k;
int h;
public:
class B // B天生就是A的友元
{
public:
void fun(const A& a)
{
cout << k << endl;//OK
cout << a.h << endl;//OK
}
};
};
int A::k = 1;
int main()
{
A::B b;
b.fun(A());
return 0;
}
B可以访问A的所有成员
特性:
public
、protected
、private
都是可以的。A::B b;
B这个类受到A类的类域的限制
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
有名对象:
A aa1;
A aa2(100);
匿名对象:
A();
A(11);
在这个代码示例中:
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
int main()
{
A();
return 0;
}
A();
这一行创建了一个 A
类的匿名对象。匿名对象是指在创建时没有被赋予一个变量名的对象。它们通常用于临时的操作,比如传递对象作为函数参数,或者从函数返回对象时不需要保留对象的名称
在这个特定的例子中,
A();
创建了一个A
类型的实例,但没有变量名与之关联。这意味着这个对象只在它被创建的那条语句中存在。一旦这条语句执行完毕,这个匿名对象的生命周期就结束了,它会被立即销毁。因此,紧接着构造函数的调用后,析构函数也会被立即调用
输出将会是:
A(int a)
~A()
class Solution {
public:
int Sum_Solution(int n) {
//...
return n;
}
};
int main()
{
Solution().Sum_Solution(10);
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;
}
我们在主函数设置几个变量来测试一下优化行为:
A aa1=2;
这里原本应该是构造加拷贝构造,在优化下合为构造:
int main()
{
const A& aa2 = 2;
return 0;
}
这里就只有构造,没有拷贝构造,没有进行优化,原因如下:
当你在C++中使用const A& aa2 = 2;
这行代码时,其实发生了一系列复杂的操作,它们体现了C++对于效率和对象生命周期管理的考量。下面是详细解释:
2
是一个整数字面量,它本身并不是A
类型的对象。当这行代码执行时,C++需要一个A
类型的对象来与aa2
绑定。因此,编译器查找A
类,找到了一个可以接受单个整数作为参数的构造函数A(int a = 0)
。使用这个构造函数,编译器创建一个匿名的A
类型的临时对象。这个过程中,构造函数被调用,打印出A(int a)
。
const A& aa2 = 2;
正是这样的情况。这行代码实际上告诉编译器:“创建一个临时的A
对象,并将aa2
作为这个临时对象的一个常量引用。” 因此,没有拷贝构造函数被调用,因为我们没有创建一个新的A
对象,只是创建了一个临时对象的引用
aa2
被绑定到这个对象上。由于aa2
是一个引用,它实际上并不拥有对象;它只是一个到临时对象的链接。因此,不需要调用拷贝构造函数来创建一个新的A
对象,这个机制避免了不必要的拷贝,提高了效率
aa2
,这个匿名的A
对象将会持续存在直到aa2
离开作用域。如果没有这个特性,临时对象将在表达式结束时立即被销毁,这将导致引用悬挂,引用一个已经不存在的对象
void f1(A aa)
{}
int main()
{
f1(A(2));
return 0;
}
这里传了一个匿名对象,先调用构造函数,再调用拷贝构造函数
编译器优化,优化为构造函数
A f2()
{
A aa;
return aa;
}
int main()
{
f2();
return 0;
}
f2应该有一个构造和一个拷贝构造
优化为构造函数
A f2()
{
A aa;
return aa;
}
int main()
{
A aa2 = f2();
return 0;
}
如果不优化,f2()
会有一个构造和一个拷贝构造,最后,这个临时副本将会被用来初始化main函数中的A aa2,再次调用拷贝构造函数,则这里是连续的拷贝构造函数
编译器优化为构造函数
A f2()
{
A aa;
return aa;
}
int main()
{
A aa1;
aa1 = f2();
return 0;
}
一个表达式中,连续拷贝构造+赋值重载,结果没有发生很大的优化
本节内容到此结束!感谢大家阅读!!!!