网站设计需要多少钱,广州市律师网站建设怎么样,分类信息源码cms,贡井区建设局网站上一章是类与对象#xff08;4#xff09;-CSDN博客 深入了构造函数和静态成员#xff0c;大概讲解了类型转换
最后一章最后一章
友元
在 C 中#xff0c;友元提供了一种突破类的访问控制#xff08;封装#xff09;的方式。通过友元#xff0c;外部的函数或类可以访…上一章是类与对象4-CSDN博客 深入了构造函数和静态成员大概讲解了类型转换
最后一章最后一章
友元
在 C 中友元提供了一种突破类的访问控制封装的方式。通过友元外部的函数或类可以访问类的私有成员和保护成员。友元分为友元函数和友元类。在类声明中使用 friend 关键字来声明友元。
友元函数 定义友元函数并不是类的成员函数但它可以访问类的私有和保护成员。友元函数是在类的外部定义的独立函数通过 friend 声明告诉编译器这个函数可以访问类的私有成员。 声明位置友元函数的声明位置可以在类定义中的任何地方。它不受类的访问权限如 private、protected、public的限制可以在类定义中的任意位置声明。 作用友元函数的主要作用是允许外部函数访问类的私有成员从而为一些特殊的需求提供便利。由于它不是类的成员函数所以它不属于类的一部分但是它拥有对类私有成员的访问权限。
class Box {
private:int length;
public:Box() : length(0) {}// 友元函数声明friend void setLength(Box b, int len);
};// 友元函数定义
void setLength(Box b, int len) {b.length len; // 可以访问 Box 类的私有成员
}多重友元一个函数可以是多个类的友元函数。例如func1 可以是 ClassA 和 ClassB 的友元函数可以同时访问这两个类的私有成员。
class ClassA;
class ClassB;void func1(ClassA a, ClassB b); // 友元函数可以访问多个类的私有成员友元类 定义友元类是一个类它可以访问另一个类的私有和保护成员。当一个类是另一个类的友元类时友元类的所有成员函数都有权访问被友元类的私有成员。友元类的关系也是单向的即如果 ClassA 是 ClassB 的友元类那么 ClassB 并不是 ClassA 的友元类。 关系友元类的成员函数可以访问另一个类的私有成员意味着友元类可以通过它的成员函数来操作其他类的私有数据。即使 ClassA 和 ClassB 中有相同的私有成员友元类的成员函数仍然可以直接访问。
class ClassB;class ClassA {
private:int dataA;
public:ClassA() : dataA(0) {}// 将 ClassB 作为友元类friend class ClassB;
};class ClassB {
public:void modify(ClassA a) {a.dataA 10; // 可以访问 ClassA 的私有成员}
};友元关系的特点 单向性友元关系是单向的。比如ClassA 是 ClassB 的友元类但这并不意味着 ClassB 也是 ClassA 的友元类。要实现双向友元关系必须显式地将另一个类声明为友元。 不可传递性友元关系是不可传递的。即使 ClassA 是 ClassB 的友元ClassB 又是 ClassC 的友元这并不意味着 ClassA 是 ClassC 的友元。友元关系不能像继承一样传递。 友元函数和友元类的访问权限友元函数和友元类的成员函数可以访问类的私有成员和保护成员。虽然它们不是类的成员但它们被授权访问类内部的实现细节。 增加耦合度友元关系虽然可以提供便利但也会使得类之间的耦合度增加。特别是当多个类间相互成为友元时会破坏类的封装性因为这会使得类的内部实现细节暴露给外部的类或函数。 因此尽量避免滥用友元应该仅在确实需要的情况下才使用它。友元函数和友元类往往用于某些特定的场景比如操作符重载、类之间的协作等。
为什么使用友元 访问权限的突破友元最主要的功能就是突破类的访问权限控制使得其他函数或类可以访问类的私有成员。这在某些特殊情况下非常有用尤其是当你需要在类外部操作类的私有数据时。 实现操作符重载在 C 中很多操作符重载如 和 输入输出流操作符通常需要将类定义为友元以便能够访问类的私有成员。
友元的使用建议
尽管友元为我们提供了方便的功能但它也会增加类之间的耦合度破坏封装性。因此友元函数和友元类的使用要谨慎避免过度依赖友元。如果可能尽量通过类的公有接口来访问类的私有数据而不是使用友元。
内部类
内部类是在一个类的内部定义的类它和普通类相比主要有一些特殊的作用域和访问权限规则。通过将一个类定义在另一个类的内部可以增加封装性使得类与类之间的关系更加紧密。以下是对内部类的详细讲解
定义和特点 独立性内部类本质上是一个独立的类它有自己的作用域和生命周期。它不像外部类那样受到全局作用域的限制而是仅限于外部类的作用域内。 尽管它被定义在外部类的内部内部类依然是一个完整的类与外部类没有直接的继承或包含关系。例如外部类 A 内部定义的类 BB 仍然是一个独立的类。 访问限定符限制内部类受外部类的访问限定符private、protected、public的影响。如果外部类是 private那么内部类也会受到同样的限制如果外部类是 public那么内部类的访问限制会根据其自身的声明来决定。 外部类不包含内部类外部类的对象中并不会包含内部类对象除非明确实例化内部类的对象。内部类是外部类的一个成员但外部类并没有包含内部类作为其成员变量的一部分。
内部类的访问权限 默认友元类内部类默认是外部类的友元类这意味着内部类可以访问外部类的所有私有成员和保护成员。反过来外部类无法直接访问内部类的私有成员除非通过公开的接口。 例如 class Outer {
private:int x;
public:Outer() : x(10) {}class Inner {public:void accessOuter(Outer o) {// 内部类访问外部类的私有成员cout Accessing Outers private member x: o.x endl;}};
};int main() {Outer::Inner inner;Outer outer;inner.accessOuter(outer); // 访问外部类的私有成员return 0;
}作用域控制内部类只能在外部类的作用域中被访问这使得它只能在外部类中使用增强了封装性。其他类无法直接访问或创建外部类内部的类。
设计上的优势 封装性内部类常用于增强封装。当两个类之间有非常紧密的关系时把一个类设计为另一个类的内部类是非常合适的。这种设计可以把一个类完全限制在另一个类的作用域中只在必要的时候才提供外部访问。 专属类设计如果你有一个类它只会被另一个类使用且不需要外部其他地方访问时可以将其作为内部类进行设计。例如B 类仅为 A 类提供某些服务那么就可以把 B 类设计成 A 类的内部类而不暴露给外部。 类之间的紧密联系内部类的设计使得两个类之间的关系更加紧密。A 类和 B 类不再是松散的独立实体它们有着更加明确的隶属关系。比如你可以将 B 类的成员限制为只在 A 类的作用域内使用避免了外部使用时的误操作。 内部类的常见用法
辅助类的设计当一个类需要一个辅助类来完成一些特定任务但这个辅助类并不需要被外部访问时可以考虑将其设计为内部类。
class Outer {
public:class Helper {public:void helperFunction() {cout Helper Function! endl;}};
};int main() {Outer::Helper helper;helper.helperFunction(); // 只能通过外部类访问内部类return 0;
}面向对象设计中的迭代器模式内部类常常在容器类设计中使用例如设计一个迭代器类作为内部类以便访问容器的私有数据。 class Container {
private:vectorint data;
public:class Iterator {private:vectorint::iterator it;public:Iterator(vectorint::iterator iterator) : it(iterator) {}int operator*() { return *it; }Iterator operator() { it; return *this; }bool operator!(const Iterator other) { return it ! other.it; }};Container() {data.push_back(1);data.push_back(2);data.push_back(3);}Iterator begin() { return Iterator(data.begin()); }Iterator end() { return Iterator(data.end()); }
};int main() {Container c;for (auto it c.begin(); it ! c.end(); it) {cout *it ; // 通过迭代器访问容器数据}return 0;
}内部类的注意点 与外部类紧密耦合内部类和外部类之间有较强的耦合性。如果外部类发生变化内部类也可能需要做相应的调整。因此在设计时需要考虑这种耦合度是否符合预期的设计目标。 访问控制尽管内部类可以访问外部类的私有成员但外部类不能直接访问内部类的私有成员。如果你希望外部类也能访问内部类的成员可以提供适当的公有接口。 内存管理如果内部类和外部类的对象关系密切需要特别注意内存管理问题避免出现不必要的内存泄漏或引用问题。
匿名对象
在编程中匿名对象是没有名字的临时对象。相比传统的有名字的对象比如“类型 对象名(实参)”匿名对象通过“类型(实参)”的方式被临时创建。这种方式极大地简化了代码避免了不必要的命名同时也能清晰地表达我们只关心对象的短期行为而不需要后续访问它。
特点 短生命周期匿名对象的生命周期异常短暂它们只存在于当前的代码行内。换句话说它们就像是昙花一现创建后立刻完成任务并销毁不会占用额外的内存或资源。对于只需要执行一次特定任务的情况匿名对象完美适用。 临时性与独立性匿名对象没有名称它们是孤立的不会与程序中的其他部分发生联系。它们的作用通常仅限于创建它们的那一行代码在这一行结束后它们会立刻被销毁。这种“即生即死”的特性让它们非常适合于一次性的操作。
class Person {
public:Person(const std::string name) {std::cout Creating Person: name std::endl;}~Person() {std::cout Destroying Person. std::endl;}
};int main() {// 使用匿名对象创建并立即执行操作Person(Alice);// 匿名对象在这一行结束后销毁return 0;
}在上面的代码中Person(Alice) 是一个匿名对象。它在这行代码执行时创建完成打印操作后它就会立即销毁。整个过程没有变量名也不需要开发者去管理它的生命周期C 会自动处理。
匿名对象的实际用途 简洁高效有时你只需要临时创建一个对象来完成某项任务匿名对象能够简洁地实现这一点。例如你可能只是需要创建一个对象传递给某个函数而不需要在整个程序中反复使用它。这样就不需要定义多余的变量名代码既简洁又清晰。 例如我们常见的流操作中经常用到匿名对象
std::cout Hello, World! std::endl;这里的 std::cout 和 std::endl 都是匿名对象创建后立即使用之后会被销毁。 临时传参当函数需要传递一个临时对象时匿名对象尤其有用。例如构造函数或方法可能需要接受一个临时对象作为参数匿名对象正好可以完成这一任务而无需为对象额外命名。
class Rectangle {
public:Rectangle(int width, int height) {std::cout Creating Rectangle with width width and height height std::endl;}
};void printArea(const Rectangle rect) {// 输出面积
}int main() {// 直接传递匿名对象printArea(Rectangle(5, 10));
}这里Rectangle(5, 10) 就是一个匿名对象它仅仅为了传递给 printArea 方法而创建之后立即销毁。
匿名对象的局限性 生命周期有限匿名对象的生命周期极为短暂它们无法在代码的其他地方使用。一旦当前代码行执行完毕匿名对象即刻销毁内存被回收。因此如果你想在其他地方访问该对象的数据或方法显然匿名对象无法满足需求。 不可引用或修改由于匿名对象没有名称你无法像有名对象那样通过指针或引用来操作它们。如果你的代码需要反复操作一个对象或者保持该对象的状态使用匿名对象则不合适。
对象拷贝时的编译器优化
在现代 C 编译器中优化对象拷贝的过程是提高程序性能的重要手段。尤其是在对象传递和返回时拷贝操作可能是一个性能瓶颈。为了提高效率编译器会尽量避免不必要的对象拷贝尤其是在不影响程序正确性的情况下进行一些“聪明”的优化。
编译器优化的基本原则
编译器的目标是尽量减少不必要的拷贝操作提高程序的执行效率。在某些情况下拷贝操作可能是多余的编译器会在编译阶段识别这些冗余的拷贝并尝试消除它们。这些优化主要集中在以下几个方面 避免临时对象的拷贝如果一个函数的参数是传值形式并且传递给函数的实参是一个临时对象编译器可能会识别到这个临时对象在函数结束后并不再使用从而避免了不必要的拷贝。 合并拷贝现代编译器会识别出在同一个表达式中连续发生的多个拷贝操作并尝试将它们合并减少拷贝的次数。这种优化会在表达式的计算过程中自动完成而不需要程序员干预。 跨行跨表达式的优化一些更“激进”的编译器会进行跨行或跨表达式的优化。即使是在不同代码行或表达式中发生的对象拷贝编译器也可能会发现这些拷贝是冗余的并将它们合并成一次有效的拷贝操作。
示例合并拷贝的优化
class Person {
public:Person(const std::string name) : name(name) {}std::string name;
};Person createPerson() {return Person(Alice);
}int main() {Person p1 createPerson(); // 对象拷贝1Person p2 p1; // 对象拷贝2return 0;
}在这段代码中我们有两个对象拷贝
第一个拷贝发生在 createPerson() 函数返回时。这里返回了一个临时对象通常会发生一次拷贝。第二个拷贝发生在 p2 p1 时这又是一次拷贝。
在没有优化的情况下编译器可能会执行两次拷贝。然而现代编译器例如 GCC、Clang、MSVC会进行优化合并这些拷贝。具体的优化方式可能包括
使用 返回值优化RVO 或 命名返回值优化NRVO 来避免 createPerson() 函数中的临时对象拷贝。对于 p2 p1如果编译器发现 p1 和 p2 实际上是完全相同的对象无副作用的赋值它可能会直接将 p1 的内容移动到 p2从而避免拷贝。
进一步的优化移动语义
随着 C11 引入的移动语义编译器在拷贝和赋值时可以进一步优化。例如使用 std::move 或者编译器能够自动识别某些场景并将拷贝操作转换为移动操作从而减少不必要的数据复制。
Person createPerson() {return Person(Alice); // 返回临时对象
}int main() {Person p1 createPerson(); // 返回值优化 (RVO)避免拷贝Person p2 std::move(p1); // 移动构造函数避免拷贝return 0;
}在这种情况下std::move(p1) 会将 p1 的资源“转交”给 p2而不需要拷贝数据从而进一步提高性能。
编译器优化的实现
不同的编译器可能有不同的优化策略编译器在拷贝操作时进行的优化可能有所不同。主流编译器如 GCC、Clang 和 MSVC 都有一定的优化能力能够识别不必要的拷贝并在可能的情况下进行合并和消除。然而C 标准并没有明确规定编译器应该如何优化对象拷贝因此不同编译器之间的实现可能会有所差异。
GCC 和 Clang这两个编译器非常注重性能它们有能力通过 RVO、NRVO 和移动语义进行深度优化甚至跨表达式、跨行合并拷贝。MSVC微软的编译器也具备类似的优化能力特别是在进行 STL 容器操作时MSVC 的编译器在拷贝构造和赋值操作中有显著的优化。
结束