基础概念
- 命名空间
声明命名空间: namespace xurui { int age = 1; char * name = "xurui"; } 使用命名空间(要求放在声明之后,可以在文件头调用,也可以在函数内调用): using namespace xurui; 访问命名空间内的成员: xurui::name 或 name(没用冲突时,可以省略命名空间::) 复制代码
#include<iostream> // C++ 标准库 using namespace std; // 命名空间,类似于 Java 的内部类 cout<< "C++" << endl; // 引用引用了std::命名空间,所以可以省略 复制代码
提示: << 是操作符重载
- C++ 中的 bool 类型
C 语言没有布尔类型,C++ 中的 bool 类型其实是对 int 的包装。
cout << true << endl; 输出:1 复制代码
- C 与 C++ 中常量的区别
C:假常量 const int number = 100; int * numP = &number; *numP = 10000; printf("%d\n", number); // 10000,“常量了” 被修改 C++:真常量 const int number = 100; // 有的编译器不通过,有的可以通过但运行报错 int * numP = &number; 复制代码
- 引用的原理
引用变量就是一个另一个变量别名,一旦把引用初始化为某个变量,就可以使用该引用名称来访问变量。
引用 vs 指针
引用很容易与指针混淆,它们之间有三个主要的不同:
- 1、不存在空引用,引用必须连接到一块合法的内存。存在野指针 / 悬空指针,野指针(wild pointer)是未初始化过的指针,悬空指针(dangling pointer)是指向已经被释放的内存的指针;
- 2、引用必须在创建时被初始化,指针可以在任何时间被初始化;
- 3、一旦引用被初始化为一个对象,就不能被指向到另一个对象,指针可以在任何时候指向到另一个对象。
- 默认形参
int add(int n1 = 0, int n2 = 1) { } 复制代码
默认形参可以在函数声明是指定,也可以在函数定义时指定。
- 可变参数
宏 | 描述 |
va_list | 参数列表指针 arg_ptr |
va_start(arg_ptr, argN) | 使参数列表指针 arg_ptr 指向函数参数列表中的第一个可选参数,argN 是位于第一个可选参数之前的固定参数 |
va_arg(arg_ptr, type) | 返回参数列表中指针 arg_ptr 所指的参数, 返回类型为 type. 并使指针 arg_ptr 指向参数列表中下一个参数 |
va_end(arg_ptr) | 清空参数列表, 并置参数指针 arg_ptr 无效 |
#include <stdarg.h> using namespace std; void sum(int count, ...) { va_list vp; va_start(vp, count); for (int index = 0; index < count; index++) { int number = va_arg(vp, int); cout << number << endl; } va_end(vp); } int main() { // 输出: // 1 // 2 // 3 sum(3, 1, 2, 3); return 0; } 复制代码
- 函数重载: 与 C 语言不同,C++ 支持函数重载
需要注意,如果你定义了带默认形参的函数,需要确认没有参数更少的重载函数,否则会报错:Call to 'add' is ambiguous
。例如:
int add(int n1){ return n1; } int add(int n1 = 1,int n2 = 1){ return n1 + n2; } int main(){ add(1); (X)// Call to 'add' is ambiguous } 复制代码
- 运算符重载:
重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。
Student .h: class Student { public: Student operator+(Student student); }; Student .cpp: Student Student::operator+(Student student) { return Student(); } ---------------------------------------------- 更严格的写法: class Student { public: Student operator+(const Student &student); }; 注意:如果没有使用 &,函数调用时会额外构建一个对象副本(拷贝构造函数) Student Student::operator+(const Student &student) { return Student(); } 复制代码
类与对象
- 类声明的规范
使用头文件 .h 声明类成员,使用 .cpp 实现类成员。这是 C++ 类定义的规范。目的是在公开 so 库时,只需要向客户端提供 .h 头文件就可以调用 so 库内的实现。
- 声明类
Student.h
class Student { // 私有成员 private: char*name; int age; }; 复制代码
- 实现类
Student.cpp
class Student { public: char *getName() const; void setName(char *name); int getAge() const; void setAge(int age); // 私有成员 private: char*name; int age; }; 复制代码
Student.cpp
char *Student::getName() const { return name; } void Student::setName(char *name) { Student::name = name; // 或 this->name = name; } int Student::getAge() const { return age; } void Student::setAge(int age) { Student::age = age; // 或 this->age = age; } 复制代码
提示: 在
Student.cpp
编辑页面按下Alt+insert
,可以选择自动插入 setter/getter 等函数
- 对象的静态开辟和动态开辟
静态开辟:栈 Student student; // 未调用构造函数 student.setAge(10); student.setName("mark"); cout << "name:" << student.getAge() << ",name:" << student.getName() << endl; Student student2("Amy") // 会调用构造函数 // 函数出栈是会隐式调用 delete 动态开辟:堆 Student *student = new Student(); // 会调用构造函数 student->setAge(10); student->setName("mark"); cout << "name:" << student->getAge() << ",name:" << student->getName() << endl; if (student) { delete student; // new 分配的空间用 delete student = NULL; // 防止存现悬空指针 // free(student); // (X) malloc 分配的空间用 free } 复制代码
注意: new 分配的空间用 delete,malloc 分配的空间用 free。malloc 不会调用构造函数,free 不会调用析构函数。
- 构造函数 & 析构函数
声明 class Student { public: Student(); Student(char*name); ~Student(); }; ---------------------------------------------- // 调用另一个重载构造函数 Student::Student() : Student("mark") { } Student::Student(char *name) { this->name = (char *) (malloc(sizeof(char) * 10)); strcpy(this->name, name); } Student::~Student() { // 必须释放构造器中开辟的堆区成员 if (this->name) { free(this->name); this->name = NULL; } } 复制代码
提示: new 会调用构造函数,delete会调用析构函数。
- 拷贝构造函数
1、当类中没有定义任何构造函数时,编译器会默认提供一个无参构造函数且其函数体为空; 2、当类中没有定义拷贝构造函数时,编译器会默认提供一个拷贝构造函数,进行成员变量之间的拷贝。(这个拷贝操作是浅拷贝)。 3、调用一次构造函数,调用一次默认拷贝构造函数(浅拷贝),两个对象的指针成员所指同一块内存。调用两次析构函数,内存空间重复释放导致奔溃。
情况 1:指针赋值,没有调用拷贝构造函数 Student* student1 = new Student(); Student* student2 = student1; 情况 2:隐式调用拷贝构造函数 Student student1; Student student2 = student1; 情况 3:不会调用自定义的拷贝构造函数,但会调用默认拷贝构造函数 Student student1; Student student2; student2 = student1; 复制代码
提示: struct 也有类似的浅拷贝。
- 静态成员1、可以使用 类名:: 直接访问静态成员(字段/函数) 2、静态字段需要声明与实现; 3、静态成员只能访问静态成员。
Student.h
class Student { public: 1、先声明静态成员: static int id; static void update(){ id = 2; } }; 复制代码
Student.cpp
2、再实现静态成员(必须): int Student::id = 1; 复制代码
Student student; cout << student.id << endl; Student::update(); cout << student.id << endl; 输出: 1 2 复制代码
- 友元函数
1、友元函数是在类中声明,在类外部实现的函数; 2、友元函数不是成员函数,但是它可以访问类中的私有成员; 3、友元函数一定程度上破坏了类的封装性。
Student.h
class Student { public: friend void setAge(Student * student,int age); private: int age; }; 复制代码
main.cpp
void setAge(Student *student, int age) { student->age = age; } int main() { Student student; setAge(&student, 1); // 输出: // 1 cout<<student.getAge()<<endl; return 0; } 复制代码
- 友元类友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。
class ImageView { private: int viewSize; friend class Class; // 友元类 }; class Class { public: ImageView imageView; void changeViewSize(int size) { imageView.viewSize = size; // 如果不声明友元类 “friend class Class”,无法访问 } }; 复制代码
思考: 在 Java 中,私有成员无法直接访问,但是使用 Class#getDeclareField() 却可以访问,底层原理是不是 C++ 友元类?
- 继承
class Rectangle: public Shape, public PaintCost { public: int getArea() { return (width * height); } }; 复制代码
多继承有二义性,会导致程序不够健壮,所以 Java 的设计里面中不支持多继承。在 C++ 中,因为存在多继承,所以在开发过程中要主动避免程序二义性。如果已经出现二义性的场景,有两种解决方案: 1、可以使用类名::变量名
显性访问来规避二义性; 2、使用虚继承;
- 继承类型
当一个类派生自基类,该基类可以被继承为 public、protected 或 private 几种类型。使用 public 继承,不同继承类型规则:
1、公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。 2、保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。 3、私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
- 虚继承
为了解决多继承时的命名二义性和冗余数据问题,C++ 设计出虚继承,使得在派生类中只保留一份间接基类的成员。
// 间接基类A class A{ protected: int m_a; }; // 直接基类B class B: virtual public A{ // 虚继承 protected: int m_b; }; // 直接基类C class C: virtual public A{ // 虚继承 protected: int m_c; }; 复制代码
- 多态
C++ 默认关闭多态(或者说是静态多态 / 静态链接):函数调用在程序执行前就确定了。如果要开启多态,需要在 父类 函数上声明virtual
(声明为虚函数)。
- 纯虚函数
C++ 接口没有直接的抽象类 / 接口的概念,但是可以使用纯虚函数来达到类似的语义。纯虚函数声明如下:
virtual void funtion1()=0; 复制代码
纯虚函数用来规范派生类的行为,即接口,纯虚函数一定没有定义。包含纯虚函数的类是抽象类,抽象类不能定义实例,但可以声明指向实现该抽象类的具体类的指针或引用。
模板(类似泛型)
- 函数模板
template <class T> void swap(T& a, T& b){ } 复制代码
- 类模板
template<class T> class A{ public: T a; T b; T hy(T c, T &d); }; 复制代码
STL 容器
类 | 描述 |
vector | 动态数组 |
list | 链表 |
stack | 栈 |
queue | 队列 |
set | 红黑树,自动排序,无重复 |
multiset | 红黑树,自动排序,有重复 |
map | 红黑树,有序的键 / 值对,有重复 |
multimap | 红黑树,有序的键 / 值对,无重复 |