我经过前期项目的学习和锻炼,现在开始准备学习C++相关的内容,也是为了之后的面试准备内容,C++相较于C语言来说,其实增加的只是几个特性而已,封装、继承、多态;以及C++其实是面向对象的语言,在接触过C#之后对类、对象的概念有了初步的理解,但还不够深刻,希望通过C++的学习可以加深对面向对象编程的理解和使用。

面向对象编程三大核心概念

封装

封装就是将数据(属性)和操作数据的方法(代码)绑定在一起,并隐藏对象的内部实现细节,仅仅暴露必要的接口(方法或者叫函数)给外界使用,确保数据的安全性和可靠性。

封装主要是通过private、public、protected来实现,下面是一个Person类的示例:

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

class Person {
private:
// 私有属性
std::string name;
int age;

public:
// 构造函数
Person(const std::string& name, int age) : name(name), age(age) {}

// Getter 和 Setter 方法
std::string getName() const { return name; }
void setName(const std::string& name) { this->name = name; }

int getAge() const { return age; }
void setAge(int age) {
if(age >= 0) { // 添加验证逻辑
this->age = age;
}
}

// 公共方法
void displayInfo() const {
std::cout << "Name: " << name << ", Age: " << age << std::endl;
}
};

int main() {
Person person("Alice", 30);
person.displayInfo(); // 输出: Name: Alice, Age: 30

// 使用 setter 修改属性
person.setAge(35);
person.displayInfo(); // 输出: Name: Alice, Age: 35

return 0;
}

继承

继承是面向对象的另一个重要概念,一个类可以继承另一个类的属性和方法,从而实现代码的复用和层次化分类,子类不仅拥有父类的属性和方法,还可以自己进行相应的扩展和重写相关的功能模块,用以实现更加具体的行为。下面是继承的实例:

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

class Animal {
protected:
std::string name;

public:
Animal(const std::string& name) : name(name) {}

void sleep() const {
std::cout << name << " is sleeping." << std::endl;
}

virtual void makeSound() const {
std::cout << "Animal makes a sound." << std::endl;
}
};

class Dog : public Animal {
public:
Dog(const std::string& name) : Animal(name) {}

void bark() const {
std::cout << name << " is barking." << std::endl;
}

// 重写父类的方法
void makeSound() const override {
std::cout << name << " barks." << std::endl;
}
};

int main() {
Dog dog("Buddy");
dog.sleep(); // 输出: Buddy is sleeping.
dog.bark(); // 输出: Buddy is barking.
dog.makeSound(); // 输出: Buddy barks.

return 0;
}

多态

多态是面向对象编程的最后一个特性,多态可以使我们以相同的方法处理不同类型的对象,只要他们继承或者实现了相同的类型,简单说,就是子类型的指针赋值给父类型的指针,多态性在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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <iostream>
#include <string>
#include <vector>

class Shape {
protected:
std::string color;

public:
Shape(const std::string& color) : color(color) {}

virtual void draw() const {
std::cout << "Drawing a " << color << " shape." << std::endl;
}

virtual ~Shape() = default; // 虚析构函数
};

class Circle : public Shape {
private:
double radius;

public:
Circle(const std::string& color, double radius) : Shape(color), radius(radius) {}

void draw() const override {
std::cout << "Drawing a " << color << " circle with radius " << radius << "." << std::endl;
}
};

class Rectangle : public Shape {
private:
double width, height;

public:
Rectangle(const std::string& color, double width, double height)
: Shape(color), width(width), height(height) {}

void draw() const override {
std::cout << "Drawing a " << color << " rectangle of width " << width
<< " and height " << height << "." << std::endl;
}
};

int main() {
std::vector<Shape*> shapes;
shapes.push_back(new Circle("red", 5.0));
shapes.push_back(new Rectangle("blue", 3.0, 4.0));

// 多态调用
for(const Shape* shape : shapes) {
shape->draw();
}

// 释放内存
for(Shape* shape : shapes) {
delete shape;
}

return 0;
}

区别于C的一些概念

1
2
3
4
5
6
7
8
9
10
#include <iostream>
using namespace std;

// main() 是程序开始执行的地方

int main()
{
cout << "Hello World"; // 输出 Hello World
return 0;
}

头文件并没有什么区别,第二行叫做命名空间。

命名空间:C++中的一个关键字,在C/C++中变量、函数和类都是大量存在且存在于全局变量域中,就会导致命名冲突,命名空间就是为了解决这个问题。

命名冲突:

