跳至主要內容

C++知识汇总(二)

CodeShouhu大约 37 分钟使用指南Markdown

c++知识汇总(二)

1,在c++中静态函数能被定义为虚函数吗?常函数对应的情况呢?

静态函数不能定义为虚函数。

首先,我们来理解一下静态函数和虚函数。静态函数在程序运行期间只有一个实例,它不属于任何类对象,而是属于类本身。静态函数可以通过类名和作用域解析运算符(::)来调用,也可以通过对象来调用。

虚函数则是一种实现动态多态性的手段。虚函数是在基类中使用关键字 virtual 声明的,可以在派生类中被重写。这样当通过基类指针或引用调用该函数时,就会执行派生类中的版本。虚函数依靠vptr和vtable来处理。vptr是一个指针,在类的构造函数中创建生成,并且只能用this指针来访 问它,因为它是类的一个成员,并且vptr指向保存虚函数地址的vtable.对于静态成员函数,它没有this指 针,所以无法访问vptr。

静态函数是属于类本身的,而不是属于类的对象。而虚函数是依赖于对象的,也就是说,需要通过对象来调用虚函数。因此,静态函数不能声明为虚函数。

静态与非静态成员函数之间有一个主要的区别,那就是静态成员函数没有this指针。 这就是为何static函数不能为virtual,虚函数的调用关系:this -> vptr -> vtable ->virtual function。

至于常函数,它是指函数体内不能修改任何非静态成员变量的函数。常函数可以被声明为虚函数,但是这种情况下,派生类中重写的常函数也必须声明为常函数。这是因为常函数的特性需要在整个继承体系中保持一致。

#include <iostream>  
  
class Base {  
public:  
    virtual void normalFunc() {  
        std::cout << "Base::normalFunc()" << std::endl;  
    }  
  
    // 静态函数不能定义为虚函数  
    static void staticFunc() {  
        std::cout << "Base::staticFunc()" << std::endl;  
    }  
  
    // 常函数可以定义为虚函数  
    virtual void constFunc() const {  
        std::cout << "Base::constFunc()" << std::endl;  
    }  
};  
  
class Derived : public Base {  
public:  
    void normalFunc() override {  
        std::cout << "Derived::normalFunc()" << std::endl;  
    }  
  
    // 静态函数可以重写,但不是虚函数  
    static void staticFunc() {  
        std::cout << "Derived::staticFunc()" << std::endl;  
    }  
  
    void constFunc() const override {  
        std::cout << "Derived::constFunc()" << std::endl;  
    }  
};  
  
int main() {  
    Base base;  
    Derived derived;  
    Base* ptr = &derived;  
  
    base.normalFunc();      // 输出:Base::normalFunc()  
    derived.normalFunc();   // 输出:Derived::normalFunc()  
    ptr->normalFunc();      // 输出:Derived::normalFunc()(动态绑定)  
  
    base.staticFunc();      // 输出:Base::staticFunc()(静态函数不是虚函数)  
    derived.staticFunc();   // 输出:Derived::staticFunc()(静态函数可以重写,但不是虚函数)  
  
    base.constFunc();       // 输出:Base::constFunc()  
    derived.constFunc();    // 输出:Derived::constFunc()  
    ptr->constFunc();       // 输出:Derived::constFunc()(常量虚函数的动态绑定)  
  
    return 0;  
}
上述代码中,normalFunc 是一个普通的虚函数,可以在派生类中被重写。staticFunc 是一个静态函数,不能在派生类中被重写为虚函数。constFunc 是一个常量的虚函数,也可以在派生类中被重写,并且可以保证函数不会修改对象的状态。

速记

静态函数不能定义为虚函数。虚函数依靠vptr和vtable来处理。vptr是一个指针,在类的构造函数中创建生成,并且只能用this指针来访 问它,因为它是类的一个成员,并且vptr指向保存虚函数地址的vtable.对于静态成员函数,它没有this指 针,所以无法访问vptr。 函数,它是指函数体内不能修改任何非静态成员变量的函数。常函数可以被声明为虚函数

2,详细解释数组和指针的不同之处

在C++中,数组和指针虽然在某些情况下可以互换使用,但它们之间存在一些重要的区别。以下是数组和指针之间的主要区别:

  1. 存储:数组是一种复合数据类型,它在内存中占据一连续的存储空间,数组是用于储存多个相同类型数据的集合。 数组名是首元素的地址。而指针只是一个变量,保存的是另一个变量的地址。
  2. 可变性:数组的大小在创建时就确定了,不能改变。而指针可以随时改变,指向不同的内存地址。
  3. 生命周期:当定义一个数组时,其生命周期取决于它是如何定义的。例如,局部数组在函数返回时就会被销毁,全局数组或者静态数组会在程序结束时销毁。而对于指针,除非显式地删除它指向的内存,否则它指向的内存会一直存在,这可能会导致内存泄露。
  4. 操作:数组支持索引操作(例如,a[i]),且可以使用sizeof操作符获取整个数组的大小(单位是字节)。指针也支持索引操作,但sizeof操作符在指针上只能得到指针本身的大小,而不能得到它所指向的内存区域的大小。
  5. 赋值:同类型指针变量可以相互赋值;数组不行,只能一个一个元素的赋值或拷贝

