【C++】类和对象⑤(static成员 | 友元 | 内部类 | 匿名对象)

简介: 📚 C++ 知识点概览:探索类的`static`成员、友元及应用🔍。

🔥个人主页:Forcible Bug Maker

🔥专栏:C++

目录

前言

static静态成员

友元

友元函数

友元类

内部类

匿名对象

结语

前言
本篇主要内容:类和对象的一些知识点补充,包括static静态成员,友元,内部类等。

本篇基本上就是类和对象主要内容的收尾环节了,在前几篇中,已经将六大默认成员函数逐一做了介绍。接下来的内容就是补充一些语法和细节。那么开始我们今天的内容。

static静态成员
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化。

在C++中,static成员是类的成员,但它与类的任何特定对象实例都不关联。也就是说,不管创建了多少个类的对象,static成员都只有一个副本。

static成员可以是成员变量或成员函数。以下是关于static成员的一些关键点:

静态数据成员:静态数据成员在类的所有对象之间共享。这意味着无论创建了多少个对象,都只有一个静态数据成员的副本,且此副本存放在静态区。静态数据成员必须在类定义外部进行初始化,且定义时不添加static关键字,类中只是声明。如:

class MyClass {
public:
static int count; // 声明静态数据成员
};

int MyClass::count = 0; // 在类定义外部初始化静态数据成员
静态成员函数:静态成员函数是类的一个成员函数,它可以在没有类的实例的情况下调用。静态成员函数只能访问静态成员(包括静态成员变量和其他静态成员函数)。它们不能访问类的非静态成员,因为非静态成员需要类的实例才能存在,本质上说,静态成员函数没有隐藏的this指针,本身就是无法访问任何非静态成员的。如:

class MyClass {
public:
static int count;
static void GetCount() {
count++; // 可以访问静态数据成员
}
void doSomething() {
// 这里不能访问静态成员count,除非使用 MyClass::count
}
};
访问静态成员:你可以使用类名和作用域解析运算符( :: )来访问静态成员,无论是否创建了类的实例。例如,MyClass::count 和 MyClass::GetCount()。

用途:静态成员常用于实现计数器(如上述示例中的count),或者当你想在类的所有实例之间共享某些数据时。静态成员函数通常用于执行与类本身相关但不依赖于任何特定对象实例的操作。

注:虽然静态成员与类的实例不关联,但它们仍然属于类的一部分,并受类的访问访问限定符(如public、protected或private)的影响。

这里用一个面试题,引入关于静态成员变量的使用:实现一个类,计算程序中创建出了多少个对象:

class A
{
public:
A() { ++_scount; }
A(const A& t) { ++_scount; }
~A() { --_scount; }
static int GetACount() { return _scount; }
private:
static int _scount;
};
int A::_scount = 0;
void TestA()
{
cout << A::GetACount() << endl;
A a1, a2;
A a3(a1);
cout << A::GetACount() << endl;
}

此份代码中,声明定义了静态成员变量_scount,当调用类的构造函数或拷贝构造时,静态成员变量_scount就会++计数,当调用析构函数时,就会对其逐一--,我们可以调用TestA()函数,来观察在这个过程中累计创建了多少对象。

注:静态成员函数不可以直接调用非静态成员变量,因为其没有this指针及现成的实例化对象;非静态成员函数可以直接调用类的静态成员变量,非静态成员函数本身并不依赖于特定的对象状态,它可以通过类的作用域直接访问静态成员变量。

友元
友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
友元分为:友元函数和友元类。

友元函数
在前面Date类的博客中,曾提到过全局变量重载流插入(cout)和流提取(cin)操作符,由于插入和提取需要访问私有的成员变量,所以我们将这两个个全局函数设置为Date类的友元,如果感兴趣可以看看那篇博客关于友元的内容,这里就不赘述了。

地址放在这:【C++】日期类Date(详解)

说明:

友元函数可访问类的私有和保护成员,但不是类的成员函数
友元函数不能用const修饰
友元函数可以在类定义的任何地方声明,不受类访问限定符限制
一个函数可以是多个类的友元函数
友元函数的调用与普通函数的调用原理相同
友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。

友元类的一些特性:

友元关系是单向的,不具有交换性。比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
友元关系不能传递。如果C是B的友元, B是A的友元,则不能说明C时A的友元。
友元关系不能继承。在继承位置再给大家详细介绍。
概念浅显易懂,用起来也不麻烦。

class Time
{
// 声明日期类为时间类的友元类,则在日期类中就直接访问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;
};