1
2
3
4
5
6
7
8
9
10
11
#include<stdio.h>
#include<stdlib.h>
// 命名冲突
// rand 是库中的一个函数
// 自己又定义了一个与其名字相同的一个变量
int rand = 0;
int main()
{
printf("%d", rand);
return 0;
}

定义命名空间:

1
2
3
namespace n1{
int rand = 0;
}

Tip:同一个作用域不能同时出现两个相同的变量,命名空间不影响生命周期。

命名空间的特性:

1.定义变量、函数和类型。

1
2
3
4
5
6
7
8
9
10
11
namespace n1{
int a; //变量
int add(int start, int end){
return start - end;
} //函数
struct listnode{
int val;
struct listnode* next;
} //数据类型

}

2.命名空间可以嵌套。

1
2
3
4
5
6
7
8
9
10
namespace n2{
int a;
int b;
namespace n3{
int c;
int d;
}
}
//此时由于是嵌套,若是要访问变量c应当如下
n2::n3::c

3.同一个工程允许存在多个相同名称的命名空间,编译器最后会合到一起。

在C语言中,存在这局部优先的原则,若是我需要使用某一个域的变量,这时候就需要使用到域作用限定符(::)

1
2
3
4
5
6
7
int a = 0;
int main(){
int a = 1;
printf("%d\n", a); //1
printf("%d\n", ::a); //0
return 0;
}

加上::访问的就是指定域的内容,若前面是空白,那就是全局域,若是某一个命名空间,那就是访问命名空间里的内容;访问命名空间一共有三种方法:

1.加命名空间名称和域作用限定符

1
2
3
4
5
6
7
8
9
10
namespace n1{
int a;
int rand;
}

int main(){
printf("%d\n", n1::a);
printf("%d\n", n1::rand);
return 0;
}

2.利用using namespace 命名空间名称全部展开

1
2
3
4
5
6
7
8
9
10
11
namespace n1{
int a;
int rand;
}
using namespace n1;

int main(){
printf("%d\n", a);
printf("%d\n", rand);
return 0;
}

Tip:值得注意的是,若是全部展开可能又会出现命名冲突的情况。

当然在使用using namespace时,展开的顺序一定不能颠倒,所以针对某些特定的命名冲突问题,需要单独进行讨论,由于全部展开的这种方式存在一些弊端,所以我们有第三个方法进行展开,那就是部分展开,用到什么展开什么。

3.使用using将命名空间中的成员进行展开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
namespace n1
{
int f = 0;
int rand = 0;
}
using n1::f;

int main()
{
f += 2;
printf("%d\n", f);
n1::rand += 2;
printf("%d\n", n1::rand);
}

那么在C++的标准库命名空间就是std。

1
2
3
4
5
6
7
#include<iostream>
using namespace std; //std 是封C++库的命名空间
int main()
{
cout << "hello world" << endl; // hello world
return 0;
}

若是省去using namespace std,则需要改成以下形式:

1
2
3
4
5
6
#include<iostream>
int main()
{
std::cout << "hello world" << std::endl;
return 0;
}

在C++中,标准的输入和输出和C中有些许不同,使用标准输出cout,必须包含iostream头文件和std标准空间,为了正确的使用命名空间,规定C++头文件不带.h的后缀。

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
using namespace std;

int main(){
int a;
double b;
char c;
cin >> a >> b >> c;
cout << a << ' ' << b << ' ' << c << endl;
return 0;
}

C++关键字和数据类型

C++的数据类型,大部分和C语言相同,多一个宽字符型。

1
typedef short int wchar_t;

实际上和short int所占的空间是一样的,一些基本类型可以使用一个或者多个类型修饰符进行修饰。

修饰符:signed;unsigned;short;long。

float和double的范围计算:

【详细解说】单精度浮点数float取值范围_float范围-CSDN博客

主要的难点在于偏移量,float是127,double是1023。

C++的类型转换

类型转换分为四类:静态转换、动态转换、常量转换和重新解释转换。

静态转换:将数据从一个类型强行转换为另一种类型,通常用于类型相似之间的转换,且不会进行类型的检查,可能会导致运行错误。

动态转换:将一个基类指针或引用转换为派生类的指针或引用,会进行类型检查,不能转换则会返回空指针和报错。

常量转换:用于将const类型的对象转换为非const的对象,只能转换掉属性,而不能改变其类型。

重新解释转换:将一个类型的值重新解释为另一个数据类型的值,用于不同数据类型的转换。