总的来说,尽管数组和指针在很多情况下可以互换使用,但它们在存储、可变性、生命周期和操作上存在明显的区别。理解这些区别对于有效地使用C++非常重要。

#include<iostream>  
using namespace std;  
  
int main() {  
    int arr[5] = {10, 20, 30, 40, 50}; // 定义一个数组  
    int *ptr = arr; // 定义一个指针,指向数组的首元素  
  
    cout << "数组arr的地址是:" << &arr << endl; // 输出数组的地址  
    cout << "指针ptr的地址是:" << &ptr << endl; // 输出指针的地址  
  
    cout << "数组arr的第一个元素是:" << arr[0] << endl; // 输出数组的第一个元素  
    cout << "指针ptr指向的元素是:" << *ptr << endl; // 输出指针指向的元素  
  
    ptr++; // 指针指向下一个元素  
    cout << "指针ptr现在指向的元素是:" << *ptr << endl; // 输出指针现在指向的元素  
  
    return 0;  
}

速记

数组在内存中占据一连续的存储空间,用于储存多个相同类型数据的集合。 数组名是首元素的地址。而指针只是一个变量,保存的是另一个变量的地址。数组的大小在创建时就确定了。而指针可以随时改变,指向不同的内存地址。数组可以使用sizeof操作符获取整个数组的大小。指针sizeof操作符在指针上只能得到指针本身的大小。同类型指针变量可以相互赋值;数组不行,只能一个一个元素的赋值或拷贝

3,详细说明什么是函数指针,举例说明有什么使用场景

函数指针是指向函数入口地址的指针。在C++中,函数名实际上是指向函数第一条指令的常量指针。在程序编译后,编译器会为每个函数分配一个首地址,也就是该函数第一条指令的地址。函数指针的定义方式如下:

返回类型 (*指针变量名)(参数类型)

例如,定义一个指向返回int类型,接受两个int类型参数的函数的函数指针:

int (*func_ptr)(int, int);

函数指针的常见使用场景是将函数作为参数传递给另一个函数,实现回调函数的功能。例如,在排序算法中,可以将比较函数作为参数传递给排序函数,以便在不同的情况下使用不同的比较方式。

需要注意的是,函数指针的定义和使用相对复杂,容易出错。在实际编程中,应谨慎使用函数指针,并确保正确理解和处理函数指针的类型和作用。

#include<iostream>  
using namespace std;  
  
// 定义一个函数  
void printHello() {  
    cout << "Hello, world!" << endl;  
}  
  
int main() {  
    // 定义一个函数指针,指向上述函数  
    void (*func_ptr)() = &printHello;  
  
    // 通过函数指针调用函数  
    (*func_ptr)();  
  
    // 更简洁的调用方式  
    func_ptr();  
  
    return 0;  
}
输出:
Hello, world!  
Hello, world!

在这个例子中,func_ptr 是一个函数指针,它指向了 printHello 函数。通过这个函数指针,我们可以调用 printHello 函数。注意,当我们写 func_ptr() 时,实际上是通过函数指针调用函数。这种写法是 C++ 的语法糖,等价于 (*func_ptr)()

速记

函数指针是指向函数入口地址的指针,将函数作为参数传递给另一个函数,实现回调函数的功能。

4,详细说明静态变量什么时候初始化,举例需要怎么做?

(1)静态局部变量:静态局部变量在函数第一次被调用时初始化,且只初始化一次。如果函数在程序运行期间没有被调用,则该静态局部变量不会被初始化。

#include <iostream>  
  
void func() {  
    static int count = 0;  
    count++;  
    std::cout << "Count: " << count << std::endl;  
}  
  
int main() {  
    func(); // 输出:Count: 1  
    func(); // 输出:Count: 2  
    func(); // 输出:Count: 3  
    return 0;  
}

(2)静态全局变量和静态类成员变量:静态全局变量和静态类成员变量在main函数之前初始化,且只初始化一次。这些变量在程序加载时就被分配了内存空间,并在main函数执行之前完成初始化。

#include <iostream>  
  
static int globalCount = 0;  
  
int main() {  
    std::cout << "Global Count: " << globalCount << std::endl; // 输出:Global Count: 0  
    globalCount++;  
    std::cout << "Global Count: " << globalCount << std::endl; // 输出:Global Count: 1  
    return 0;  
}

(3)作用域:C++里作用域可分为6种:全局,局部,类,语句,命名空间和文件作用域。

静态全局变量 :全局作用域+文件作用域,所以无法在其他文件中使用。

静态局部变量 :局部作用域,只被初始化一次,直到程序结束。

类静态成员变量:类作用域。

