公众号:阿Q技术站
struct MyStruct {
int x; // 默认是 public
void foo() {} // 默认是 public
};
class MyClass {
int y; // 默认是 private
void bar() {} // 默认是 private
};
语法上和使用上的区别
定义方式:虽然 struct
和 class
可以用来定义数据成员和成员函数,但使用 class
更常见于表示具有行为和状态的对象,而 struct
更常用于表示纯粹的数据结构。
继承:在继承时,class
的继承默认是 private
,而 struct
的继承默认是 public
。
struct Base1 {};
class Base2 {};
// 继承默认权限不同
struct Derived1 : Base1 {}; // public 继承
class Derived2 : Base2 {}; // private 继承
C++ 的 struct
可以定义成员函数。实际上,struct
和 class
除了默认访问权限不同外,语法上几乎是一样的。
struct MyStruct {
int x;
void setX(int val) {
x = val;
}
int getX() {
return x;
}
};
多态指的是同一个接口可以有不同的实现方式。多态通过允许不同类型的对象以相同的方式进行处理,极大地提高了代码的灵活性和可扩展性。多态主要通过继承和接口实现,并且可以分为编译时多态和运行时多态。
提高代码重用性和可维护性: 多态允许你编写更加通用的代码。例如,可以编写一个函数来处理不同类型的对象,而不需要了解这些对象的具体类型。这样,当需要增加新的类型时,只需要新增类的实现,而不需要修改已经存在的代码。
// 基类
class Animal {
public:
virtual void makeSound() = 0; // 纯虚函数
};
// 派生类
class Dog : public Animal {
public:
void makeSound() override {
cout << "Woof" << endl;
}
};
class Cat : public Animal {
public:
void makeSound() override {
cout << "Meow" << endl;
}
};
void makeAnimalSound(Animal* animal) {
animal->makeSound();
}
int main() {
Dog dog;
Cat cat;
makeAnimalSound(&dog); // 输出 Woof
makeAnimalSound(&cat); // 输出 Meow
}
简化代码: 多态可以用统一的接口来操作不同类型的对象,简化了代码的复杂度和可读性。例如,在上面的代码中,无论是 Dog
还是 Cat
,你都可以通过调用 makeSound
来发出声音,而不需要分别为每种类型写不同的处理逻辑。
增强系统的扩展性: 多态使得系统更容易扩展。当需要添加新的功能时,只需要新增实现类,而不需要修改现有的代码。例如,如果需要新增一个 Bird
类,只需要继承 Animal
并实现 makeSound
方法。
实现动态绑定: 通过多态,程序在运行时可以根据对象的实际类型进行方法调用,而不是在编译时确定调用哪个方法。这种动态绑定使得程序更加灵活和动态。
重载是指在同一个类中定义多个同名方法,但这些方法具有不同的参数列表(参数类型或参数个数)。编译器根据方法的参数列表来区分这些方法。在C++中,构造函数也可以被重载。
class Print {
public:
void display(int i) {
cout << "整数: " << i << endl;
}
void display(double f) {
cout << "浮点数: " << f << endl;
}
void display(string s) {
cout << "字符串: " << s << endl;
}
};
int main() {
Print obj;
obj.display(5); // 调用 display(int)
obj.display(3.14); // 调用 display(double)
obj.display("Hello");// 调用 display(string)
return 0;
}
重写是指子类重新定义从基类继承的方法,目的是提供子类自己的实现版本。重写的方法必须具有相同的名称、参数列表和返回类型。重写通常与多态(Polymorphism)结合使用。
virtual
)以允许子类重写。class Animal {
public:
virtual void makeSound() {
cout << "Animal sound" << endl;
}
};
class Dog : public Animal {
public:
void makeSound() override { // 重写基类的 makeSound 方法
cout << "Woof" << endl;
}
};
int main() {
Animal* animal = new Dog();
animal->makeSound(); // 调用的是 Dog 类的 makeSound 方法
delete animal;
return 0;
}
在建立连接之前,Client处于CLOSED状态,而Server处于LISTEN的状态。
进程是程序在计算机中的一次执行过程,它是CPU分配资源和执行指令的基本单位。CPU通过不断地切换执行不同的进程,实现了多任务同时执行的效果。当一个进程被CPU执行时,它会占用CPU的运行时间,执行其中的指令。当CPU需要执行其他任务时,会将当前进程的状态保存起来,并切换到其他进程执行,这样不断地在不同进程之间切换,就实现了多任务的效果。
#include <iostream>
struct ListNode {
int val;
ListNode* next;
ListNode(int x) : val(x), next(nullptr) {}
};
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (head == nullptr || head->next == nullptr) {
return head; // 如果链表为空或者只有一个节点,直接返回头结点
}
ListNode* pre = nullptr; // 初始化 pre 为 nullptr
ListNode* cur = head; // 初始化 cur 为头结点
ListNode* node = nullptr; // 初始化 node 为 nullptr
while (cur != nullptr) {
node = cur->next; // 保存当前节点的下一个节点
cur->next = pre; // 当前节点的 next 指向 pre,完成反转
pre = cur; // 更新 pre
cur = node; // 更新 cur
}
return pre; // pre 就是反转后的新头结点
}
};
int main() {
ListNode* head = new ListNode(1);
head->next = new ListNode(2);
head->next->next = new ListNode(3);
Solution solution;
ListNode* newHead = solution.reverseList(head);
while (newHead != nullptr) {
std::cout << newHead->val << " ";
newHead = newHead->next;
}
return 0;
}
详见一面
定义:
引用: 引用是一个别名,它为一个已存在的变量提供了另一个名字。引用在创建时必须初始化,并且一旦初始化后,它将一直引用相同的对象。
语法:
*
符号来声明指针,以及通过 *
来访问指针指向的值。int x = 10;
int *ptr = &x; // 指针的声明和初始化int value = *ptr; // 使用指针访问值
&
符号来声明引用,没有类似 *
的解引用符号。int x = 10;
int &ref = x; // 引用的声明和初始化int value = ref; // 直接使用引用访问值
空值(NULL 或 nullptr):
多级间接引用:
数组:
传递给函数:
使用场景:
多态指的是通过子类对象或子类类型的对象来调用父类中定义的方法,实现不同子类对象对同一消息的不同响应。
在C++中,可以通过继承和虚函数实现多态性。当父类的函数被声明为虚函数时,子类可以重写(覆盖)该函数,并且在运行时,会根据实际对象的类型来调用对应的函数。这就是多态的体现。
可以将一个父类的指针或引用指向一个子类的对象,这样就可以通过父类的指针或引用来访问子类的成员变量和方法。这种行为是安全的,因为子类对象也是一个父类对象,它继承了父类的属性和方法,所以可以将子类对象看作是父类对象的一种特殊形式。
确定数的位数和符号位:假设我们要计算一个8位二进制数的补码,其中最高位为符号位(0表示正数,1表示负数)。
如果是正数:正数的补码就是其原码。例如,+3的8位原码是00000011,那么它的补码也是00000011。
如果是负数:
取反:先将负数的绝对值转换为二进制形式,然后对每一位取反(0变为1,1变为0)。例如,-3的绝对值是3,其二进制形式是00000011,取反后为11111100。
加1:将取反后的结果加1。例如,11111100加1得到11111101。
得到补码:将上述步骤得到的结果作为负数的补码。例如,-3的补码为11111101。
二进制补码是用来表示有符号整数的一种方式,常用于计算机中。它解决了原码和反码表示法中0的符号位不统一的问题。在二进制补码表示法中,正数的补码与其二进制原码相同,而负数的补码是其二进制原码取反后加1。
下面是一些常见的二进制补码示例:
二进制补码的一个重要性质是,对于任意一个整数,其补码加上其相反数的补码等于全1的二进制数。例如,5的补码是00000101,-5的补码是11111011,它们相加得到11111111,即全1的二进制数。
以字符串"babad"为例。
dp
,其大小为 n x n
(n
为字符串长度),并初始化所有元素为 false
。dp[i][i]
,将对应位置的元素设为 true
,因为单个字符肯定是回文串。n
的子串,计算 dp[i][j]
的值。len
的子串,枚举起始位置 i
,计算结束位置 j = i + len - 1
。s[i] == s[j]
且 dp[i+1][j-1]
为 true
,则说明去掉头尾两个字符后的子串是回文串,即 dp[i][j] = true
。dp[i][j]
为 true
时,更新起始位置 start = i
和最大长度 maxLen = len
。start
和最大长度 maxLen
,使用 substr
方法从原始字符串中取出最长回文子串并返回。#include <iostream>
#include <vector>
#include <string>
using namespace std;
string longestPalindrome(string s) {
if (s.empty()) return "";
int n = s.length();
vector<vector<bool>> dp(n, vector<bool>(n, false)); // 定义二维动态规划数组
int start = 0, maxLen = 1; // 记录最长回文子串的起始位置和长度
for (int i = 0; i < n; ++i) {
dp[i][i] = true; // 单个字符肯定是回文串
if (i < n - 1 && s[i] == s[i + 1]) {
dp[i][i + 1] = true; // 相邻字符相同则是回文串
start = i;
maxLen = 2;
}
}
for (int len = 3; len <= n; ++len) { // 枚举子串长度
for (int i = 0; i + len - 1 < n; ++i) { // 枚举子串起始位置
int j = i + len - 1; // 子串结束位置
if (s[i] == s[j] && dp[i + 1][j - 1]) {
dp[i][j] = true; // 根据状态转移方程计算 dp[i][j]
start = i;
maxLen = len;
}
}
}
return s.substr(start, maxLen); // 返回最长回文子串
}
int main() {
string s = " ";
std::cout << "输入字符串:";
std::cin >> s;
std::cout << longestPalindrome(s) << std::endl; // 输出最长回文子串
return 0;
}
这里直接给大家看一个维基百科给的解释:
动态规划常用的算法包括:
动态规划和贪心算法的区别: