一、c++的引用 引用和指针的的区别?
引用是一种更安全的指针:
1. 引用必须初始化,指针可以不用初始化
int a =10;
int *p; // 指针可能是野指针
int &b =a;
//引用赋值",通常指的是直接修改引用所引用的对象的值,而不需要显式解引用。如果你在说 "指针赋值",通常需要通过解引用操作来修改指针所指向的对象的值。
2. 引用只有一级引用,没有多级引用, 指针有一级,也有多级
int a =10;
int * p = &a; // *p=30 需要手动解引用
int &b = a; // 在底层 b =20; 的时候,会自动解引用
3. 定义一个引用变量和 定义一个指针变量,其汇编指令是一样的; 通过引用变量修改所引用内存的值,和通过指针解引用修改指针指向内存的值,其底层指令也是一样的。
// 引用一个数组
int array[5] ={};
int (&q)[5] = array;
二、左值引用和右值引用
右值引用主要用于绑定到右值(右值是指表达式结束后不再存在的临时对象,比如字面量、临时表达式的结果等)。
左值引用和右值引用的引入是为了支持移动语义和避免不必要的拷贝操作,特别是在处理临时对象和资源管理时。右值引用可以绑定到临时对象,并通过移动语义实现高效的资源管理。左值引用则绑定到持久存在的对象,用于常规的引用传递和修改对象的操作。 C++11 引入了右值引用,C++14 进一步完善了右值引用的特性。
右值引用的本质主要体现在以下几个方面:
-
允许绑定到临时对象: 右值引用允许绑定到即将被销毁的临时对象,从而允许我们获取和操作这些临时对象的内容。
-
支持移动语义: 通过右值引用,可以实现移动语义。移动语义是一种在对象之间转移资源所有权的机制,可以避免不必要的拷贝操作,提高性能。
-
在重载函数中的应用: 右值引用广泛用于重载移动构造函数和移动赋值运算符,以实现高效的对象移动
-
class MyString { public: // 移动构造函数 MyString(MyString&& other) noexcept : data(other.data), size(other.size) { other.data = nullptr; other.size = 0; } // 移动赋值运算符 MyString& operator=(MyString&& other) noexcept { if (this != &other) { delete[] data; data = other.data; size = other.size; other.data = nullptr; other.size = 0; } return *this; } private: char* data; std::size_t size; };
上述代码中,
MyString
类实现了移动构造函数和移动赋值运算符,通过右值引用,可以高效地进行对象的资源移动。总体而言,右值引用的本质是一种引用类型,其设计目的是为了支持移动语义和更高效的资源管理。通过右值引用,程序员可以更精准地控制对象的生命周期和资源的传递,以提高代码的性能和效率。
三、 delete 和free区别是是吗? new和malloc区别是什么?
new
和 malloc
都是在 C++ 中用于动态分配内存的操作符,但它们有一些重要的区别:
-
语法:
new
:new
是 C++ 中的关键字,使用时直接调用,并返回指定类型的指针。int* p = new int;
malloc
:malloc
是 C 语言中的库函数,返回void*
类型的指针。在 C++ 中可以使用,但通常搭配reinterpret_cast
来进行类型转换。int* p = reinterpret_cast<int*>(malloc(sizeof(int)));
-
类型安全:
new
:new
是类型安全的,它会自动计算所需的内存大小,并返回指定类型的指针。它还调用了对象的构造函数,对于自定义类型来说更加方便。malloc
:malloc
返回的是void*
,需要手动计算所需的内存大小,并且不会调用对象的构造函数。在 C++ 中,使用malloc
分配内存后,需要使用placement new
构造对象。
-
构造与析构:
new
:new
会调用对象的构造函数,创建一个完全初始化的对象。当使用delete
释放内存时,会调用对象的析构函数。malloc
:malloc
不会调用对象的构造函数和析构函数,需要手动管理对象的生命周期。
-
操作符重载:
new
: 可以通过重载new
和delete
操作符来自定义内存分配和释放行为。malloc
:malloc
和free
是标准库函数,不能被重载。
-
异常处理:
new
: 如果分配失败,会抛出std::bad_alloc
异常,可以通过nothrow
参数禁止抛出异常。malloc
: 如果分配失败,返回nullptr
,不抛出异常。需要手动检查返回值。
在 C++ 中,推荐使用 new
和 delete
,因为它们更加类型安全,对对象的构造和析构进行了正确处理。使用 malloc
和 free
主要是在一些特殊情况下,或者与 C 语言代码交互的场景中。在现代 C++ 中,智能指针和容器类的使用也减少了手动内存管理的需求
三、 面向对象
面向对象思想(详解)-CSDN博客
面向对象是一种编程的思想,对象在栈上
类内实现的方法会自动展开为内联函数
类外实现的方法
对象的大小通过成员变量计算
#include <iostream>
class MyClass {
public:
int x; // 4 bytes
char c; // 1 byte
double d; // 8 bytes
// Constructor
MyClass(int a, char b, double e) : x(a), c(b), d(e) {}
};
int main() {
std::cout << "Size of MyClass: " << sizeof(MyClass) << " bytes" << std::endl;
return 0;
}
this
是一个指向当前对象的指针,它是类成员函数中的一个隐含参数。this
指针提供了对调用对象的访问,使得在类的成员函数中可以访问到当前对象的成员变量和其他成员函数。
每个对象都有自己的成员变量,但是他们共享一套成员方法。(那怎么区分哪个对象调用成员变量)
类的成员方法一经编译,所有的方法参数,都会加一个this指针,接受调用该方法的对象地址。this会告诉编译器处理哪个对象的方法。
以下是关于 this
指针的一些重要事项:
-
隐含参数: 在类的成员函数中,编译器会隐含地将当前对象的地址作为第一个参数传递给成员函数,而这个参数就是
this
指针。 -
使用
this
指针: 可以使用this
指针访问对象的成员。例如,在成员函数中访问成员变量时,可以省略this->
,因为编译器会自动识别并添加。class MyClass { public: int x; void setX(int value) { this->x = value; // 可以省略this->,直接写成x = value } };
-
返回
this
指针: 在成员函数中,可以返回this
指针,以支持链式调用(chaining)。使用链式调用可以写成:obj.setX(42).doSomething();
-
class MyClass { public: int x; MyClass& setX(int value) { this->x = value; return *this; } };
-
在静态成员函数中不存在: 静态成员函数是与类关联而不是与对象关联的,因此在静态成员函数中不存在
this
指针。class MyClass { public: static void staticFunction() { // 在静态成员函数中无法使用this指针 } };
this
指针在编写成员函数时非常有用,它使得成员函数能够访问对象的成员变量和其他成员函数。通过 this
指针,可以在类的成员函数中明确指出当前对象,从而正确地访问成员。
四、构造函数和析构函数
构造函数和析构函数的函数名字和类名一样,切没有返回值。
1 . 开辟内存(在栈上为对象开辟内存)
2. 调用构造函数(初始化,可以有参数,可以提供多个构造函数,叫做构造函数的重载)
...
n. 出作用域的时候,在return处对象依次会析构(先构造的晚析构,后构造的先析构, 类只能有一个析构函数,析构可以自己调用s1.~seq();,释放对象成员变量占用的外部堆内存, 析构函数调用以后,我们就说对象没有了,但是对象在栈上,只要函数存在对象就在,但是别去调用对象的方法,有可能造成堆的内存非法访问)
可以在三个地方创建对象:
栈(Stack): 进入函数时构造
深拷贝:
在赋值时,把s2的new的内存直接丢了,而指向s1的。这时候需要做=运算符重载 s2.operator=(s1);
深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象,是“值”而不是“引用”(不是分支)
拷贝第一层级的对象属性或数组元素
递归拷贝所有层级的对象属性和数组元素
深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
- 栈是一种后进先出(LIFO)的数据结构,用于存储函数调用和局部变量。
- 对象在栈上分配的时候,它的生命周期与所在函数的生命周期相同,当函数执行完成时,对象自动被销毁。
- 对象的大小通常较小,因为栈的大小是有限制的,过大的对象可能会导致栈溢出。
- 通过在栈上分配对象,可以实现高效的内存管理,但是对象的生命周期受限于函数的执行。
#include <iostream> class MyClass { public: MyClass(int val) : value(val) { std::cout << "Constructor called. Value: " << value << std::endl; } ~MyClass() { std::cout << "Destructor called. Value: " << value << std::endl; } void display() { std::cout << "Value: " << value << std::endl; } private: int value; }; int main() { // 在栈上构建对象 MyClass stackObject(10); stackObject.display(); // 对象生命周期结束时,析构函数会被自动调用 return 0; }
堆(Heap): new的时候构造
- 堆是一块用于动态分配内存的区域,对象在堆上分配时,它的生命周期由程序员负责管理。
- 对象在堆上分配通常使用
new
(C++)或malloc
(C)等操作符。 - 使用堆分配内存可以实现灵活的对象生命周期,但需要手动释放分配的内存,以防止内存泄漏。
- 堆上的对象可以在多个函数之间传递,因为它的生命周期不受限于单个函数
-
#include <iostream> class MyClass { public: MyClass(int val) : value(val) { std::cout << "Constructor called. Value: " << value << std::endl; } ~MyClass() { std::cout << "Destructor called. Value: " << value << std::endl; } void display() { std::cout << "Value: " << value << std::endl; } private: int value; }; int main() { // 在堆上构建对象 MyClass* heapObject = new MyClass(20); heapObject->display(); // 注意:需要手动释放堆上分配的对象 delete heapObject; return 0; }
对于new的对象,是在堆上建的,首先delete会调用对象的析构函数对象在堆上的成员变量进行释放,然后再释放对象内存
-
数据段/静态存储区: 程序启动时构造函数
- 数据段用于存储全局变量和静态变量,这些变量在程序运行的整个生命周期内都存在。
- 对象在数据段上分配时,它的生命周期与程序的生命周期相同。
- 数据段适合存储一些在程序运行期间始终存在的全局数据。
#include <iostream> class MyClass { public: MyClass(int val) : value(val) { std::cout << "Constructor called. Value: " << value << std::endl; } ~MyClass() { std::cout << "Destructor called. Value: " << value << std::endl; } void display() { std::cout << "Value: " << value << std::endl; } private: int value; }; // 在数据段上定义全局对象 MyClass globalObject(30); int main() { // 全局对象在整个程序生命周期内存在 globalObject.display(); return 0; }
-
五、对象的深拷贝和浅拷贝
-
浅拷贝
-
浅拷贝只复制指向某个对象的指针,
SeqStack s; SeqStack s1(10); SeqStack s2 =s1; // 默认拷贝构造函数 相当于 SeqStack s3(s1); -》浅拷贝(内存拷贝) s2 =s1 ; 默认赋值函数 -》 浅拷贝(内存拷贝) 点击并拖拽以移动 memcpy和recalloc都是浅拷贝
而不复制对象本身,新旧对象还是共享同一块内存(分支)
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。
如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。
深拷贝:
在赋值时,把s2的new的内存直接丢了,而指向s1的。这时候需要做=运算符重载 s2.operator=(s1);
深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象,是“值”而不是“引用”(不是分支)
拷贝第一层级的对象属性或数组元素
递归拷贝所有层级的对象属性和数组元素
深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。