(4)所在空间:都在静态存储区。因为静态变量都在静态存储区,所以下次调用函数的时候还是能取到原来的值。

(5)生命周期:静态全局变量、静态局部变量都在静态存储区,直到程序结束才会回收内存。类静态成员变量在静态存储区,当超出类作用域时回收内存。

速记

静态局部变量:静态局部变量在函数第一次被调用时初始化,且只初始化一次。静态全局变量和静态类成员变量:静态全局变量和静态类成员变量在main函数之前初始化,且只初始化一次。静态全局变量 :全局作用域+文件作用域。静态局部变量 :局部作用域。类静态成员变量:类作用域。都在静态存储区。因为静态变量都在静态存储区。静态全局变量、静态局部变量都在静态存储区,直到程序结束才会回收内存。类静态成员变量在静态存储区,当超出类作用域时回收内存。

5,可以使用nullptr调用类里面的成员函数可以吗?为什么?

能。

原因:因为在编译时对象就绑定了函数地址,和指针空不空没关系。

//给出实例
class animal{
public:
    void sleep(){ cout << "animal sleep" << endl; }
    void breathe(){ cout << "animal breathe haha" << endl; }
};
class fish :public animal{
public:
    void breathe(){ cout << "fish bubble" << endl; }
};
int main(){
    animal *pAn=nullptr;
    pAn->breathe();   // 输出:animal breathe haha
    fish *pFish = nullptr;
    pFish->breathe(); // 输出:fish bubble
    return 0;
}  

原因:因为在编译时对象就绑定了函数地址,和指针空不空没关系。pAn->breathe();编译的时候,函数的地址就和指针pAn绑定了;调用breath(*this), this就等于pAn。由于函数中没有需要解引用this的地方,所以函数运行不会出错,但是若用到this,因为this=nullptr,运行出错。

速记

因为在编译时对象就绑定了函数地址,和指针空不空没关系。

6,C++中什么是野指针,有什么避免方案吗?

在C++中,野指针是指向一个无效内存区域的指针。它通常是由于指针变量没有被正确初始化或者在指针变量生命周期结束后仍然使用而引起的。

野指针的产生主要有以下几种情况:

  1. 指针变量没有被初始化。任何指针变量在使用前都应该被初始化,否则它的值是未定义的,可能指向任何内存地址。
  2. 指针变量指向的内存已经被释放。当使用deletefree等函数释放内存后,指针变量不会自动变为nullptr,如果继续使用该指针变量,则会成为野指针。
  3. 指针变量越界访问。如果指针变量指向了数组以外的内存区域,或者访问了已经被释放的内存区域,就会成为野指针。
#include <iostream>  
  
int main() {  
    int *ptr;  
    int num = 10;  
    ptr = &num;  
    std::cout << "ptr points to: " << *ptr << std::endl;  
  
    num = 20;  
    std::cout << "ptr points to: " << *ptr << std::endl;  
  
    int *ptr2 = new int;  
    *ptr2 = 30;  
    std::cout << "ptr2 points to: " << *ptr2 << std::endl;  
  
    delete ptr2;  
    std::cout << "ptr2 points to: " << *ptr2 << std::endl; // 野指针,已经释放了内存  
  
    return 0;  
}
输出:
ptr points to: 10  
ptr points to: 20  
ptr2 points to: 30  
ptr2 points to: 134514080  // 随机值,因为内存已经被释放

为了避免野指针的产生,可以采取以下几种措施:

  1. 指针变量在使用前必须被初始化,可以将其初始化为nullptr
  2. 在释放内存后将指针变量设置为nullptr,以避免访问已经被释放的内存区域。
  3. 在使用指针变量之前,先检查其是否为nullptr,以避免访问无效内存区域。
  4. 尽量避免使用裸指针,可以使用C++提供的智能指针等RAII机制来管理内存。智能指针会在适当的时机自动释放内存,避免了手动管理内存的麻烦和容易出错的问题。
#include <iostream>  
  
int main() {  
    int *ptr = nullptr;  // 初始化为nullptr  
    int num = 10;  
    ptr = &num;  
    std::cout << "ptr points to: " << *ptr << std::endl;  
  
    num = 20;  
    std::cout << "ptr points to: " << *ptr << std::endl;  
  
    int *ptr2 = new int;  
    *ptr2 = 30;  
    std::cout << "ptr2 points to: " << *ptr2 << std::endl;  
  
    delete ptr2;  
    ptr2 = nullptr;  // 释放内存后将指针置为nullptr  
  
    if (ptr2 != nullptr) {  // 使用前进行非空判断  
        std::cout << "ptr2 points to: " << *ptr2 << std::endl;  
    } else {  
        std::cout << "ptr2 is nullptr" << std::endl;  
    }  
  
    return 0;  
}
输出:
ptr points to: 10  
ptr points to: 20  
ptr2 points to: 30  
ptr2 is nullptr  // 不会输出野指针的值

速记

