指针与引用区分
指针 :
是一个变量,它存储的是另一个变量的内存地址 。
可以是 NULL(空指针),表示不指向任何对象。使用前必须检查是否有效。
可以改变指向。(重定向)
通常占用 4 字节(32位系统)或 8 字节(64位系统)的内存空间来存储地址。
可以使用自增(p++)操作,在内存中移动到下一个位置。
引用 :
是一个变量的别名 (绰号)。它不是一个新的变量,不占用额外的内存(在底层实现上通常占一个指针的大小,但在语法层面视作原变量本身)。
不能为空 。在定义引用时,必须 立即初始化,而且必须绑定到一个有效的对象上。你不可能有一个“不存在的变量的别名”。
一旦初始化,就不能改变指向 。
在语法层面上,引用只是别名,不占内存。但在编译器底层,引用通常也是通过指针实现的,所以实际运行时占用的空间和指针一样,但我们在写代码时应该把它理解为“没有开销的别名”。
r++ 的意思是“它所代表的那个变量的值加 1”,而不是移动到下一个内存位置。
修饰符Public vs Private vs Protected
当一个类继承另一个类时,父类(基类)的成员在子类(派生类)中的可访问性取决于两个因素 :
父类成员的访问修饰符 (public、private、protected)
继承方式 (public、private、protected 继承)
继承方式与访问权限表
Public 继承(公开继承)
最常用的继承方式 。父类的 public 成员在子类中仍然是 public,protected 成员仍然是 protected。
Private 继承(私有继承)
父类的 public 和 protected 成员在子类中都变成 private 。不推荐使用 (违反里氏替换原则)。
Protected 继承(受保护的继承)
父类的 public 成员在子类中变成 protected ,protected 成员仍然是 protected。适用于需要进一步继承的情况。
访问受限成员的方法
当父类成员被设为 private 或因继承方式变成 private/protected 时,有几种合法的方式来访问它们。
方法 1:使用 Public Getter 和 Setter 函数
这是最推荐的方式 。在父类中提供 public 函数来访问和修改 private 成员。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <iostream> using namespace std;class Animal {private : int age = 5 ; string name = "Unknown" ; public : int getAge () { return age; } string getName () { return name; } void setAge (int newAge) { if (newAge > 0 ) { age = newAge; } } void setName (string newName) { name = newName; } }; class Dog : public Animal {public : void displayInfo () { cout << "Name: " << getName () << endl; cout << "Age: " << getAge () << endl; } }; int main () { Dog dog; dog.setName ("Buddy" ); dog.setAge (3 ); dog.displayInfo (); return 0 ; }
方法 2:使用 Protected 成员 Public继承
将 private 改为 protected,使得派生类可以直接访问。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <iostream> using namespace std;class Animal {protected : int age = 5 ; string type = "Mammal" ; public : void eat () { cout << "Eating..." << endl; } }; class Dog : public Animal {public : void setAge (int newAge) { age = newAge; } void displayInfo () { cout << "Type: " << type << endl; cout << "Age: " << age << endl; } }; int main () { Dog dog; dog.setAge (4 ); dog.displayInfo (); return 0 ; }
方法 3:使用 Friend 函数或类
允许指定的函数或类访问 private 成员。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <iostream> using namespace std;class Animal {private : int age = 5 ; string name = "Unknown" ; public : friend void printAnimalInfo (Animal& animal) ; friend class AnimalHelper ; }; void printAnimalInfo (Animal& animal) { cout << "Name: " << animal.name << endl; cout << "Age: " << animal.age << endl; } class AnimalHelper {public : void updateAnimal (Animal& animal, string newName, int newAge) { animal.name = newName; animal.age = newAge; } }; int main () { Animal dog; printAnimalInfo (dog); AnimalHelper helper; helper.updateAnimal (dog, "Buddy" , 3 ); printAnimalInfo (dog); return 0 ; }
方法 4:解决 Private 继承的访问问题
使用 using 关键字 重新暴露父类成员。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> using namespace std;class Animal {public : void eat () { cout << "Eating..." << endl; } void sleep () { cout << "Sleeping..." << endl; } }; class Dog : private Animal {public : using Animal::eat; using Animal::sleep; void bark () { cout << "Woof! Woof!" << endl; } }; int main () { Dog dog; dog.eat (); dog.sleep (); dog.bark (); return 0 ; }
方法 6:使用公开的虚函数
在父类中提供 public 虚函数来访问 private 数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <iostream> using namespace std;class Animal {private : int age = 5 ; string name = "Unknown" ; public : virtual void updateAge (int newAge) { if (newAge > 0 ) { age = newAge; } } virtual void displayInfo () { cout << "Name: " << name << endl; cout << "Age: " << age << endl; } }; class Dog : public Animal {public : void updateAge (int newAge) override { Animal::updateAge (newAge); } }; int main () { Dog dog; dog.updateAge (3 ); dog.displayInfo (); return 0 ; }
对比总结表
优先使用 Getter/Setter - 最安全,提供数据验证
使用 protected - 简化派生类的开发
避免 friend - 破坏封装
使用 using - 修复继承问题
选择合适的访问权限 - 设计类时就要考虑好
多重继承
多重继承 是指一个派生类同时继承自两个或多个 父类。派生类将继承所有父类的成员和方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> using namespace std;class Animal {public : void eat () { cout << "Animal is eating" << endl; } }; class Vehicle {public : void run () { cout << "Vehicle is running" << endl; } }; class Car : public Animal, public Vehicle {public : void display () { cout << "This is a Car" << endl; } }; int main () { Car car; car.eat (); car.run (); car.display (); return 0 ; }
Output:
1 2 3 Animal is eating Vehicle is running This is a Car
多重继承的问题:钻石问题
最常见的问题 :当两个父类都继承自同一个祖父类时,会产生歧义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> using namespace std;class Animal {public : void eat () { cout << "Eating..." << endl; } }; class Dog : public Animal {public : void bark () { cout << "Woof!" << endl; } }; class Cat : public Animal {public : void meow () { cout << "Meow!" << endl; } }; class DogCat : public Dog, public Cat {}; int main () { DogCat dc; return 0 ; }
输出错误:
1 error: request for member 'eat' is ambiguous
解决钻石问题:虚继承
使用 virtual 关键字 来解决钻石问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <iostream> using namespace std;class Animal {public : void eat () { cout << "Eating..." << endl; } }; class Dog : virtual public Animal {public : void bark () { cout << "Woof!" << endl; } }; class Cat : virtual public Animal {public : void meow () { cout << "Meow!" << endl; } }; class DogCat : public Dog, public Cat {}; int main () { DogCat dc; dc.eat (); dc.bark (); dc.meow (); return 0 ; }
Output:
访问多个父类的同名成员
当两个父类有同名的函数时,需要指定使用哪个。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> using namespace std;class ClassA {public : void display () { cout << "Display from ClassA" << endl; } }; class ClassB {public : void display () { cout << "Display from ClassB" << endl; } }; class DerivedClass : public ClassA, public ClassB {public : void show () { ClassA::display (); ClassB::display (); } }; int main () { DerivedClass dc; dc.show (); dc.ClassA::display (); dc.ClassB::display (); return 0 ; }
Output:
1 2 3 4 Display from ClassA Display from ClassB Display from ClassA Display from ClassB
多重继承的对比表
何时使用多重继承
✅ 使用场景:
一个类需要多个独立的接口或功能
模拟现实中的"多重身份"(如学生既是人也是学习者)
实现 mixin 模式
❌ 避免使用:
设计变得过于复杂
导致钻石问题
只是为了代码复用(用组合代替)
最佳实践建议
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Student {private : Person person; Learner learner; public : void study () { learner.study (); } }; class ComplexClass : public A, public B, public C, public D { };
Virtual(虚函数)详解
virtual 是 C++ 中实现多态的关键字 ,它让程序在运行时 根据对象的实际类型来调用函数,而不是根据指针/引用的声明类型。
Virtual 的核心作用
不用 virtual(静态绑定)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Animal { public : void sound () { cout<<"Some sound\n" ; }}; class Dog : public Animal{ public : void sound () { cout<<"Woof Woof\n" ; }}; int main () { Animal *ptr = new Dog (); ptr->sound (); return 0 ; }
问题 :尽管指向 Dog 对象,但因为 ptr 是 Animal* 类型,所以调用了 Animal 的 sound()。
用 virtual(动态绑定)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Animal { public : virtual void sound () { cout<<"Some sound\n" ; }}; class Dog : public Animal{ public : void sound () { cout<<"Woof Woof\n" ; }}; int main () { Animal *ptr = new Dog (); ptr->sound (); return 0 ; }
现在正确了 :运行时检查 ptr 实际指向 Dog,所以调用 Dog::sound()。
Virtual 的工作原理
Virtual 的五大主要用途
1. 实现多态性 (最重要)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class Shape { public : virtual void draw () { cout<<"Drawing Shape\n" ; }}; class Circle : public Shape{ public : void draw () { cout<<"Drawing Circle\n" ; }}; class Rectangle : public Shape{ public : void draw () { cout<<"Drawing Rectangle\n" ; }}; int main () { Shape *shapes[3 ]; shapes[0 ] = new Circle (); shapes[1 ] = new Rectangle (); shapes[2 ] = new Shape (); for (int i = 0 ; i < 3 ; i++) { shapes[i]->draw (); } return 0 ; }
2. 处理不同派生类对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Employee { public : virtual void calculateSalary () { cout<<"Base salary\n" ; }}; class Manager : public Employee{ public : void calculateSalary () { cout<<"Manager salary + bonus\n" ; }}; class Developer : public Employee{ public : void calculateSalary () { cout<<"Developer salary\n" ; }}; int main () { Employee *emp; Manager m; Developer d; emp = &m; emp->calculateSalary (); emp = &d; emp->calculateSalary (); return 0 ; }
3. 虚析构函数 (防止内存泄漏)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Base { public : virtual ~Base () { cout<<"Base destructor\n" ; } }; class Derived : public Base{ public : ~Derived () { cout<<"Derived destructor\n" ; } }; int main () { Base *ptr = new Derived (); delete ptr; return 0 ; }
重要 :如果没有 virtual ~Base(),只会调用 Base 的析构函数,Derived 的资源不会被释放。
4. 纯虚函数 (定义接口)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Shape { public : virtual void draw () = 0 ; virtual ~Shape () {} }; class Circle : public Shape{ public : void draw () { cout<<"Drawing Circle\n" ; }}; int main () { Circle c; c.draw (); Shape *ptr = new Circle (); ptr->draw (); return 0 ; }
用途 :
= 0 表示纯虚函数
包含纯虚函数的类是抽象类 ,不能直接创建对象
强制派生类必须实现这个函数
5. 向上转型安全使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Animal { public : virtual void makeSound () = 0 ; }; class Cat : public Animal{ public : void makeSound () { cout<<"Meow\n" ; }}; class Dog : public Animal{ public : void makeSound () { cout<<"Woof\n" ; }}; void processAnimal (Animal *a) { a->makeSound (); } int main () { Cat cat; Dog dog; processAnimal (&cat); processAnimal (&dog); return 0 ; }
Virtual 的重要规则
1. 子类重写时不需要写 virtual (但推荐写)
1 2 3 4 5 6 7 8 9 10 11 12 13 class Base { public : virtual void func () { } }; class Derived : public Base{ public : void func () { } };
2. 虚函数不能是静态函数
1 2 3 4 5 class Base { public : virtual static void func () { } };
3. 虚函数不能是模板函数
1 2 3 4 5 6 class Base { public : template <typename T> virtual void func (T x) { } };
4. 函数签名必须完全相同
1 2 3 4 5 6 7 8 9 10 11 12 class Base { public : virtual void func (int x) { } }; class Derived : public Base{ public : void func (double x) { } void func (int x) { } };
Virtual 的性能开销
1 2 3 4 5 6 7 class Base { public : void normalFunc () { } virtual void virtualFunc () { } };
使用场景总结
总结 :virtual 是 C++ 多态的核心,让程序能够在运行时根据对象实际类型来调用正确的函数 ,而不是根据指针类型。这是面向对象设计中非常重要的特性。
泛型编程
泛型编程是一种编程方法,允许你编写可以处理多种数据类型的代码,而不需要为每种类型重复编写相同的逻辑。 用一个通用的、与类型无关的方式来解决问题。
泛型编程的核心思想
不依赖于具体的数据类型,而是使用 类型参数 来代表任何数据类型。
1 2 3 4 5 template <class T > T min (T &a, T &b) { return (a<b)?a:b; }
这个函数对 所有支持比较运算符 < 的类型都有效 。
泛型编程 vs 传统编程
传统编程(不使用泛型)
1 2 3 4 5 6 7 8 9 10 11 12 int min_int (int a, int b) { return (a<b)?a:b; }float min_float (float a, float b) { return (a<b)?a:b; }double min_double (double a, double b) { return (a<b)?a:b; }char min_char (char a, char b) { return (a<b)?a:b; }int main () { cout << min_int (5 , 3 ); cout << min_float (5.5 , 3.3 ); cout << min_double (5.5 , 3.3 ); }
问题:
代码重复很多
难以维护
如果要改逻辑,需要改所有函数
容易出错
泛型编程(使用模板)
1 2 3 4 5 6 7 8 9 10 11 12 13 template <class T>T min (T &a, T &b) { return (a<b)?a:b; } int main () { cout << min (5 , 3 ); cout << min (5.5 , 3.3 ); cout << min ('A' , 'B' ); }
优点:
代码简洁
易于维护
逻辑改一次,所有类型都适用
减少重复代码
泛型编程的三种主要方式
1. 函数模板(Function Template)
1 2 3 4 5 6 7 8 9 10 11 template <class T>T add (T a, T b) { return a + b; } int main () { cout << add (5 , 3 ); cout << add (5.5 , 3.3 ); }
2. 类模板(Class Template)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 template <class T >class Box { private : T value; public : void setValue (T v) { value = v; } T getValue () { return value; } }; int main () { Box<int > intBox; intBox.setValue (10 ); cout << intBox.getValue (); Box<string> strBox; strBox.setValue ("Hello" ); cout << strBox.getValue (); }
3. 模板特化(Template Specialization)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 template <class T>void print (T a) { cout << "通用模板: " << a << endl; } template <>void print <int >(int a){ cout << "整数特化: " << a << endl; } int main () { print (5 ); print (5.5 ); print ("Hi" ); }
泛型编程的优势
泛型编程的限制
1 2 3 4 5 6 7 8 template <class T>T divide (T a, T b) { return a / b; }
泛型编程要求: 传入的类型必须支持模板内使用的所有运算符。
现实应用
C++ 标准库大量使用泛型编程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <vector> #include <algorithm> using namespace std;int main () { vector<int > intVec = {5 , 2 , 8 , 1 }; vector<string> strVec = {"dog" , "cat" , "bird" }; sort (intVec.begin (), intVec.end ()); sort (strVec.begin (), strVec.end ()); return 0 ; }
vector 和 sort() 都是泛型的,可以处理任何数据类型。
C++ 中的操作符:.、::、->
这三个操作符在 C++ 中有不同的作用。让我为你详细解释:
. 操作符(点操作符)
作用 :用于访问对象的成员(数据成员或成员函数)。
用法 :用于直接对象 或引用 。
1 2 3 4 5 6 7 8 9 class Person {public : string name; void greet () { cout << "Hello" ; } }; Person p; p.name = "Alice" ; p.greet ();
:: 操作符(作用域解析操作符)
作用 :用于访问命名空间、类、枚举 中的成员或全局作用域中的符号。
访问命名空间成员
1 2 3 4 5 6 7 namespace MyNamespace { int x = 10 ; } int main () { cout << MyNamespace::x; }
访问类的静态成员或类型
1 2 3 4 5 6 7 8 class Math {public : static int PI; static void calculate () { } }; Math::PI = 314 ; Math::calculate ();
访问全局作用域
1 2 3 4 5 6 7 int x = 5 ; int main () { int x = 10 ; cout << ::x; cout << x; }
在类外定义成员函数
1 2 3 4 5 6 7 8 class MyClass {public : void myFunction () ; }; void MyClass::myFunction () { cout << "Hello" ; }
-> 操作符(箭头操作符)
作用 :用于访问指针 所指向的对象的成员。
用法 :用于指针对象 。
1 2 3 4 5 6 7 8 9 10 11 class Person {public : string name; void greet () { cout << "Hello" ; } }; Person p; Person* ptr = &p; ptr->name = "Bob" ; ptr->greet ();
等价写法 :
1 2 ptr->name; (*ptr).name;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> using namespace std;class Student {public : string name; static int count; void study () { cout << "Studying..." ; } }; int Student::count = 0 ; int main () { Student s; s.name = "Tom" ; s.study (); Student* ps = &s; ps->name = "Jerry" ; ps->study (); Student::count = 1 ; return 0 ; }
构造函数与析构函数