Recopilación - C/C++(13) 1.Expresiones Lambda07-02 2.Partición y manejo de memoria en C/C++06-13 3.Punteros inteligentes06-14 4.Análisis de punteros complejos en C: regla de derecha a izquierda06-14 5.Cadenas en C/C++06-14 6.Modificadores static, const y extern para variables en C++06-15 7. Funciones y Clases en C++ 06-16 8.Plantillas en C++06-17 9.Preprocesamiento, compilación, enlazado y organización de archivos en C++06-20 10.Manjo de excepciones06-20 11.STL: Iteradores y algoritmos comunes06-23 12.Contenedores STL06-24 13.Flujos en C++07-01 Colapsar Índice
- I. Definición de funciones
- II. Paso de parámetros por valor y por referencia
- III. Constructores y destructores
- IV. Herencia
- V. Amistad (friend)
Volver al inicio I. Definición de funciones
En C++, las funciones son unidades básicas para organizar la lógica del código, permitiendo un diseño de programas modular, reutilizable y estructurado.
1. Estructura básica de una función
`tipo_retorno nombre_funcion(lista_parametros) { // cuerpo de la función return valor; // opcional, depende del tipo de retorno}`
Declaración (Declaration):
Informa al compilador que la función existe, generalmente en archivos de cabecera:
`int sumar(int a, int b); // declaración de función`
Definición (Definition):
Proporciona la implementación de la función, generalmente en archivos .cpp:
`int sumar(int a, int b) { return a + b;}`
2. Sobrecarga de funciones (Overload)
El mismo nombre de función puede tener múltiples definiciones con diferentes parámetros:
`void mostrar(int x);void mostrar(double x);void mostrar(std::string s);`
Nota: La sobrecarga requiere diferencia en el número o tipo de parámetros, el tipo de retorno diferente no basta por sí solo.
3. Parámetros por defecto
`void saludar(std::string nombre = "Invitado") { std::cout << "Hola, " << nombre << "!\n";}saludar(); // Muestra: Hola, Invitado!saludar("Carlos"); // Muestra: Hola, Carlos!`
4. Funciones en línea (inline)
Sugiere al compilador insertar el código de la función en el lugar de su llamada, ideal para funciones pequeñas y frecuentes.
`inline int cuadrado(int x) { return x * x;}`
5. Funciones virtuales y puramente virtuales
En C++, las funciones virtuales (virtual) y las funciones puramente virtuales (= 0) son mecanismos clave para implementar el polimorfismo, pero difieren en sintaxis, propósito y comportamiento.
| Característica | Función virtual (Virtual Function) | Función puramente virtual (Pure Virtual Function) |
|---|---|---|
| Forma de definición | virtual void foo(); |
virtual void foo() = 0; |
| Tiene implementación | Puede tener implementación (o no) | Debe ser implementada en subclases (interfaz abstracta) |
| Requiere redefinición | Subclases opcionalmente pueden redefinirla | Subclases deben redefinirla (a menos que también sea abstracta) |
| Clase contenedora | Puede estar en cualquier clase | Debe estar en una clase abstracta (contiene funciones puramente virtuales) |
| Creación de objetos | Se pueden instanciar clases con funciones virtuales | Clases abstractas no se pueden instanciar |
| Uso | Proporciona implementación por defecto para polimorfismo | Fuerza implementación en subclases, como especificación de interfaz |
1. Ejemplo de función virtual (puede ser redefinida)
`class Animal {public: virtual void hablar() { std::cout << "El animal hace un sonido\n"; }};class Perro : public Animal {public: void hablar() override { std::cout << "El perro ladra\n"; }};Animal* p = new Perro();p->hablar(); // Muestra: El perro ladra (polimorfismo)`
Si Perro no redefine hablar(), se usará la implementación por defecto de Animal.
2. Ejemplo de función puramente virtual (debe ser redefinida)
`class Figura {public: virtual void dibujar() = 0; // función puramente virtual};class Circulo : public Figura {public: void dibujar() override { std::cout << "Dibujando un círculo\n"; }};// Figura f; // ❌ Error, no se pueden instanciar clases abstractasFigura* p = new Circulo();p->dibujar(); // Muestra: Dibujando un círculo`
Si Circulo no implementa dibujar(), también se volverá una clase abstracta.
Clase interfaz (interface)
C++ no tiene la palabra clave interface, pero puedes simular una interfaz con funciones puramente virtuales:
`class IFlujo {public: virtual void leer() = 0; virtual void escribir() = 0; virtual ~IFlujo() {} // Las clases interfaz deben tener un destructor virtual};`
6. Expresiones Lambda (C11 en adelante)
`[lista_captura](lista_parametros) -> tipo_retorno { cuerpo_de_la_funcion}`
Donde:
[]: lista de captura (puede capturar variables externas)(): lista de parámetros->: tipo de retorno (puede omitirse){}: cuerpo de la función
Funciones anónimas, ideales para callbacks concisos:
`auto sumar = [](int a, int b) -> int { return a + b;};std::cout << sumar(3, 5); // Muestra: 8// El tipo de retorno puede deducirse, por lo que se puede omitir `-> int`:auto sumar = [](int a, int b) { return a + b;};std::cout << sumar(3, 4); // Muestra 7`
Las expresiones Lambda son funciones anónimas introducidas en C++11 para definir objetos funcionales en línea, especialmente adecuadas para escenarios como callbacks en algoritmos, manejo de eventos, creación de hilos, etc.
Captura de variables externas
| Método de captura | Ejemplo | Descripción |
|---|---|---|
| Captura por valor | [x] |
Captura el valor de la variable x (copia) |
| Captura por referencia | [&x] |
Captura la referencia de la variable x |
| Captura todo por valor | [=] |
Captura el valor de todas las variables externas |
| Captura todo por referencia | [&] |
Captura la referencia de todas las variables externas |
| Captura mixta | [=, &y] |
Captura por valor todas excepto y, que se captura por referencia |
`int x = 10;int y = 5;auto f = [=, &y]() { // x se captura por valor, y por referencia std::cout << x + y << "\n"; y += 1; // permitido modificar y};f();`
Aplicaciones comunes
- Combinación con algoritmos STL (como
std::sort)
`std::vector<int> v = {4, 2, 5, 1};std::sort(v.begin(), v.end(), [](int a, int b) { return a < b;});`
- Uso con hilos:
`#include <thread>std::thread t([] { std::cout << "En un hilo\n";});t.join();`
Lambda mutable
Por defecto, las variables capturadas por valor no pueden modificarse. Con mutable se permiten modificaciones en la copia:
`int x = 5;auto f = [x]() mutable { x += 1; // permitido modificar la copia de la variable capturada std::cout << x;};f(); // Muestra 6, pero x externo no cambia`
Tipos especiales de funciones
| Tipo | Uso |
|---|---|
| Constructor | Se llama automáticamente al crear un objeto |
| Destructor | Se llama automáticamente al destruir un objeto |
| Constructor de copia | Se llama al inicializar un objeto con otro objeto |
| Constructor de movimiento | Se llama al inicializar un objeto con un valor rvalue |
| Funciones de sobrecarga de operadores | Sobrecargan operadores como +, ==, etc. |
| Función virtual | Para polimorfismo |
| Función puramente virtual | Miembro de función de clase abstracta |
Volver al inicio II. Paso de parámetros por valor y por referencia
En C++, los parámetros de función se pasan por valor por defecto, pero no siempre por valor. C++ soporta múltiples formas de paso de parámetros, principalmente:
1. Paso por valor (Pass by Value)
- Se pasa una copia del argumento real a la función
- Las modificaciones al parámetro dentro de la función no afectan a la variable original
`void modificar(int x) { x = 100;}int main() { int a = 10; modificar(a); std::cout << a; // Muestra 10, sin cambios}`
2. Paso por referencia (Pass by Reference)
- Se pasa un alias de la variable, las modificaciones afectan a la variable original
`void modificar(int& x) { x = 100;}int main() { int a = 10; modificar(a); std::cout << a; // Muestra 100, modificado}`
3. Paso por puntero (Pass by Pointer)
- La función recibe la dirección de la variable y accede/modifica a través del puntero
`void modificar(int* x) { *x = 100;}int main() { int a = 10; modificar(&a); std::cout << a; // Muestra 100}`
4. Paso por referencia constante (Pass by const Reference)
- Ideal para evitar copias de objetos grandes
- No permite modificar el argumento
- Común para pasar objetos como
std::string,std::vector
`void mostrar(const std::string& s) { std::cout << s;}`
5. Paso por referencia a rvalue (Pass by rvalue reference) C11+
- Soporta semántica de movimiento, evitando copias innecesarias
`void procesar(std::string&& s) { std::cout << s;}procesar("hola"s); // paso por movimiento`
Resumen comparativo:
| Método | ¿Copia? | ¿Puede modificar variable original? | Caso de uso |
|---|---|---|---|
| Paso por valor | Sí | No | Tipos de datos pequeños (int, float) |
| Paso por referencia | No | Sí | Cuando se necesita modificar la variable original |
| Paso por puntero | No | Sí | Similar a referencia, pero más flexible |
| Paso por referencia constante | No | No | Pasar objetos grandes sin modificar |
| Paso por referencia a rvalue | No | Sí | Soporta movimiento, evita copias |
Conclusión:
En C++, el paso de parámetros es por defecto por valor, pero puedes usar
&(referencia),*(puntero) o&&(referencia a rvalue) para otros métodos.
Volver al inicio III. Constructores y destructores
Constructor
El constructor es una función especial que se llama automáticamente cuando se crea un objeto, utilizada para inicializar las variables miembro del objeto.
- Tiene el mismo nombre que la clase
- No tiene tipo de retorno
- Puede haber múltiples
| Tipo | Uso |
|---|---|
| Constructor por defecto | Sin parámetros o todos los parámetros tienen valores por defecto |
| Constructor con parámetros | Proporciona parámetros de inicialización |
| Constructor de copia | Crea un nuevo objeto a partir de uno existente |
| Constructor de movimiento (C11) | "Roba" recursos (más eficiente) |
| Constructor delegado (C11) | Un constructor llama a otro constructor de la misma clase |
Constructor de copia
Si no defines explícitamente el operador de asignación, C++ genera automáticamente uno que hace una copia superficial (shallow copy). Pero si tu clase contiene punteros sin gestionar, la copia por defecto causará problemas de copias superficiales (recurso compartido, conflictos en destrucción). En estos casos, debes definir tu propio operador de asignación.
El constructor de copia se usa para:
- Inicializar un objeto a partir de otro existente
- Pasar objetos por valor a funciones
- Devolver objetos por valor desde funciones
`Clase(const Clase& otro);`
El parámetro es
const ¶ evitar llamadas recursivas
Prohibir la copia:
`// En C++11/14/17, se recomienda usar `= default` y `= delete` para especificar explícitamente:class MiClase {public: MiClase() = default; MiClase(const MiClase&) = delete; ~MiClase() = default;};`
Destructor
El destructor es una función especial que se llama automáticamente cuando se destruye un objeto, utilizada para liberar recursos, cerrar archivos, limpiar punteros, etc.
- El nombre es
~NombreClase - No tiene parámetros ni tipo de retorno
- Cada clase puede tener como máximo un destructor
- Puede ser virtual (para eliminación polimórfica)
Ejemplo
Implementación completa en un solo archivo de cabecera:
`#include <iostream>#include <string>class Persona {public: std::string nombre; int* edad; // ✅ 1. Constructor por defecto (delega al constructor con parámetros) Persona() : Persona("desconocido", 0) { std::cout << "Constructor por defecto llamado (delegado)\n"; } // ✅ 2. Constructor con parámetros Persona(const std::string& nombre_, int edad_) { nombre = nombre_; edad = new int(edad_); std::cout << "Constructor con parámetros llamado\n"; } // ✅ 3. Constructor de copia (copia profunda) Persona(const Persona& otra) { nombre = otra.nombre; edad = new int(*otra.edad); std::cout << "Constructor de copia llamado\n"; } // ✅ 4. Constructor de movimiento (C11) Persona(Persona&& otra) noexcept { nombre = std::move(otra.nombre); // string tiene su propio movimiento edad = otra.edad; // robar puntero otra.edad = nullptr; // evitar doble liberación en destructor std::cout << "Constructor de movimiento llamado\n"; } // ✅ 5. Destructor ~Persona() { std::cout << "Destructor llamado para " << nombre << "\n"; delete edad; }};`
Escritura moderna recomendada con separación de cabecera y fuente:
`// Persona.h#ifndef PERSONA_H#define PERSONA_H#include <string>class Persona {public: std::string nombre; int* edad; // ✅ 1. Constructor por defecto Persona(); // ✅ 2. Constructor con parámetros Persona(const std::string& nombre_, int edad_); // ✅ 3. Constructor de copia (copia profunda) Persona(const Persona& otra); // ✅ 4. Constructor de movimiento (C11) Persona(Persona&& otra) noexcept; // ✅ 5. Destructor virtual ~Persona(); // Operador de asignación por copia (opcional) Persona& operator=(const Persona& otra); // Operador de asignación por movimiento (opcional) Persona& operator=(Persona&& otra) noexcept; // ✅ const indica que la función no modificará variables miembro virtual void presentarse() const;};#endif`
`// Persona.cpp#include "Persona.h"#include <iostream>// ✅ 1. Constructor por defectoPersona::Persona() : Persona("desconocido", 0) { std::cout << "Constructor por defecto llamado\n";}// ✅ 2. Constructor con parámetrosPersona::Persona(const std::string& nombre_, int edad_) { nombre = nombre_; edad = new int(edad_); std::cout << "Constructor con parámetros llamado\n";}// ✅ 3. Constructor de copia (copia profunda)Persona::Persona(const Persona& otra) { nombre = otra.nombre; edad = new int(*otra.edad); std::cout << "Constructor de copia llamado\n";}// ✅ 4. Constructor de movimiento (C11)Persona::Persona(Persona&& otra) noexcept { nombre = std::move(otra.nombre); edad = otra.edad; otra.edad = nullptr; std::cout << "Constructor de movimiento llamado\n";}// ✅ 5. DestructorPersona::~Persona() { std::cout << "Destructor de Persona llamado para " << nombre << "\n"; delete edad;}// Operador de asignación por copia (opcional)Persona& Persona::operator=(const Persona& otra) { if (this != &otra) { nombre = otra.nombre; delete edad; edad = new int(*otra.edad); } return *this;}// Operador de asignación por movimiento (opcional)Persona& Persona::operator=(Persona&& otra) noexcept { if (this != &otra) { nombre = std::move(otra.nombre); delete edad; edad = otra.edad; otra.edad = nullptr; } return *this;}void Persona::presentarse() const { std::cout << "Hola, soy " << nombre << ", tengo " << *edad << " años.\n";}`
Resumen del comportamiento por defecto de constructores/copia/destructores
| Tipo de función | ¿Se genera automáticamente? | ¿Cuándo necesita personalización? |
|---|---|---|
| Constructor | (si no se escribe) | Cuando los miembros necesitan inicialización especial |
| Constructor de copia | (si no se escribe) | Si contiene punteros, recursos, o si se debe prohibir la copia |
| Destructor | (si no se escribe) | Cuando los miembros incluyen recursos dinámicos (como new) deben liberarse |
| Constructor de movimiento | (C11+) | Para optimizar o evitar copias |
- Si no lo escribes, el compilador genera automáticamente una versión de "copia superficial".
- Si la clase tiene punteros sin gestionar, debes escribir tu propio constructor de copia, de lo contrario podrías tener errores de doble liberación.
Copia profunda y copia superficial
En C++, la copia profunda (deep copy) y la copia superficial (shallow copy) son dos formas de copiar objetos, que difieren en si realmente se copian los recursos en el montón. Entender estas diferencias es crucial para dominar los constructores, constructores de copia y destructores de clases.
Definiciones y diferencias
| Característica | Copia superficial (Shallow Copy) | Copia profunda (Deep Copy) |
|---|---|---|
| Contenido copiado | Solo se copia la dirección del puntero | Se asigna nueva memoria y se copia el contenido |
| Recursos compartidos | El objeto original y la copia apuntan a la misma memoria | El objeto original y la copia tienen memoria independiente |
| Seguridad | Modificar un objeto afecta al otro; riesgo de punteros colgantes, doble liberación | Los objetos no se afectan entre sí |
| Riesgo en destrucción | Doble liberación de la misma memoria (si no se gestiona correctamente) | Cada objeto libera su propia memoria |
Ejemplo demostrativo
`#include <iostream>#include <cstring>class Persona {public: char* nombre; // Constructor Persona(const char* n) { nombre = new char[strlen(n) + 1]; strcpy(nombre, n); } // Constructor de copia superficial (por defecto) // Persona(const Persona& otra) = default; // ✅ Constructor de copia profunda Persona(const Persona& otra) { nombre = new char[strlen(otra.nombre) + 1]; strcpy(nombre, otra.nombre); } // Destructor ~Persona() { delete[] nombre; } void imprimir() { std::cout << "Nombre: " << nombre << std::endl; }};`
Si no escribes el constructor de copia profunda, el compilador usa por defecto la copia superficial, es decir, solo copia la dirección del puntero nombre. Esto significa que ambos objetos apuntan a la misma memoria en el montón. Así, modificar p2.nombre afectaría a p1.nombre, y ambos objetos al destruirse intentaría liberar la misma memoria varias veces, provocando un fallo.
Comportamiento por defecto en copia
| Tipo de operación | Comportamiento por defecto | ¿Es seguro? |
|---|---|---|
| Constructor de copia | Copia superficial | |
Operador de asignación = |
Asignación superficial | |
| Destructor | Liberación por defecto | (si se usan punteros sin gestionar) |
El rol de const en las clases
const puede modificar funciones miembro de la clase, parámetros y valores de retorno de funciones, y los objetos, pero no puede modificar directamente toda la clase.
const modifica funciones (funciones miembro constantes)
`class MiClase {public: int obtenerValor() const; // ✅ indica que la función no modificará variables miembroprivate: int valor = 42;};int MiClase::obtenerValor() const { // this->valor = 10; ❌ Error de compilación: no se pueden modificar variables miembro return valor;}`
`const MiClase obj; // ✅ **solo puede llamar a funciones miembro constantes**MiClase obj2; // ✅ **puede llamar tanto a funciones miembro constantes como no constantes**`
const modifica parámetros y valores de retorno
Modificación de parámetros
`void imprimirNombre(const std::string& nombre); // ✅ evita copia + garantiza que no se modifique el parámetro`
Modificación de valores de retorno
`const std::string& obtenerNombre() const; // ✅ el valor de retorno no puede ser modificado (previene uso incorrecto)`
Nota: Cuando
constmodifica valores de retorno, generalmente se usa para devolver referencias o punteros, rara vez para devolver por valor.
No se puede modificar directamente la clase
`const class MiClase {}; // ❌ poco común, básicamente sin sentido`
Volver al inicio IV. Herencia
Las funciones de la clase base se modifican con virtual, y las subclases pueden usar override para redefinirlas
Modificadores de herencia y visibilidad
La herencia en C++ es idéntica a la de Java, con los mismos modificadores de herencia:
| Modificador de herencia | Los miembros public de la base se convierten en |
Los miembros protected de la base se convierten en |
Los miembros private de la base |
|---|---|---|---|
public |
public |
protected |
No accesibles |
protected |
protected |
protected |
No accesibles |
private |
private |
private |
No accesibles |
Visibilidad
| Modificador | Acceso dentro de la clase | Acceso en clases derivadas | Acceso fuera de la clase |
|---|---|---|---|
public |
Accesible | Accesible | Accesible |
protected |
Accesible | Accesible | No accesible |
private |
Accesible | No accesible | No accesible |
Ejemplo
`//estudiante.h#ifndef ESTUDIANTE_H#define ESTUDIANTE_H#include "Persona.h" class Estudiante : public Persona {public: std::string escuela; Estudiante(); Estudiante(const std::string& nombre_, int edad_, const std::string& escuela_); ~Estudiante() override; void presentarse() const override;};#endif`
`//estudiante.cpp#include "Estudiante.h"#include <iostream>Estudiante::Estudiante() : Persona("estudiante_desconocido", 18), escuela("Escuela Desconocida") { std::cout << "Constructor por defecto de Estudiante\n";}Estudiante::Estudiante(const std::string& nombre_, int edad_, const std::string& escuela_) : Persona(nombre_, edad_), escuela(escuela_) { std::cout << "Constructor con parámetros de Estudiante\n";}Estudiante::~Estudiante() { std::cout << "Destructor de Estudiante llamado para " << nombre << "\n";}void Estudiante::presentarse() const { std::cout << "Hola, soy el estudiante " << nombre << ", tengo " << *edad << " años y estudio en " << escuela << ".\n";}`
Orden de constructores y destructores
- Orden de construcción: primero la clase base → luego la clase derivada
- Orden de destrucción: primero la clase derivada → luego la clase base
`class Base {public: Base() { std::cout << "Base()\n"; } ~Base() { std::cout << "~Base()\n"; }};class Derivada : public Base {public: Derivada() { std::cout << "Derivada()\n"; } ~Derivada() { std::cout << "~Derivada()\n"; }};`
Orden de salida:
Base()
Derivada()
~Derivada()
~Base()
Característica específica de C++: herencia múltiple y herencia virtual - Problema de la herencia en diamante
Herencia múltiple
`class A { public: int x; };class B { public: int x; };class C : public A, public B {}; // herencia múltiple, C tiene dos x, debe distinguir A::x y B::x`
El "problema de la herencia en diamante (Diamond Problem)" es un conflicto estructural específico en la herencia múltiple de C++ que causa:
- Duplicación de subobjetos de la clase base
- Ambigüedad en el acceso a datos / acceso ambiguo
- Confusión en construcción y destrucción
- Desperdicio de recursos
¿Qué es la estructura de herencia en diamante?
Como se muestra en el diagrama, B y C heredan de A, y D hereda de B y C:
` A / \ B C \ / Dclass A {public: int valor;};class B : public A {};class C : public A {};class D : public B, public C {};`
Problemas causados por la herencia en diamante
1. Se crean múltiples subobjetos A
En D hay dos objetos A:
- Uno de
B(B→A) - Uno de
C(C→A)
2. Ambigüedad al acceder a valor
`D d;d.valor = 10; // ❌ Error: el compilador no sabe si es B::A::valor o C::A::valor`
Se debe especificar manualmente:
`d.B::valor = 10;d.C::valor = 20;`
Solución: herencia virtual (virtual)
Usa virtual para modificar la relación de herencia, haciendo que B y C compartan el mismo subobjeto A.
`class A {public: int valor;};class B : virtual public A {};class C : virtual public A {};class D : public B, public C {};`
Efecto de la herencia virtual:
- En
Dsolo hay un objeto A, coordinado automáticamente por el compilador - El acceso a miembros ya no es ambiguo:
`D d;d.valor = 10; // ✅ Acceso normal, sin ambigüedad`
Volver al inicio V. Amistad (friend)
En C++, el mecanismo de amistad (friend) permite que funciones no miembro u otras clases accedan a los miembros privados (private) y protegidos (protected) de una clase. La relación de amistad es unidireccional y no transitiva, y se usa comúnmente para sobrecargar operadores, herramientas de depuración o colaboración estrecha entre clases.
I. Tres formas de amistad
1. Función amiga (Friend Function)
`class Caja {private: int ancho;public: Caja(int w) : ancho(w) {} // Declarar función amiga friend void mostrarAncho(const Caja& c);};// Función no miembro, pero puede acceder a miembros privados de Cajavoid mostrarAncho(const Caja& c) { std::cout << "Ancho: " << c.ancho << std::endl;}`
Características:
- No es una función miembro de la clase, pero tiene acceso a sus miembros privados
- Se usa comúnmente para sobrecargar operadores como
operator<<
2. Clase amiga (Friend Class)
`class Motor;class Coche {private: int velocidad;public: Coche(int s) : velocidad(s) {} friend class Motor; // Motor puede acceder a miembros privados de Coche};class Motor {public: void mostrarVelocidad(const Coche& c) { std::cout << "Velocidad: " << c.velocidad << std::endl; }};`
Características:
- Una clase puede declarar otra clase como amiga
- Todas las funciones miembro de la clase amiga pueden acceder a los miembros privados de la clase que declara la amistad
- Unidireccional: Motor es amigo de Coche, pero Coche no puede acceder a miembros privados de Motor
3. Función miembro como amiga
`class B;class A {private: int a_valor;public: A(int v) : a_valor(v) {} friend void B::mostrarA(const A& a); // solo una función miembro específica de B se convierte en amiga};class B {public: void mostrarA(const A& a) { std::cout << a.a_valor << std::endl; }};`
Nota: Este método requiere declarar primero la clase B, de lo contrario el compilador no sabrá qué es B::mostrarA.
II. Resumen de características de amistad
| Característica | Descripción |
|---|---|
| Acceso unidireccional | La clase declarada como amiga puede acceder a miembros privados de la clase que declara, pero no al revés |
| Funciones no miembro también pueden ser amigas | Funciones ordinarias, clases, funciones miembro de clases pueden ser declaradas como amigas |
| No rompe la encapsulación | Aunque pueden acceder a miembros privados, el alcance de acceso está explícitamente limitado |
| Enlace en tiempo de compilación | La relación de amistad se establece en tiempo de compilación, no puede establecerse dinámicamente en tiempo de ejecución |
| No se hereda ni transmite | Las subclases no heredan los derechos de amistad, y las clases amigas de la clase amiga tampoco tienen acceso |
III. Ejemplo: sobrecarga del operador <<
`class Persona {private: std::string nombre; int edad;public: Persona(std::string n, int a) : nombre(n), edad(a) {} friend std::ostream& operator<<(std::ostream& os, const Persona& p);};std::ostream& operator<<(std::ostream& os, const Persona& p) { os << "Nombre: " << p.nombre << ", Edad: " << p.edad; return os;}`
Autor original: aslanvon Publicado en: https://www.cnblogs.com/aslanvon/p/18931148