野指针由于指针变量没有被正确初始化或者在指针变量生命周期结束后仍然使用而引起的。野指针的产生主要有以下几种情况:指针变量没有被初始化。指针变量指向的内存已经被释放。指针变量越界访问。可以采取以下几种措施:指针变量在使用前必须被初始化,在释放内存后将指针变量设置为nullptr,在使用指针变量之前,先检查其是否为nullptr,可以使用C++提供的智能指针等RAII机制来管理内存。

7,说说c++中静态局部变量,静态全局变量,全局变量,局部变量的特点,以及使用场景

在C++中,变量可以根据其生命周期和作用域进行分类。以下是静态局部变量、静态全局变量、全局变量和局部变量的特点和使用场景:

1,静态局部变量(static local variable):静态局部变量是在函数内部定义的,但它的生命周期是整个程序的执行期间,而不是只在函数调用时存在。它在第一次进入函数时被初始化,并在程序执行期间保持不变。静态局部变量对于保持函数的状态或计数等场景非常有用,例如在递归函数中记录递归次数。局部作用域,只被初始化一次,直到程序结束。

#include <iostream>  
  
void staticLocalVariable() {  
    static int count = 0;  
    count++;  
    std::cout << "Static local variable count: " << count << std::endl;  
}  
  
int main() {  
    staticLocalVariable(); // 输出:Static local variable count: 1  
    staticLocalVariable(); // 输出:Static local variable count: 2  
    return 0;  
}

2,静态全局变量(static global variable):静态全局变量是在函数外部定义的,但它的作用域仅限于定义它的文件。它在程序执行期间只初始化一次,而不是在每个文件使用时都初始化。静态全局变量对于限制变量的作用域和可见性非常有用,例如在不同文件中定义同名的静态全局变量。全局作用域+文件作用域,所以无法在其他文件中使用。

// File: main.cpp  
#include <iostream>  
#include "other_file.h"  
  
static int staticGlobalVariable = 10; // 静态全局变量  
  
int main() {  
    std::cout << "Static global variable in main.cpp: " << staticGlobalVariable << std::endl; // 输出:Static global variable in main.cpp: 10  
    otherFunction(); // 输出:Static global variable in other_file.cpp: 20  
    return 0;  
}

3,全局变量(global variable):全局变量是在函数外部定义的,它的作用域是整个程序。它在程序执行期间一直存在,可以被任何函数访问和修改。全局变量对于在多个函数之间共享数据非常有用,例如在多个函数中访问和修改同一个数组。全局作用域,可以通过extern作用于其他非定义的源文件。

4,局部变量(local variable):局部变量是在函数内部定义的,它的生命周期是在函数调用时存在,并在函数返回时被销毁。它只能在其所在函数内部被访问和修改。局部变量对于在函数内部处理临时数据非常有用,例如在函数内部计算一个临时结果。局部作用域,比如函数的参数,函数内的局部变量等等。

局部变量在栈上外,出了作用域就回收内存。其他都在静态存储区。因为静态变量都在静态存储区,所以下次调用函数的时候还是能取到原来的值,直到程序结束才会回收内存。

速记

静态局部变量:局部作用域,只被初始化一次,直到程序结束。静态全局变量:全局作用域+文件作用域,所以无法在其他文件中使用。全局变量:全局变量是在函数外部定义的,它的作用域是整个程序。全局作用域,可以通过extern作用于其他非定义的源文件。局部变量:局部作用域,比如函数的参数,函数内的局部变量等等。局部变量在栈上,出了作用域就回收内存。其他都在静态存储区。因为静态变量都在静态存储区,所以下次调用函数的时候还是能取到原来的值,直到程序结束才会回收内存。

8,详细解释C++中内联函数和宏函数的不同之处

内联函数是一种特殊的函数,它在编译时将被直接嵌入到调用它的代码中,而不是像普通函数一样在运行时通过函数调用的方式进行调用。内联函数的作用是提高程序的执行效率,因为它避免了函数调用的开销,如保存寄存器、设置栈帧等。

内联函数的实现方法是在函数定义前加上inline关键字。例如:

inline int max(int a, int b) {
    return a > b ? a : b;
}

在上面的例子中,max函数被定义为一个内联函数。当它被调用时,编译器会将其直接嵌入到调用它的代码中,而不是通过函数调用的方式进行调用。

内联函数和宏函数都是用于提高程序执行效率的技术,但它们有一些区别:

  1. 内联函数在编译时展开,而宏函数在预编译时展开。
  2. 内联函数直接嵌入到目标代码中,而宏函数只是简单地做文本替换。
  3. 内联函数有类型检测、语法判断等功能,而宏函数没有。
  4. 内联函数是函数,可以使用类的成员函数和继承等特性,而宏函数不是。

总的来说,内联函数相比宏函数更安全、更易于调试和维护,而且在某些情况下可以提高程序的执行效率。因此,在实际编程中,应该优先使用内联函数,而不是宏函数。