在上述代码中,Date类被设置成了Time类的友元,所以在Date类的中成员函数中,可以直接使用Time类型的对象。

内部类
概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的非共有成员。外部类对内部类没有任何优越的访问权限。

注:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。

特性:

内部类定义在外部类的public、protected、private都是可以的。
注意内部类可以直接访问外部类中的static成员,不需要外部类的对象 / 类名。
sizeof(外部类) = 外部类成员所占内存合计,和内部类没有任何关系。
class A
{
private:
static int k;
int h;
public:
class B // B天生就是A的友元
{
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;
}

上述代码中,演示了这一系列特性,总的来说,B定义在A里面只是受A的类域限制,其他除了访问限定符影响类成员的访问之外,就和两个独立定义的类没什么区别了。当class B定义在private里时,就无法通过A::B bb;创建B类型的对象。

匿名对象
C++的匿名对象(也称为临时对象)是在代码中没有显式命名的对象。这种对象通常在创建后立即使用,并且在表达式结束时自动销毁(生命周期只存在于当前这一行)。匿名对象通常用于简化代码和提高可读性。

class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
class Solution {
public:
int Sum_Solution(int n) {
//...
return n;
}
};
int main()
{
A aa1;
// A aa1(); 不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义

// 我们可以这么定义匿名对象,匿名对象的特点是不用取名字,
// 但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数
A();
cout << endl;
A aa2(2);
// 匿名对象在这样场景下就很好用,当然还有一些其他使用场景,这个我们以后遇到了再说
Solution().Sum_Solution(10);
return 0;

}

以上代码中,给出了匿名对象的样例使用,作为匿名对象,其生命周期只存在于当前这一行,当到下一行时就会被销毁,编译器自动调用其析构函数。虽然只有一行的生命周期,其还是有用武之地的。比如说像上面那样通过匿名对象直接调用类的成员函数,不需要我们单独再创建一个对象。

关于匿名对象的使用可以做一个总结:

  1. 函数返回值:当函数返回一个对象时,如果该对象没有被赋值给任何变量,那么它就是一个匿名对象。

struct Foo {
Foo() { / ... / }
~Foo() { / ... / }
};

Foo createFoo() {
return Foo(); // 返回一个匿名对象
}

  1. 局部变量:在某些情况下,你可能希望创建一个局部变量但不为其命名。这通常发生在对象的生命周期非常短暂,且仅用于单个表达式时。

void doSomething() {
std::string("Hello, World!").size(); // 创建一个匿名std::string对象并获取其大小
}

