面向过程是一种编程范式,它主要关注程序的执行过程和步骤。程序员通过将问题分解为一个个具体的步骤和函数,逐步解决问题。面向过程的编程语言强调函数的调用和顺序的执行。
特点:
示例语言:
示例代码(C语言):
#include <stdio.h>
// 函数定义
void greet() {
printf("Hello, World!\n");
}
int main() {
// 调用函数
greet();
return 0;
}
在这个例子中,我们定义了一个叫做 greet
的函数,这个函数打印问候语。main
函数调用 greet
函数来实现问候。
示例语言:
简单示例(C++):
#include <iostream>
using namespace std;
// 定义一个叫做 Person 的类
class Person {
public:
// 类的属性
string name;
// 类的方法
void greet() {
cout << "Hello, " << name << "!" << endl;
}
};
int main() {
// 创建一个 Person 对象
Person person;
person.name = "World";
// 调用对象的方法
person.greet();
return 0;
}
在这个例子中,我们定义了一个 Person
类,这个类有一个 name
属性和一个 greet
方法。我们创建了一个 Person
对象,设置了它的 name
属性,然后调用它的 greet
方法来打印问候语。
在 C 语言中,我们使用结构体(struct)来定义一组相关的变量。例如,一个表示点的结构体可以包含 x
和 y
坐标:
// C 语言的结构体
struct Point {
int x;
int y;
};
在这个例子中,Point
结构体只能包含变量 x
和 y
,不能包含函数。
在 C++ 中,结构体不仅可以包含变量,还可以包含函数。这使得结构体比 C 语言中的结构体更加强大和灵活。例如,我们可以在结构体中定义一个函数来打印点的坐标:
// C++ 中的结构体
struct Point {
int x;
int y;
// 结构体中的函数
void print() {
cout << "Point(" << x << ", " << y << ")" << endl;
}
};
不过,在 C++ 中,更常用的是类(class),因为类提供了更多的功能和控制。类和结构体在语法上很相似,但有一些重要的区别:
我们可以使用类来定义一个更加完整的点类,包括私有变量和公有函数:
#include <iostream>
using namespace std;
// 定义一个 Point 类
class Point {
private:
int x;
int y;
public:
// 构造函数
Point(int xVal, int yVal) {
x = xVal;
y = yVal;
}
// 公有函数
void print() {
cout << "Point(" << x << ", " << y << ")" << endl;
}
};
int main() {
// 创建一个 Point 对象
Point point(3, 4);
// 调用对象的方法
point.print();
return 0;
}
在这个例子中,我们定义了一个 Point
类,它有私有的 x
和 y
变量,以及一个构造函数来初始化这些变量。我们还定义了一个 print
函数来打印点的坐标。这样,类不仅封装了数据,还提供了操作数据的方法。
通过引入类,C++ 提供了更强大的工具来组织和管理代码,使得代码更易于维护和扩展。
类是一个模板,它定义了一种新的数据类型,这种类型包含数据(变量)和功能(函数)。可以把类想象成一种蓝图,用来创建对象(具体的实例)。
用 class
关键字来定义一个类。类的名字可以是你选择的任何名字,类的主体包含变量和函数。在类定义的最后要加上一个分号 ;
。
我们来定义一个表示点(Point)的类,这个类有两个属性 x
和 y
,以及一个打印点坐标的方法。
#include <iostream>
using namespace std;
class Point {
public:
int x; // 成员变量
int y; // 成员变量
// 成员函数
void print() {
cout << "Point(" << x << ", " << y << ")" << endl;
}
};
int main() {
Point point; // 创建一个 Point 对象
point.x = 3; // 设置属性 x
point.y = 4; // 设置属性 y
point.print(); // 调用成员函数
return 0;
}
在这个例子中,Point
类定义了两个变量 x
和 y
,还有一个打印点坐标的函数 print
。在 main
函数中,我们创建了一个 Point
对象,设置了它的 x
和 y
值,然后调用 print
方法打印坐标。
class Point {
public:
int x;
int y;
void print() {
cout << "Point(" << x << ", " << y << ")" << endl;
}
};
Point.h:
#ifndef POINT_H
#define POINT_H
class Point {
public:
int x;
int y;
void print();
};
#endif // POINT_H
Point.cpp:
#include <iostream>
#include "Point.h"
using namespace std;
void Point::print() {
cout << "Point(" << x << ", " << y << ")" << endl;
}
main.cpp:
#include "Point.h"
int main() {
Point point;
point.x = 3;
point.y = 4;
point.print();
return 0;
}
class
定义,包含变量和函数。C++ 提供了访问限定符来控制类成员(属性和方法)的访问权限。通过这些限定符,我们可以实现封装,让类更加安全和易于维护。
class
中,默认访问权限是 private
。在 struct
中,默认访问权限是 public
。示例:
#include <iostream>
using namespace std;
class Person {
public:
string name; // 公有成员变量
protected:
int age; // 保护成员变量
private:
string secret; // 私有成员变量
public:
// 构造函数
Person(string n, int a, string s) : name(n), age(a), secret(s) {}
// 公有成员函数
void showInfo() {
cout << "Name: " << name << ", Age: " << age << endl;
}
// 私有成员函数
void showSecret() {
cout << "Secret: " << secret << endl;
}
};
int main() {
Person person("Alice", 30, "Loves coding");
// 直接访问公有成员
cout << "Name: " << person.name << endl;
// 直接调用公有成员函数
person.showInfo();
// 尝试访问保护和私有成员会导致编译错误
// cout << "Age: " << person.age << endl; // 错误
// cout << "Secret: " << person.secret << endl; // 错误
return 0;
}
struct
和 class
的区别在 C++ 中,struct
可以用来定义类,其功能与 class
基本相同,唯一的区别在于默认的访问权限不同:
struct
默认访问权限是 public
。class
默认访问权限是 private
。示例:
struct Point {
int x;
int y;
void print() {
cout << "Point(" << x << ", " << y << ")" << endl;
}
};
class Circle {
private:
int radius;
public:
void setRadius(int r) {
radius = r;
}
void showRadius() {
cout << "Radius: " << radius << endl;
}
};
封装是面向对象编程的三大特性之一。它将数据和操作数据的方法有机结合,隐藏对象的属性和实现细节,仅对外公开接口来与对象进行交互。
现实例子: 就像使用计算机时,我们不需要知道内部的工作原理,只需要通过键盘、鼠标、显示器等接口与计算机交互即可。
在 C++ 中实现封装: 通过类将数据和方法结合在一起,并使用访问限定符来控制访问权限。
示例:
class Computer {
private:
string cpu;
int memory;
public:
// 构造函数
Computer(string c, int m) : cpu(c), memory(m) {}
// 公有成员函数
void start() {
cout << "Computer started with CPU: " << cpu << " and Memory: " << memory << "GB" << endl;
}
};
int main() {
Computer myComputer("Intel i7", 16);
myComputer.start(); // 通过公开的接口与对象交互
// 直接访问私有成员会导致编译错误
// cout << myComputer.cpu << endl; // 错误
return 0;
}
public
、protected
和 private
。struct
和 class
的区别:struct
默认访问权限是 public
,class
默认访问权限是 private
。在 C++ 中,类定义了一个新的作用域。类的所有成员(变量和函数)都在这个类的作用域中。如果在类体外定义成员函数,需要使用 ::
作用域操作符来指明成员属于哪个类。
什么是作用域? 作用域指的是变量或函数在程序中可以被访问的区域。在类中,类的作用域指的是类的所有成员变量和成员函数的可访问范围。 作用域操作符
::
作用域操作符::
用于指定一个变量或函数属于哪个作用域。在类体外定义成员函数时,需要使用::
来指明函数属于哪个类。
假设我们有一个表示点(Point)的类,包含两个成员变量 x
和 y
以及一个打印点坐标的成员函数 print
。
我们可以在类体内声明成员函数,然后在类体外定义它们。
在头文件(如 Point.h
)中声明类和成员函数:
#ifndef POINT_H
#define POINT_H
class Point {
public:
int x;
int y;
// 声明成员函数
void print();
};
#endif // POINT_H
在源文件(如 Point.cpp
)中定义成员函数:
#include <iostream>
#include "Point.h"
using namespace std;
// 使用作用域操作符 :: 来定义成员函数
void Point::print() {
cout << "Point(" << x << ", " << y << ")" << endl;
}
在主文件(如 main.cpp
)中使用这个类:
#include "Point.h"
int main() {
Point point;
point.x = 3;
point.y = 4;
point.print(); // 调用成员函数
return 0;
}
::
指明成员属于哪个类。::
作用域操作符在类体外定义成员函数类的实例化是用类类型创建对象的过程。可以把类看作一种描述或模板,通过这个模板可以创建具体的对象。
类就像一个模型或模板,它定义了对象的成员(变量和函数),但本身并不占用内存空间。例如,一个 Person
类可能包含成员变量 name
和 age
,以及成员函数 printInfo
,但只有在实例化出具体对象时,这些成员才会实际占用内存空间。
类的例子:
class Person {
public:
string name;
int age;
void printInfo() {
cout << "Name: " << name << ", Age: " << age << endl;
}
};
现实类比: 类就像一张空的学生信息表,它描述了学生有哪些信息(比如姓名和年龄),但没有具体存储任何学生的信息。只有在填写表格时,才会有具体的学生数据。
一个类可以实例化出多个对象,这些对象占用实际的物理空间,用于存储类的成员变量。
实例化对象的例子:
int main() {
// 创建 Person 类的对象(实例化)
Person person1;
person1.name = "Alice";
person1.age = 30;
Person person2;
person2.name = "Bob";
person2.age = 25;
// 调用对象的成员函数
person1.printInfo();
person2.printInfo();
return 0;
}
在这个例子中,我们实例化了两个 Person
对象,分别是 person1
和 person2
。这些对象各自拥有自己的 name
和 age
,并实际占用了内存空间。
类实例化出对象就像使用建筑设计图建造房子:
类与对象的关系:
在 C++ 中,类的对象模型描述了类的实例(对象)在内存中的存储方式。了解这个模型对于优化程序性能和理解内存管理非常重要。
问题: 一个类的对象包含什么?如何计算一个类的大小?
回答: 一个类的对象包含类的成员变量,但不直接包含成员函数。成员函数在公共的代码段中存储。类的对象大小实际上是该类中所有成员变量的大小之和,考虑内存对齐后得到的结果。
示例:
class MyClass {
public:
int a;
double b;
void myFunction() {}
};
int main() {
MyClass obj;
cout << "Size of MyClass: " << sizeof(MyClass) << endl; // 输出类对象的大小
return 0;
}
对象模型猜测:
这样可以节省内存空间,因为多个对象共享同一份成员函数代码。
存储方式总结:
示例:
class MyClass {
public:
int a;
double b;
void myFunction() {
cout << "Hello" << endl;
}
};
int main() {
MyClass obj1;
MyClass obj2;
// 输出类对象的大小
cout << "Size of MyClass: " << sizeof(MyClass) << endl;
return 0;
}
内存对齐是为了提高内存访问效率,使得 CPU 可以快速读取和写入数据。
内存对齐规则:
示例:
#include <iostream>
using namespace std;
struct MyStruct {
char a;
int b;
double c;
};
int main() {
cout << "Size of MyStruct: " << sizeof(MyStruct) << endl; // 输出结构体大小
return 0;
}
内存对齐示例:
char
对齐到 1 字节int
对齐到 4 字节double
对齐到 8 字节计算大小:
a
从偏移量 0 开始,占 1 字节。b
需要对齐到 4 字节,从偏移量 4 开始,占 4 字节。c
需要对齐到 8 字节,从偏移量 8 开始,占 8 字节。结构体怎么对齐?为什么要进行内存对齐?
回答: 结构体的每个成员按照对齐规则对齐,具体规则如上所述。内存对齐是为了提高内存访问效率,使 CPU 可以快速读取和写入数据。
如何让结构体按照指定的对齐参数进行对齐?能否按照 3、4、5 即任意字节对齐?
回答: 可以使用编译器提供的指令来指定对齐参数,例如 #pragma pack
指令。可以按照任意字节对齐,但通常使用的是 1、2、4、8 等字节对齐。
#pragma pack(push, 1)
struct MyStruct {
char a;
int b;
double c;
};
#pragma pack(pop)
什么是大小端?如何测试某台机器是大端还是小端,有没有遇到过要考虑大小端的场景?
回答: 大端(Big-endian)和小端(Little-endian)是两种字节序,决定了多字节数据的存储顺序。大端将最高字节存储在最低地址,小端将最低字节存储在最低地址。
测试方法:
#include <iostream>
using namespace std;
int main() {
unsigned int x = 1;
char *c = (char*)&x;
if (*c)
cout << "Little-endian" << endl;
else
cout << "Big-endian" << endl;
return 0;
}
场景: 网络传输、文件存储时需要考虑大小端问题,因为不同系统可能使用不同的字节序,需要进行转换以确保数据正确解析。
当我们定义一个类,并在类中包含成员函数时,这些函数需要知道它们是属于哪个对象。例如,我们定义一个日期类 Date
:
class Date {
public:
int day;
int month;
int year;
void Init(int d, int m, int y) {
day = d;
month = m;
year = y;
}
void Print() {
cout << day << "/" << month << "/" << year << endl;
}
};
问题: 当我们有两个对象 d1
和 d2
时,调用 d1.Init(1, 1, 2020)
和 d2.Init(2, 2, 2021)
,函数是如何区分这两个对象的?
解决方法: C++ 编译器通过引入 this
指针来解决这个问题。this
指针是一个隐藏的指针参数,指向当前对象(即调用成员函数的对象)。在成员函数内部,所有的成员变量访问都是通过 this
指针实现的。
类类型* const
,即 this
指针是指向类对象的常量指针,不能修改 this
指针的指向。this
指针只能在成员函数中使用。this
指针。所以对象本身并不存储 this
指针。this
指针是成员函数的第一个隐含的指针形参,编译器会自动处理,不需要用户传递。示例:
class Date {
public:
int day;
int month;
int year;
void Init(int d, int m, int y) {
this->day = d;
this->month = m;
this->year = y;
}
void Print() {
cout << this->day << "/" << this->month << "/" << this->year << endl;
}
};
this
指针存储在成员函数的形参列表中,由编译器在调用成员函数时自动传递,通常通过寄存器(如 ecx
寄存器)传递。
this
指针不会为空,因为它指向的是当前调用成员函数的对象。但是在某些特定情况下(如对象被错误地删除或未正确初始化),this
指针可能会变成空指针或指向无效地址。
在 C 语言中,实现 Stack 通常需要定义一个结构体,并且所有操作都通过函数来实现。
示例:
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int *data;
int top;
int capacity;
} Stack;
// 初始化栈
void InitStack(Stack *s, int capacity) {
s->data = (int *)malloc(capacity * sizeof(int));
s->top = -1;
s->capacity = capacity;
}
// 压栈
void Push(Stack *s, int value) {
if (s->top < s->capacity - 1) {
s->data[++s->top] = value;
}
}
// 出栈
int Pop(Stack *s) {
if (s->top >= 0) {
return s->data[s->top--];
}
return -1; // 栈空
}
// 打印栈
void PrintStack(Stack *s) {
for (int i = 0; i <= s->top; i++) {
printf("%d ", s->data[i]);
}
printf("\n");
}
在 C 语言中,每个函数都需要传递 Stack
指针作为参数,并且需要手动检查指针是否为 NULL。
在 C++ 中,通过类可以将数据和操作数据的方法结合在一起。这样使用时更方便,且更加符合人类对事物的认知。
示例:
#include <iostream>
using namespace std;
class Stack {
private:
int *data;
int top;
int capacity;
public:
// 构造函数
Stack(int capacity) {
this->data = new int[capacity];
this->top = -1;
this->capacity = capacity;
}
// 析构函数
~Stack() {
delete[] data;
}
// 压栈
void Push(int value) {
if (top < capacity - 1) {
data[++top] = value;
}
}
// 出栈
int Pop() {
if (top >= 0) {
return data[top--];
}
return -1; // 栈空
}
// 打印栈
void PrintStack() {
for (int i = 0; i <= top; i++) {
cout << data[i] << " ";
}
cout << endl;
}
};
int main() {
Stack s(10);
s.Push(1);
s.Push(2);
s.Push(3);
s.PrintStack();
s.Pop();
s.PrintStack();
return 0;
}
在 C++ 中,Stack
类将数据和操作方法结合在一起,使用时不需要显式传递 Stack
指针,因为编译器会自动处理 this
指针。
this
指针指向当前对象。