#include <iostream>  
// 定义内联函数  
inline int add(int x, int y) {  
    return x + y;  
}  
// 定义宏函数  
#define ADD(x, y) ((x) + (y))  
int main() {  
    int a = 10, b = 20;  
    // 调用内联函数  
    std::cout << "a + b = " << add(a, b) << std::endl;  
    // 调用宏函数  
    std::cout << "a + b = " << ADD(a, b) << std::endl;  
    return 0;  
}

速记

内联函数在编译时将被直接嵌入到调用它的代码中,内联函数的作用是提高程序的执行效率,因为它避免了函数调用的开销,内联函数的实现方法是在函数定义前加上inline关键字。内联函数在编译时展开,而宏函数在预编译时展开。内联函数直接嵌入到目标代码中,而宏函数只是简单地做文本替换。内联函数有类型检测、语法判断等功能,而宏函数没有。内联函数是函数,可以使用类的成员函数和继承等特性,而宏函数不是。

9,详细解释运算符i++和++i的不同之处

在C++中,i++++i都是对变量i进行自增操作,但它们的使用方式和效果略有不同。

i++是后缀自增运算符,它表示先返回变量i的值,然后再将i的值加1。例如:

int i = 0;
int j = i++; // j的值为0,i的值为1

在上面的例子中,i++先返回i的原始值0,然后将i的值加1。因此,j的值为0,而i的值为1。

++i是前缀自增运算符,它表示先将变量i的值加1,然后返回加1后的值。例如:

int i = 0;
int j = ++i; // j的值为1,i的值为1

在上面的例子中,++i先将i的值加1,然后返回加1后的值1。因此,j的值为1,而i的值为1。

需要注意的是,无论是i++还是++i,都会将变量i的值加1,只是返回值的顺序不同。在实际编程中,应该根据具体的需求选择使用哪种自增运算符。

10,通过C++ 编程举例new和delete关键字

C++提供了一种动态内存分配机制 ,使得程序可以在运行期间,根据实际需要,要求操作系统临时分配一片内存空间用于存放数据.此种内存分配是在程序运行中进行的,而不是在编译时就确定的,因此称为“动态内存分配”

第一行 :建立类型为int的指针p
第二行:p=new int此行动态分配了一片4个字节大小(int)的内存空间,而指针p指向指向这片空间
第三行:通过p读写这片空间

delete

程序从操作系统动态分配所得的内存空间在使用完后应该释放,交还操作系统,以便操作系统将这片内存空间分配给其他程序使用.C++ 中提供了delete 运算符,用以释放动态分配的内存空间.

#include <iostream>  
  
int main() {  
    // 使用 new 关键字动态分配内存  
    int* ptr = new int;  
    // 将分配的内存初始化为 5  
    *ptr = 5;  
    // 输出分配的内存中的值  
    std::cout << "Value in allocated memory: " << *ptr << std::endl;  
    // 使用 delete 关键字释放内存  
    delete ptr;  
    // 将指针设置为 nullptr,避免悬挂指针  
    ptr = nullptr;  
    return 0;  
}
输出:
Value in allocated memory: 5

在这个示例中,我们使用new关键字动态分配了一个整数类型的内存,并将分配的内存地址存储在指针ptr中。然后,我们将内存中的值初始化为5,并通过指针ptr输出该值。最后,我们使用delete关键字释放了分配的内存,并将指针ptr设置为nullptr,以避免悬挂指针的问题。

11,说说new和malloc的区别,各自底层实现原理。

newmalloc都是在C++中用于动态内存分配的操作符,但它们之间存在一些重要的差异。

  1. 调用构造函数和析构函数new会自动调用对象的构造函数,delete会自动调用析构函数。而mallocfree是C语言中的函数,只负责分配和释放内存,不会调用构造函数和析构函数。new可以被重载;malloc不行。new是操作符,而malloc是函数。new在调用的时候先分配内存,在调用构造函数,释放的时候调用析构函数;而malloc没有构造函数和析构函数。malloc需要给定申请内存的大小,返回的指针需要强转;new会调用构造函数,不用指定内存的大小,返回指针不用强转。
  2. 类型安全new是类型安全的,它会返回正确的类型指针。例如,如果你分配一个intnew会返回一个int*。相比之下,malloc只是返回一个void*,它需要被显式地转换为正确的类型。
  3. 异常处理:当new无法分配所需的内存时,它会抛出一个bad_alloc异常。然而,malloc在无法分配内存时只是返回一个NULL指针。

关于它们的底层实现原理:

newdelete是C++的运算符,它们的实现取决于编译器。一般来说,当你使用new时,编译器会首先在堆上找到足够的内存,然后调用构造函数(如果有的话)来初始化对象。同样,当你使用delete时,编译器会调用析构函数(如果有的话),然后释放内存。

**new底层实现:**关键字new在调用构造函数的时候实际上进行了如下的几个步骤:

  1. 创建一个新的对象
  2. 将构造函数的作用域赋值给这个新的对象(因此this指向了这个新的对象)
  3. 执行构造函数中的代码(为这个新对象添加属性)
  4. 返回新对象