  1. 直接初始化:在直接初始化中,你可以使用匿名对象来初始化另一个对象。

std::string str = std::string("Hello, World!"); // 使用匿名对象初始化str
注:由于匿名对象的生命周期很短,如果你在它们的生命周期之外访问它们,或者依赖于它们的特定销毁行为(例如,释放资源),那么可能会出现问题。因此,在使用匿名对象时,务必确保你了解其生命周期和销毁行为。

结语
到这里本篇博客的内容基本上就结束了,类和对象到这里基本上算是基本掌握,算是迈过了C++的第一道坎。我们今天补充了类和对象的一些语法细节,包括static静态成员:没有this指针,所有对象共用;友元:包括友元函数和友元类;还提到了内部类:内部类天生是外部类的友元,但是外部类却不是内部类的友元;最后说到了匿名对象:声明周期只存在于当前一行。在下篇博客中,我们会提及C++内存管理相关的内容,敬请期待!

博主后续还会产出更多有意思的内容,感谢大家的支持!♥

相关文章
|
3天前
|
存储 C++
【C++】string类的使用③(非成员函数重载Non-member function overloads)
这篇文章探讨了C++中`std::string`的`replace`和`swap`函数以及非成员函数重载。`replace`提供了多种方式替换字符串中的部分内容,包括使用字符串、子串、字符、字符数组和填充字符。`swap`函数用于交换两个`string`对象的内容,成员函数版本效率更高。非成员函数重载包括`operator+`实现字符串连接,关系运算符(如`==`, `&lt;`等)用于比较字符串,以及`swap`非成员函数。此外,还介绍了`getline`函数,用于按指定分隔符从输入流中读取字符串。文章强调了非成员函数在特定情况下的作用,并给出了多个示例代码。
|
3天前
|
C++
【C++】string类的使用④(常量成员Member constants)
C++ `std::string` 的 `find_first_of`, `find_last_of`, `find_first_not_of`, `find_last_not_of` 函数分别用于从不同方向查找目标字符或子串。它们都返回匹配位置,未找到则返回 `npos`。`substr` 用于提取子字符串,`compare` 则提供更灵活的字符串比较。`npos` 是一个表示最大值的常量,用于标记未找到匹配的情况。示例代码展示了这些函数的实际应用,如替换元音、分割路径、查找非字母字符等。
|
3天前
|
编译器 C++
【C++】string类的使用④(字符串操作String operations )
这篇博客探讨了C++ STL中`std::string`的几个关键操作,如`c_str()`和`data()`,它们分别返回指向字符串的const char*指针,前者保证以&#39;\0&#39;结尾,后者不保证。`get_allocator()`返回内存分配器,通常不直接使用。`copy()`函数用于将字符串部分复制到字符数组,不添加&#39;\0&#39;。`find()`和`rfind()`用于向前和向后搜索子串或字符。`npos`是string类中的一个常量,表示找不到匹配项时的返回值。博客通过实例展示了这些函数的用法。
|
8天前
|
C++
【C++】日期类Date(详解)②
- `-=`通过复用`+=`实现,`Date operator-(int day)`则通过创建副本并调用`-=`。 - 前置`++`和后置`++`同样使用重载,类似地,前置`--`和后置`--`也复用了`+=`和`-=1`。 - 比较运算符重载如`&gt;`, `==`, `&lt;`, `&lt;=`, `!=`,通常只需实现两个,其他可通过复合逻辑得出。 - `Date`减`Date`返回天数,通过迭代较小日期直到与较大日期相等,记录步数和符号。 ``` 这是236个字符的摘要,符合240字符以内的要求,涵盖了日期类中运算符重载的主要实现。
|
3天前
|
C++
C++】string类的使用③(修改器Modifiers)
这篇博客探讨了C++ STL中`string`类的修改器和非成员函数重载。文章介绍了`operator+=`用于在字符串末尾追加内容,并展示了不同重载形式。`append`函数提供了更多追加选项,包括子串、字符数组、单个字符等。`push_back`和`pop_back`分别用于在末尾添加和移除一个字符。`assign`用于替换字符串内容,而`insert`允许在任意位置插入字符串或字符。最后,`erase`函数用于删除字符串中的部分内容。每个函数都配以代码示例和说明。
|
3天前
|
安全 编译器 C++
【C++】string类的使用②(元素获取Element access)
```markdown 探索C++ `string`方法:`clear()`保持容量不变使字符串变空;`empty()`检查长度是否为0;C++11的`shrink_to_fit()`尝试减少容量。`operator[]`和`at()`安全访问元素,越界时`at()`抛异常。`back()`和`front()`分别访问首尾元素。了解这些,轻松操作字符串!💡 ```
|
3天前
|
存储 编译器 Linux
【C++】string类的使用②(容量接口Capacity )
这篇博客探讨了C++ STL中string的容量接口和元素访问方法。`size()`和`length()`函数等价,返回字符串的长度;`capacity()`提供已分配的字节数,可能大于长度;`max_size()`给出理论最大长度;`reserve()`预分配空间,不改变内容;`resize()`改变字符串长度,可指定填充字符。这些接口用于优化内存管理和适应字符串操作需求。
|
3天前
|
C++ 容器
【C++】string类的使用①(迭代器接口begin,end,rbegin和rend)
迭代器接口是获取容器元素指针的成员函数。`begin()`返回首元素的正向迭代器,`end()`返回末元素之后的位置。`rbegin()`和`rend()`提供反向迭代器,分别指向尾元素和首元素之前。C++11增加了const版本以供只读访问。示例代码展示了如何使用这些迭代器遍历字符串。
|
3天前
|
存储 编译器 C语言
【C++】string类的使用①(默认成员函数
本文介绍了C++ STL中的`string`类,它是用于方便地操作和管理字符串的类,替代了C语言中不便的字符数组操作。`string`基于`basic_string`模板,提供类似容器的接口,但针对字符串特性进行了优化。学习资源推荐[cplusplus.com](https://cplusplus.com/)。`string`类提供了多种构造函数,如无参构造、拷贝构造、字符填充构造等,以及析构函数和赋值运算符重载。示例代码展示了不同构造函数和赋值运算符的用法。
|
3天前
|
C++
C++基础知识(四:类的学习)
类指的就是对同一类对象,把所有的属性都封装起来,你也可以把类看成一个高级版的结构体。