C++学习笔记2

最近上班需要用到C++,之前只系统的学习过C,没有了解过C++的一些特性,业余时间继续去学习一下C++的语法和特性(与C语言相同的部分不再赘述)

字符串、向量和数组

string和vector是两种最重要的标准库类型,前者可支持边长字符串,后者表示可变长集合。

标准库类型string

标准库类型string表示可变长的字符序列,使用string类型必须要包含string头文件(#include<string>)

定义和初始话string

如何初始话类的对象由类本身决定的,一个类可以定义很多种初始话对象的方式,这些方式之间必须有所区别,或者是初始值数量不同,或者是初始值类型不同,例如:

1
2
3
4
string s1; // 默认初始化,s1是空字符串
string s2 = s1; // s2是s1的副本
string s3 = "hello"; // s3是该字符串字面值的副本
string s4(10, 'c'); // s4的内容是10个字符c

string对象上的操作

操作 说明
os << s 将s写道输出流os中,返回os
is >> s 从is中读取字符串赋值给s,字符串以空白分割,返回is
getline(is, s) 从is中读取一行赋给s,返回is
s.empty() s为空返回true,否则返回false
s.size() 返回s中的字符个数
s[n] 返回s中第n个字符的引用
s1+s2 返回s1和s2连接后的结果
s1=s2 用s2的副本代替s1中原来的字符
s1 == s2 如果s1和s2中的字符完全一样,则相等,大小写敏感
<, <=, >, >= 利用字符在字典中的顺序进行比较,且对字母的大小写敏感

标准库类型vector

vector表示对象的集合,其中所有对象的类型都相同。集合中的每个对象都有一个与之对应的索引,索引用于访问对象。

要想使用vector需要包含头文件<vector>,vector是模板而非类型,由vector生成的类型必须包含vector中的元素类型,例如vector[int]

定义和初始化vector对象

初始化vector对象的常用方法:

操作 说明
vector<T> v1 v1是一个空 vector,它潜在的元素是T类型的,执行默认初始化
vector<T> v2 (v1) v2中包含所有v1元素的副本,等价于v2 = v1
vector<T> v3 (n, val) v3包含了n个val
vector<T> v4 (n) 包含了n个重复执行了值初始话的对象
vector<T> v5 {a, b, c…} 每个元素被赋予了初始值,等价于v5 = {a, b, c…}

向vector对象中添加元素

使用v.push_back(var)

C++中无需指定元素数量效率最高,与Java等语言不同

其他vector操作

v.empty():判断是否为空

v.size():判断v中元素的个数

v.push_back(var):向v的尾部添加值为var的元素

v[n]:返回v中第n个位置上的元素引用

C++ 面向对象

C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++ 的核心特性,通常被称为用户定义的类型。类用于指定对象的形式,是一种用户自定义的数据类型,它是一种封装了数据和函数的组合。类中的数据称为成员变量,函数称为成员函数。类可以被看作是一种模板,可以用来创建具有相同属性和行为的多个对象。

类的定义

定义一个类需要使用关键字 class,然后指定类的名称,并类的主体是包含在一对花括号中,主体包含类的成员变量和成员函数。定义一个类,本质上是定义一个数据类型的蓝图,它定义了类的对象包括了什么,以及可以在这个对象上执行哪些操作。类成员可以被定义为 public、private 或 protected。默认情况下是定义为 private(如果使用struct构造类,默认情况下定义为public)。

1
2
3
4
5
6
class classname
{
Access specifiers: // 访问修饰符 private/pubilc/protected
Data members/variables // 变量
Member functions(){} // 方法
}; // 分号结束一个类

构造函数

类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。

构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。

析构函数

类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。

析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。

this指针

在 C++ 中,this 指针是一个特殊的指针,它指向当前对象的实例。

在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。

this是一个隐藏的指针,可以在类的成员函数中使用,它可以用来指向调用对象。

拷贝构造函数

拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:

  • 通过使用另一个同类型的对象来初始化新创建的对象。
  • 复制对象把它作为参数传递给函数。
  • 复制对象,并从函数返回这个对象。

如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。拷贝构造函数的最常见形式如下:

1
2
3
classname (const classname &obj) {
// 构造函数的主体
}

友元函数

类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员,友元函数并不是成员函数

友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。

如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend

1
2
3
4
5
6
7
class ClassOne
{
double width;
public:
friend void printWidth(ClassOne name); // 友元函数
friend class ClassTwo;// 声明类 ClassTwo 的所有成员函数作为类 ClassOne 的友元,需要在类 ClassOne 的定义中放置该声明,ClassTwo中成员函数可访问ClassOne受保护的和私有的成员
};

一般在类定义的开始或结束前的位置集中声名友元

静态成员

我们可以使用 static 关键字来把类成员定义为静态的。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。

静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化。

静态成员函数

如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。

静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。静态成员函数有一个类范围,他们不能访问类的 this 指针(因为类实例不存在时也能调用静态成员函数)。

指向类的指针

一个指向 C++ 类的指针与指向结构的指针类似,访问指向类的指针的成员,需要使用成员访问运算符 **->**,就像访问指向结构的指针一样。与所有的指针一样,必须在使用指针之前,对指针进行初始化。在 C++ 中,指向类的指针指向一个类的对象,与普通的指针相似,指向类的指针可以用于访问对象的成员变量和成员函数。

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
#include <iostream>

class MyClass {
public:
int data;

void display() {
std::cout << "Data: " << data << std::endl;
}
};

int main() {
// 创建类对象
MyClass obj;
obj.data = 42;
// 声明和初始化指向类的指针
MyClass *ptr = &obj;

// 动态分配内存创建类对象
// MyClass *ptr = new MyClass;
// ptr->data = 42;

// 通过指针访问成员变量
std::cout << "Data via pointer: " << ptr->data << std::endl;

// 通过指针调用成员函数
ptr->display();

return 0;
}

继承

面向对象程序设计中最重要的一个概念是继承。继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行时间的效果。

当创建一个类时,不需要重新编写新的数据成员和成员函数,只需要指定新建的类集成一个现有的类,现有的类被称为基类,新建的类被称为派生类。

1
class 派生类名: 访问修饰符 基类

访问修饰符包括public、protected、private,不声明默认为private

继承时访问标识符的作用主要是控制父类成员在子类中的可见性和访问权限。具体行为是:

  • public 继承:父类的 public 成员保持 publicprotected 成员保持 protectedprivate 成员无法访问。
  • protected 继承:父类的 public 成员变为 protectedprotected 成员保持 protectedprivate 成员无法访问。
  • private 继承:父类的 publicprotected 成员都变为 privateprivate 成员无法访问。

多继承

1
2
3
4
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…
{
<派生类类体>
};

重载

C++允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载。重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它们的参数列表和定义(实现)不相同。

函数重载

在同一个作用域可以声明几个功能类似的同名函数,但是这些参数的形式参数(参数类型、个数、顺序等)必须不同,不能仅通过返回值类型不同去重载函数。

运算符重载

重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。

1
2
3
4
5
6
Human operator+(const Human& b) {
Human human;
human.age = this->age + b.age;
human.weight = this->weight + b.weight;
return human;
}

多态

当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态,多态意味着调用成员函数时会根据调用函数的对象的类型来执行不同的函数。

如果在子类中重写父类的方法,在子类调用该方法时想执行子类重写的方法,就需要把父类中的对应函数用

virtual关键字修饰,这样的函数被称作虚函数,在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。我们想根据调用的对象类型去选择调用的函数,这种擦做叫做动态链接货哦这后期绑定。

纯虚函数

1
2
3
4
5
6
7
8
class Shape {
public:
// 纯虚函数
virtual void draw() = 0;

// 析构函数通常会声明为虚函数
virtual ~Shape() = default;
};

我们可能想在基类定义虚函数,但是又不想给虚函数有意义的实现,就可以直接定义虚函数=0,告诉编译器函数没有主体,上面的虚函数是纯虚函数。

纯虚函数的特点:

  1. 抽象类:包含纯虚函数的类是抽象类,不能被实例化。
  2. 强制派生类实现:派生类如果继承自包含纯虚函数的类,必须在派生类中实现这些纯虚函数,除非派生类本身也是抽象类。
  3. 可以有成员变量:纯虚函数的类仍然可以有成员变量、构造函数和其他成员函数。
  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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
using namespace std;

class Shape {
public:
// 纯虚函数
virtual void draw() = 0;

// 析构函数通常会声明为虚函数
virtual ~Shape() = default;
};

class Circle : public Shape {
public:
// 必须实现 draw 函数
void draw() override {
cout << "Drawing a Circle" << endl;
}
};

class Square : public Shape {
public:
// 必须实现 draw 函数
void draw() override {
cout << "Drawing a Square" << endl;
}
};

int main() {
// Shape s; // 错误,无法实例化抽象类

Shape* shape1 = new Circle();
Shape* shape2 = new Square();

shape1->draw(); // 输出: Drawing a Circle
shape2->draw(); // 输出: Drawing a Square

delete shape1;
delete shape2;

return 0;
}

在 C++11 及其后续版本中,**override** 关键字用于标明派生类中的虚函数是对基类虚函数的重写。虽然加上 override 不是强制性的,但推荐使用,它能提供更强的类型检查,帮助你捕获潜在的错误。


C++学习笔记2
https://chujian521.github.io/blog/2024/04/13/C-学习笔记2/
作者
Encounter
发布于
2024年4月13日
许可协议