mallocfree是C语言库函数,它们通常在底层操作系统提供的API上进行封装。当你调用malloc时,它会在堆上找到足够的内存,并返回一个指向这块内存的指针。当你调用free时,它会将之前分配的内存返回给操作系统。这两个函数都不会对内存进行任何初始化或清理操作。

malloc的分配内存有两个系统调用,一个brk,一个mmap,brk是将.data的最高地址指针_edata往高地址走,一般情况下,我们使用malloc,如果小于128k,则使用brk分配,如果大于128k,则使用mmap在堆和栈之间找一个空闲空间分配。

mmap将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动数据到对应的文件磁盘上,即完成了对文件的操作而不必再调用read, write等系统调用函数,从而可以实现不同进程间的文件共享。

mmap函数其实就是把物理地址映射到虚拟地址,可以由用户空间的进程进行操作,起到一个虚实地址转换的作用;主要有5个参数,第一个一般设置为null,让系统自己分配内存,第二个是映射的长度,第三个是内存的属性,可读,可共享,第四个是要映射的文件,以及文件的偏移量,返回映射的地址,我们可以在这上面进行操作,比如说输出该地址上面的值等,最后用munmap(addr,length)

总的来说,尽管newmalloc都可以用于动态内存分配,但它们在C++中的用法和功能上有明显的差异。在C++中,更推荐使用newdelete,因为它们提供了更多的功能,并且更符合C++的面向对象设计。

#include <iostream>  
  
int main() {  
    // 使用 new 关键字动态分配内存,并初始化对象  
    int* ptr1 = new int(10);  
    // 使用 malloc 函数动态分配内存,需要显式地调用构造函数来初始化对象  
    int* ptr2 = static_cast<int*>(malloc(sizeof(int)));  
    *ptr2 = 20;  
    // 输出分配的内存中的值  
    std::cout << "Value using new: " << *ptr1 << std::endl;  
    std::cout << "Value using malloc: " << *ptr2 << std::endl;  
  
    // 使用 delete 关键字释放内存,会自动调用对象的析构函数  
    delete ptr1;  
    // 使用 free 函数释放内存,不会自动调用析构函数  
    free(ptr2);  
    return 0;  
}
输出:
Value using new: 10  
Value using malloc: 20

在这个示例中,我们使用new关键字和malloc函数分别动态分配了整数类型的内存。对于new关键字,它不仅分配内存,还会调用对象的构造函数来初始化对象。而对于malloc函数,它只是简单地分配内存,需要显式地调用构造函数来初始化对象。

速记

newmalloc都是在C++中用于动态内存分配的操作符,但它们之间存在一些重要的差异。

new会自动调用对象的构造函数,delete会自动调用析构函数。而mallocfree是C语言中的函数,只负责分配和释放内存,不会调用构造函数和析构函数。new可以被重载;malloc不行。new是操作符,而malloc是函数。new在调用的时候先分配内存,malloc需要给定申请内存的大小,返回的指针需要强转;new会调用构造函数,不用指定内存的大小,返回指针不用强转。

new是类型安全的,它会返回正确的类型指针。new无法分配所需的内存时,它会抛出一个bad_alloc异常。malloc在无法分配内存时只是返回一个NULL指针。

newdelete是C++的运算符,new底层实现:创建一个新的对象,将构造函数的作用域赋值给这个新的对象,执行构造函数中的代码,返回新对象

mallocfree是C语言库函数,malloc的分配内存有两个系统调用,一个brk,一个mmap,brk是将.data的最高地址指针_edata往高地址走,一般情况下,我们使用malloc,如果小于128k,则使用brk分配,如果大于128k,则使用mmap在堆和栈之间找一个空闲空间分配。mmap将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。

12,详细说明C++中函数指针和指针函数在用法和目的不同之处。

函数指针和指针函数在C++中都涉及到指针的概念,但它们的用法和目的完全不同。

  1. 函数指针:函数指针是指向函数的指针变量。通常,我们可以把一个函数的地址赋值给一个指针变量,然后通过这个指针变量来调用这个函数。函数指针的声明方式如下:
返回类型 (*指针变量名)(参数类型)

例如:

int (*fp)(int, int);  // fp 是一个指向接受两个int参数并返回int的函数的指针

函数指针主要用于回调函数、函数表等场景。

  1. 指针函数:指针函数是指返回指针类型的函数。即该函数返回的是一个地址值,这个地址值指向一个具体的变量。指针函数的声明方式如下:
返回类型* 函数名(参数类型)

例如:

int* getPointer(int value) {  // getPointer 是一个返回int类型指针的函数
    static int storage = value;  // static使得storage在函数调用结束后不会被销毁
    return &storage;  // 返回storage的地址
}

指针函数主要用于动态内存分配、数据结构的创建等场景。

#include <iostream>  
  
// 定义一个函数,用于演示函数指针  
void printHello() {  
    std::cout << "Hello from printHello function!" << std::endl;  
}  
  
