一、new和malloc ⭐
- new是C++的关键字,用于动态分配内存并创建对象。它可以根据类型自动计算所需内存空间,并调用对象的构造函数进行初始化。在使用new分配内存后,需要使用delete来释放这些内存空间,以防止内存泄漏。
- malloc是C语言的库函数,用于动态分配一块指定大小的内存块,并返回其地址。需要注意的是,使用malloc分配内存后,需要使用free来释放这些内存空间,以防止内存泄漏。
C++ #include <iostream> #include <cstdlib>
int main() { // 使用new进行动态内存分配和释放 int* newPtr = new int(10); std::cout << "Value allocated with new: " << *newPtr << std::endl; delete newPtr;
// 使用malloc进行内存分配和释放 int* mallocPtr = (int*)malloc(sizeof(int)); if (mallocPtr != nullptr) { *mallocPtr = 20; std::cout << "Value allocated with malloc: " << *mallocPtr << std::endl; free(mallocPtr); }
return 0; } |
二、class和struct的区别 ⭐
C++ struct StructExample { int publicMember; // 默认public,可以直接访问 };
class ClassExample { int privateMember; // 默认private,不能直接访问
public: // 公共接口,允许外部访问privateMember int getPrivateMember() const { return privateMember; }
void setPrivateMember(int value) { privateMember = value; } };
int main() { StructExample se; se.publicMember = 10; // 直接访问
ClassExample ce; ce.setPrivateMember(20); // 通过公共成员函数设置私有成员 int value = ce.getPrivateMember(); // 通过公共成员函数获取私有成员 } |
三、char和int之间的转换
C++ char c = 'A'; int i = c; // 将字符'A'的ASCII码值赋给i
int i = 65; char c = static_cast<char>(i); // 将整数65转换为对应的字符'A' |
需要注意的是,对于转换为char的int值,如果超出了char类型的范围(-128至127),将会发生溢出,只保留最低位字节的值。 |
四、什么是野指针和悬挂指针 ⭐
- 野指针(Dangling Pointer):指的是没有初始化过的指针,它指向的地址是未知的、不确定的、随机的。
产生野指针的原因主要是指针未初始化,防止的措施就是指针初始化(包括及时初始化或置空)。
C++ int main() { int* ptr; // 未初始化的指针,成为野指针
// 使用野指针会导致未定义的行为 *ptr = 5; // 解引用野指针,可能导致程序崩溃
return 0; } |
- 悬挂指针(Dangling Reference):指针最初指向的内存已经被释放了的一种指针。指针指向的内存已释放,但指针的值没有被清零,对悬空指针操作的结果不可预知。
C++ int* createInt() { int value = 5; int* ptr = &value; return ptr; // 返回指向局部变量的指针 }
int main() { int* danglingPtr = createInt(); // 指向已释放的内存
// 对悬挂指针操作的结果不可预知 int value = *danglingPtr; // 解引用悬挂指针,可能导致未定义的行为
return 0; } |
局部变量 value 会在 createInt 函数执行完毕后,离开其作用域时被销毁。此时,value 占用的内存将不再被 value 所拥有,但是通过 ptr 返回的指针依然指向这块内存。由于这块内存已经被释放,再通过 ptr 访问它将会导致未定义行为(Undefined Behavior),可能引起程序崩溃或其他不可预知的错误。 |
重要的是要理解,局部变量 value 的生命周期仅限于 createInt 函数的作用域内。一旦函数返回,value 的生命周期结束,它所占用的内存可以被系统回收或重新分配给其他变量。 |
在C++中,正确处理这类情况的一种方法是使用动态内存分配,例如使用 new 关键字分配内存,并在适当的时候使用 delete 来释放内存:
C++ int* createInt() { int* ptr = new int(5); // 使用new分配内存,并初始化为5 return ptr; // 返回指向这块内存的指针 }
// 使用完毕后,需要使用delete释放内存 int* ptr = createInt(); // ... 使用ptr ... delete ptr; // 释放ptr指向的内存 ptr = nullptr; // 将ptr设置为nullptr,避免悬挂指针问题 |
五、NULL和nullptr区别⭐
- 在C++11之前,NULL 是一个宏,通常被定义为 0 或 (void*)0。在C++11及以后,NULL 被定义为 nullptr。
- nullptr 是C++11引入的一个更现代、更安全的方式来表示空指针。尽管 NULL 在旧代码中仍然广泛使用,但在新的C++代码中,推荐使用 nullptr。
六、指针常量和常量指针有何区别⭐
- 指针常量:指针本身的值不可改变(即不能让它指向另一个地址),但可以改变它所指向的数据(除非数据本身是常量)。(指针地址不可变,值可变。注:指针常量在定义时要赋初值。)
C++ int a = 0,b = 0; int* const p = &a; *p = 1; //正确,可以修改值 *p = &b; //错误,不可改变地址 |
- 常量指针:指针可以改变指向的地址,但它所指向的数据是不可修改的(即数据是常量)。
(指针地址可以变,值不能变)
C++ int a = 0,b = 0; const int *p = &a; *p = 1; //错误,不可修改常量值 *p = &b; //正确,可以指向另一个常量 |
区分记忆:比较const和*谁离ptr更近,如果const更近则表示指针本身是常量,是指针常量;如果*离ptr更近则表示指向的值value不能更改,是常量指针;否则是指向常量的指针常量(const int * const p = &a) |
七、物理内存和虚拟内存的区别⭐
- 本质:物理内存是实际存在的硬件资源,而虚拟内存是操作系统提供的一种抽象和扩展。
- 大小:物理内存的大小受限于硬件,虚拟内存的大小受限于物理内存和磁盘空间。
- 访问速度:物理内存的访问速度通常远快于虚拟内存,因为硬盘的读写速度远低于RAM。
- 目的:物理内存用于存储当前活跃的程序和数据,虚拟内存用于扩展可用的内存空间,允许更多的程序并发运行或处理更大的数据集。
- 管理:物理内存由硬件直接管理,而虚拟内存由操作系统通过复杂的内存管理算法进行管理。
虚拟内存的使用可以提高系统的多任务处理能力,但过度依赖虚拟内存可能导致性能下降,因为频繁的页面交换(也称为“页面抖动”或“抖动”)会增加对硬盘的访问,从而降低程序的响应速度。 |
八、重载、重写和隐藏的区别⭐
1、重载(Overloading)
- 重载是在同一个作用域内定义多个相同名称但参数列表不同的函数或方法。
C++ #include <iostream>
void printNumber(int num) { std::cout << "Integer number: " << num << std::endl; }
void printNumber(double num) { std::cout << "Floating-point number: " << num << std::endl; }
int main() { printNumber(10); printNumber(3.14); return 0; } |
2、重写(override)
- 重写是指子类重新定义从父类继承的虚函数,使其具有不同的实现。
- 重写的函数签名(函数名、参数列表和返回类型)必须与被重写函数相同。
C++ #include <iostream>
class Base { public: virtual void sayHello() { std::cout << "Hello from Base class!" << std::endl; } };
class Derived : public Base { public: void sayHello() override { // 使用 override 关键字表明重写了父类的函数 std::cout << "Hello from Derived class!" << std::endl; } };
int main() { Base* basePtr = new Derived(); basePtr->sayHello(); // Output: "Hello from Derived class!" delete basePtr; return 0; } |
3、隐藏(Hiding)
- 隐藏发生在派生类中,当派生类中的一个成员(可以是函数或变量)与基类中的成员名称相同,但又不是重写(即函数签名不匹配)时,就会发生隐藏。
- 隐藏的成员可以通过基类类型的引用或指针来访问,或者在派生类中使用 using 声明来解决名称冲突。
C++ #include <iostream>
class Base { public: void sayHello() { std::cout << "Hello from Base class!" << std::endl; } };
class Derived : public Base { public: void sayHello() { std::cout << "Hello from Derived class!" << std::endl; } };
int main() { Base baseObj; Derived derivedObj; baseObj.sayHello(); // Output: "Hello from Base class!" derivedObj.sayHello(); // Output: "Hello from Derived class!" Base* basePtr = new Derived(); basePtr->sayHello(); // Output: "Hello from Base class!" delete basePtr; return 0; } |
4、总结
重载和重写是多态性的重要体现,而隐藏更多是名称冲突的一种情况。正确理解这些概念有助于更好地设计和实现面向对象的程序。 |