// 定义一个指针函数,返回整型指针  
int* getPointer() {  
    static int value = 42;  
    return &value;  
}  
  
int main() {  
    // 函数指针示例  
    void (*funcPtr)() = printHello; // 定义指向函数的指针,并将其指向printHello函数  
    funcPtr(); // 通过函数指针调用函数  
  
    // 指针函数示例  
    int* ptrFunc = getPointer; // 定义指向函数的指针,该函数返回一个整型指针  
    std::cout << "Value from pointer function: " << *ptrFunc << std::endl;  
  
    return 0;  
}
输出:
Hello from printHello function!  
Value from pointer function: 42

在这个示例中,我们定义了一个函数printHello和一个指针函数getPointerprintHello函数用于输出一条简单的问候语。getPointer函数返回一个整型指针,指向一个静态变量value

main函数中,我们首先演示了函数指针的用法。我们定义了一个指向函数的指针funcPtr,并将其指向printHello函数。然后,通过该函数指针调用函数,输出问候语。

接下来,我们演示了指针函数的用法。我们定义了一个指向函数的指针ptrFunc,该函数返回一个整型指针。然后,我们通过该指针函数调用getPointer函数,并输出返回的指针所指向的值。

总的来说,函数指针和指针函数的主要区别在于:函数指针是指向函数的指针,而指针函数则是返回指针的函数。

13,说说const int *a, int const *a, const int a, int *const a, const int *const a分别是什么,有什么特点。

1. const int a;     //指的是a是一个常量,不允许修改。
2. const int *a;    //a指针所指向的内存里的值不变,即(*a)不变
3. int const *a;    //同const int *a;
4. int *const a;    //a指针所指向的内存地址不变,即a不变
5. const int *const a;   //都不变,即(*a)不变,a也不变

14,说说使用指针需要注意什么?

在C++中使用指针时,有几个关键注意事项:

  1. 初始化指针:一个未初始化的指针可能指向任何位置,可能导致程序崩溃或未定义的行为。因此,最好立即初始化指针,或者至少将其设置为nullptr。
  2. 避免悬挂指针:如果一个指针指向的内存被释放(例如,使用delete操作符),那么该指针就成为悬挂指针。再次使用悬挂指针可能会导致未定义的行为。为了避免这种情况,释放内存后应将指针设置为nullptr。
  3. 避免野指针:野指针是指向非法内存区域的指针,如已被释放的内存,或者是系统保护的内存区域。访问野指针通常会导致程序崩溃。
  4. 内存泄漏:使用new操作符分配的内存必须在使用完后通过delete操作符释放,否则会导致内存泄漏。
  5. 指针运算:指针可以进行算术运算,但必须小心。如果指针指向数组的元素,那么可以通过增加或减少指针的值来访问数组的其他元素。但是,如果指针算术运算的结果超出了数组的边界,就会导致未定义的行为。
  6. 检查指针是否为空:在使用指针之前,应该检查其是否为nullptr。如果指针为空,则尝试访问其指向的内存将导致程序崩溃。
  7. 避免使用裸指针:在现代C++编程中,建议尽可能使用智能指针(如unique_ptr, shared_ptr)代替裸指针,因为智能指针可以自动管理内存,避免内存泄漏和悬挂指针等问题。
  8. 指针与数组:在C++中,数组名称可以被视为指向数组第一个元素的指针。但是,这个指针是一个常量指针,不能被修改以指向其他内存位置。
  9. 指针与const:指向const对象的指针不能用于修改该对象。指向非const对象的const指针不能用于改变指针本身指向的对象,但可以改变对象的内容。
  10. 动态内存分配:使用new和delete进行动态内存分配和释放时,必须匹配使用。即,用new分配的内存必须用delete释放,用new[]分配的内存必须用delete[]释放。

速记

初始化指针,避免悬挂指针:如果一个指针指向的内存被释放(例如,使用delete操作符),那么该指针就成为悬挂指针。避免野指针:内存泄漏,指针运算:如果指针算术运算的结果超出了数组的边界,就会导致未定义的行为。检查指针是否为空,避免使用裸指针,指针与数组:在C++中,数组名称可以被视为指向数组第一个元素的指针。但是,这个指针是一个常量指针,不能被修改以指向其他内存位置。指针与const:指向const对象的指针不能用于修改该对象。动态内存分配:使用new和delete进行动态内存分配和释放时,必须匹配使用

15,请详细说明const * 和* const的不同之处

是的,您是对的。在C++中,const ** const 涉及到指针和 const 关键字,但它们的含义和使用方式确实有所不同。

  1. const *

    • 这是一个指向 const 对象的指针。
    • 这意味着您不能通过这个指针来修改它所指向的对象的值,但您可以改变这个指针本身所指向的对象。
    • 例如,如果您有一个 const int *p,那么您不能通过 *p = 5; 来改变 p 指向的 int 值,但您可以让 p 指向另一个 int 值。
  2. * const

    • 这是一个 const 指针,即这个指针本身的值是 const 的。
    • 这意味着您不能改变这个指针所指向的对象,但您可以改变这个指针所指向的对象的值。
    • 例如,如果您有一个 int * const p,那么您不能让 p 指向另一个 int 值,但您可以通过 *p = 5; 来改变 p 所指向的 int 值。
#include <iostream>  
  
int main() {  
    int value = 5;  
  
    // const int *ptr1 表示指针指向的内容为常量,不能通过指针修改内容  
    const int *ptr1 = &value;  
    std::cout << "ptr1 points to: " << *ptr1 << std::endl;  
    // *ptr1 = 10; // 编译错误,不能通过ptr1修改内容  
  
    // int *const ptr2 表示指针本身为常量,不能修改指针的指向  
    int *const ptr2 = &value;  
    std::cout << "ptr2 points to: " << *ptr2 << std::endl;  
    // ptr2 = &value; // 编译错误,不能修改ptr2的指向  
  
    return 0;  
}

总的来说,const ** const 的主要区别在于 const 关键字修饰的是指针本身还是指针所指向的对象。const * 表示指针所指向的对象是 const 的,而 * const 表示指针本身是 const 的。

16, 详细说明C++有几种传值方式,它们之间的区别是什么?

传参方式有这三种:值传递、引用传递、指针传递

  1. 值传递:这是函数参数传递的默认方式。在这种方式下,实参的值被复制到形参中,因此在函数内部对形参的任何修改都不会影响实参。这种方式的优点是安全,因为函数不能修改实参的值,但缺点是如果传递的数据量大,如大数组或复杂结构,效率可能会较低,因为需要复制整个数据。
  2. 引用传递:在这种方式下,传递给函数的是实参的地址,而不是实参的值。这意味着在函数内部,对形式参数的任何修改都会影响到实际参数。这种方式的优点是可以避免数据的复制,从而提高效率,而且函数可以修改实参的值。但缺点是如果函数误操作,可能会修改实参的值,造成数据错误。
  3. 指针传递:这种方式类似于传址调用,也是传递实参的地址给函数,只是传递的是一个指针。在函数内部,通过解引用指针来访问和修改实参的值。这种方式的优点和缺点与传址调用相似,但由于使用了指针,操作更为复杂,也更容易出错。

总的来说,这三种传值方式的区别主要在于参数传递的机制和对实参的影响。在实际编程中,应根据具体需求和场景选择合适的传值方式。

#include <iostream>  
  
void passByValue(int value) {  
    value += 10;  
    std::cout << "Inside passByValue: " << value << std::endl;  
}  
  
void passByReference(int& ref) {  
    ref += 10;  
    std::cout << "Inside passByReference: " << ref << std::endl;  
}  
  
void passByPointer(int* ptr) {  
    if (ptr != nullptr) {  
        *ptr += 10;  
        std::cout << "Inside passByPointer: " << *ptr << std::endl;  
    }  
}  
  
int main() {  
    int value = 5;  
    std::cout << "Original value: " << value << std::endl;  
  
    passByValue(value);  
    std::cout << "After passByValue: " << value << std::endl;  
  
    passByReference(value);  
    std::cout << "After passByReference: " << value << std::endl;  
  
    passByPointer(&value);  
    std::cout << "After passByPointer: " << value << std::endl;  
  
    return 0;  
}
输出:
Original value: 5  
Inside passByValue: 15  
After passByValue: 5  
Inside passByReference: 15  
After passByReference: 15  
Inside passByPointer: 25  
After passByPointer: 25

在这个示例中,我们定义了一个整数变量 value,然后分别调用了三个不同的函数 passByValuepassByReferencepassByPointer,每个函数都尝试修改 value 的值。

passByValue 函数采用传值调用方式,它将 value 的副本传递给函数,并在函数内部修改副本的值。因此,原始 value 的值不会改变。

passByReference 函数采用传引用调用方式,它直接传递 value 的引用给函数,函数内部对引用的修改将影响到原始 value 的值。

passByPointer 函数采用传指针调用方式,它传递指向 value 的指针给函数,函数内部通过解引用指针来修改 value 的值。同样,这会影响到原始 value 的值。

速记

值传递:形参即使在函数体内值发生变化,也不会影响实参的值;引用传递:形参在函数体内值发生变化,会影响实参的值。指针传递:在指针指向没有发生改变的前提下,形参在函数体内值发生变化,会影响实参的值;

17,在c++中构造函数、析构函数、虚函数可否声明为内联函数

编译器并不真正对声明为inline的构造和析构函数进行内联操作,因为编译器会在构造和析构函数中添加额外的操作(申请/释放内存,构造/析构对象等),其次, class中的函数默认是inline型的,编译器也只是有选择性的inline,将构造函数和析构函数声明为内联函数是没有什么意义的。

指向派生类的指针调用声明为inline的虚函数时,不会内联展开;当是对象本身调 用虚函数时,会内联展开,当然前提依然是函数并不复杂的情况下