跳至主要內容

C++知识汇总(三)

CodeShouhu大约 28 分钟使用指南Markdown

C++知识汇总(三)

1,malloc 函数返回的地址是什么地址

malloc函数只能返回第一个字节的地址

#include <iostream>  
#include <cstdlib>  
  
int main() {  
    // 使用malloc函数动态分配内存  
    int* ptr = (int*)malloc(sizeof(int) * 5);  
  
    // 检查是否分配成功  
    if (ptr != nullptr) {  
        // 输出分配的内存地址  
        std::cout << "Allocated memory address: " << ptr << std::endl;  
  
        // 初始化分配的内存  
        for (int i = 0; i < 5; i++) {  
            ptr[i] = i + 1;  
        }  
  
        // 输出分配的内存中的值  
        std::cout << "Values in allocated memory: ";  
        for (int i = 0; i < 5; i++) {  
            std::cout << ptr[i] << " ";  
        }  
        std::cout << std::endl;  
  
        // 释放分配的内存  
        free(ptr);  
        ptr = nullptr;  
    } else {  
        std::cout << "Failed to allocate memory." << std::endl;  
    }  
  
    return 0;  
}
输出示例:
Allocated memory address: 0x7f8a59402b30  
Values in allocated memory: 1 2 3 4 5

在这个示例中,我们使用malloc函数动态分配了一个大小为5个整数的内存块,并将返回的指针存储在ptr变量中。然后,我们检查是否分配成功,并输出分配的内存地址。接下来,我们使用返回的指针初始化分配的内存,并输出其中的值。最后,我们使用free函数释放分配的内存,并将指针设置为nullptr,以避免悬挂指针的问题。

2,详解c++中的this指针:

在调用成员函数时,编译器会隐含地插入一个参数,这个参数就是this指针。this指针指向当前对象本身,表示当前对象的地址。

每次成员函数存取数据成员时,由隐含作用this指针。而通常不去显式地使用this指针来引用数据成员

#include <iostream>  
  
class MyClass {  
private:  
    int myValue;  
  
public:  
    MyClass(int value) : myValue(value) {  
        std::cout << "Constructor called with value: " << value << std::endl;  
    }  
  
    void setValue(int value) {  
        this->myValue = value; // 使用this指针访问当前对象的myValue成员变量  
        std::cout << "setValue() called with value: " << value << std::endl;  
    }  
  
    int getValue() const {  
        return myValue;  
    }  
};  
  
int main() {  
    MyClass obj(5); // 创建对象并调用构造函数,输出:Constructor called with value: 5  
    obj.setValue(10); // 调用setValue函数,输出:setValue() called with value: 10  
    std::cout << "myValue: " << obj.getValue() << std::endl; // 输出:myValue: 10  
    return 0;  
}

在上面的示例中,this指针在setValue函数中使用,用于区分成员变量myValue和函数参数value。通过this->myValue,我们明确地表示要访问当前对象的myValue成员变量,而不是函数参数。输出显示了对象的状态和函数调用的过程。

3,使用C++运行的程序程序占用的内存总共分为哪几个部分以及每个部分的特征?

一个由C/C++编译的程序占用的内存可以分为以下几个部分:

  1. 代码段:代码段也称文本段,其中存放了程序的二进制代码。这部分区域的内容通常在程序执行期间是不可变的。
  2. 数据段:数据段可以分为初始化数据段和未初始化数据段。初始化数据段包含了程序中初始化的全局变量和静态变量,而未初始化数据段(在C++中通常称为BSS段)则包含了程序中未初始化的全局变量和静态变量。在程序启动前,操作系统会自动将未初始化数据段的内容设置为0或空指针。
  3. 堆区:堆区是由程序员动态分配和管理的内存区域。程序在运行时,可以通过mallocnew等函数向系统申请分配内存,并在使用完毕后通过freedelete等函数释放内存。注意,由于堆区是由程序员自行管理的,因此需要谨慎处理内存分配和释放,以避免内存泄漏等问题。
  4. 共享区:共享区是存放共享库或动态链接库的内存区域。多个程序在运行时可以共享同一块共享区内的代码和数据,以节省内存空间。
  5. 栈区:栈区是由编译器自动分配和管理的内存区域。栈区主要用于存放函数的参数、局部变量以及存储程序的执行上下文。每当一个函数被调用时,编译器会在栈区为其分配一个新的栈帧,并在函数返回后自动释放该栈帧。

这些内存区域的组织和管理方式是由编译器和操作系统共同决定的。了解程序内存布局有助于更好地理解和调试程序。

4,在C++中初始化为0的全局变量和静态变量主要存放在哪里

BSS段通常是指用来存放程序中未初始化的或者初始化为0的全局变量和静态变量的一块内存区域。特点是可读写的,在程序执行之前BSS段会自动清0。

5,详细解释C++中内存对齐的作用,如何使用?

内存对齐应用于三种数据类型中:struct/class/union

struct/class/union内存对齐原则有四个:

  1. 数据成员对齐规则:结构(struct)或联合(union)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小的整数倍开始。
  2. 结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部"最宽基本类型成员"的整数倍地址开始存储。(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储)。
  3. 收尾工作:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的"最宽基本类型成员"的整数倍。不足的要补齐。(基本类型不包括struct/class/uinon)。
  4. sizeof(union),以结构里面size最大元素为union的size,因为在某一时刻,union只有一个成员真正存储于该地址。

在结构体中,**编译器为结构体的每个成员按其自然边界(alignment)分配空间。**各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构体的地址相同。

所谓的“对齐”,比如4字节的int型,其起始地址应该位于4字节的边界上,即起始地址能够被4整除

6,详细解释内存泄露的原因,以及如何预防?

在C++中,内存泄露是指程序在动态分配内存后,未能正确释放该内存,从而导致系统内存逐渐耗尽的问题。内存泄露通常是由于程序员的疏忽或错误导致的,比如在堆区分配的内存没有被正确释放,或者指针被错误地重新分配或释放。

内存泄露的检测可以使用一些工具和技术,以下是一些常用的方法:

  1. 代码审查:通过对代码进行仔细审查,找出可能导致内存泄露的代码片段。这需要对C++的内存管理机制有一定的了解,并且需要阅读和理解大量的代码。
  2. 内存检测工具:使用一些内存检测工具,如Valgrind、Dr. Memory等,可以帮助检测内存泄露。这些工具通过在程序运行时监视内存分配和释放情况,可以发现未被释放的内存块。
  3. 日志记录:在程序中添加日志记录功能,记录每次内存分配和释放的情况。通过分析日志文件,可以发现是否存在内存泄露。
  4. 智能指针:使用C++11引入的智能指针(如unique_ptr、shared_ptr等)可以自动管理内存,避免内存泄露。智能指针会在适当的时候自动释放内存,从而减少了程序员的工作量,也减少了内存泄露的风险。
  5. 内存管理库:使用一些内存管理库,如Boost.Pool、jemalloc等,可以提供更高效的内存分配和释放机制,也可以帮助检测内存泄露。

需要注意的是,内存泄露的检测和修复是一项复杂的工作,需要程序员具备一定的经验和技能。在实际编程中,应该尽可能避免使用动态内存分配,尽可能使用智能指针等RAII机制来管理内存。

7, 详细解释堆和栈的不同之处以及特征

在C++编程中,堆(Heap)和栈(Stack)是两种非常重要的内存管理概念。以下是它们的主要区别:

  1. 管理方式:栈是由操作系统自动分配和释放的,因此,其内存管理效率高于堆。而堆是由程序员手动分配和释放的,如果程序员忘记释放,就会导致内存泄漏。
  2. 内存大小:一般来说,栈的内存空间相对较小,而堆的内存空间较大。这是因为操作系统为每个进程分配的栈的大小通常是有限的,而且栈的大小一般是固定的。而堆的大小只受限于操作系统的可用内存。
  3. 生存周期:栈上分配的内存的生命周期由函数调用决定。当函数被调用时,其在栈上分配的内存被创建,当函数返回时,这些内存被自动释放。而堆上分配的内存的生命周期由程序员手动管理。一旦分配,它将一直存在,直到程序员显式地释放它。
  4. 分配方式:栈是连续分配的,分配和释放速度较快。而堆是不连续分配的,分配和释放速度较慢。
  5. 碎片问题:在堆上频繁地分配和释放不同大小的内存可能会导致内存碎片化,而在栈上分配内存则不会导致这个问题。
  6. 适用场景:栈主要用于存储局部变量和函数调用信息,而堆主要用于存储动态分配的内存,例如使用new关键字创建的对象。

总的来说,栈和堆各有其优点和缺点,应根据具体的使用场景选择合适的内存管理方式。

8,当C++程序需要从堆中动态分配内存时,系统应该在虚拟内存上如何操作的

页表:是一个存放在物理内存中的数据结构,它记录了虚拟页与物理页的映射关系

在进行动态内存分配时,例如malloc()函数或者其他高级语言中的new关键字,操作系统会在硬盘中创建或申请一段虚拟内存空间,并更新到页表(分配一个页表条目(PTE),使该PTE指向硬盘上这个新创建的虚拟页),通过PTE建立虚拟页和物理页的映射关系。

9,C++底层从堆和栈上建立对象哪个快?

从两方面来考虑︰

分配和释放,堆在分配和释放时都要调用函数( malloc,free),比如分配时会到堆空间去寻找足够大小的空间(因为多次分配释放后会造成内存碎片),这些都会花费一定的时间,具体可以看看malloc和free的源代码,函数做了很多额外的工作,而栈却不需要这些。

访问时间,访问堆的一个具体单元,需要两次访问内存,第一次得取得指针,第二次才是真正的数据,而栈只需访问一次。另外,堆的内容被操作系统交换到外存的概率比栈大,栈一般是不会被交换出去的。

#include <iostream>  
#include <chrono>  
  
class MyClass {  
public:  
    MyClass() {  
        // 执行一些初始化操作  
    }  
};  
  
int main() {  
    std::chrono::high_resolution_clock::time_point t1, t2;  
  
    // 从栈上创建对象  
    t1 = std::chrono::high_resolution_clock::now();  
    MyClass obj1;  
    t2 = std::chrono::high_resolution_clock::now();  
    std::chrono::duration<double> elapsed_time = t2 - t1;  
    std::cout << "创建栈对象耗时:" << elapsed_time.count() << " 秒" << std::endl;  
  
    // 从堆上创建对象  
    t1 = std::chrono::high_resolution_clock::now();  
    MyClass* obj2 = new MyClass();  
    t2 = std::chrono::high_resolution_clock::now();  
    elapsed_time = t2 - t1;  
    std::cout << "创建堆对象耗时:" << elapsed_time.count() << " 秒" << std::endl;  
  
    // 释放堆对象  
    delete obj2;  
  
    return 0;  
}

在这个示例程序中,我们创建了一个名为MyClass的类,并在main函数中分别从栈和堆上创建了该类的对象。我们使用C++标准库中的chrono库来计算创建对象所需的时间,并将结果输出到控制台。

10,详细解释堆栈溢出是什么,有什么区别?

堆栈溢出就是向该数据块写入了过多的数据,导致数据越界。堆栈溢出可以理解为两个方面:堆溢出和栈溢出。堆溢出:比如不断的new 一个对象,一直创建新的对象,而不进行释放,最终导致内存不足。将会报错:OutOfMemory Error。
栈溢出:一次函数调用中,栈中将被依次压入:参数,返回地址等,而方法如果递归比较深或进去死循环,就会导致栈溢出。将会报错:StackOverflow Error。

#include <iostream>  
  
void recursiveFunction() {  
    std::cout << "Function called." << std::endl;  
    recursiveFunction(); // 无限递归调用,导致堆栈溢出  
}  
  
int main() {  
    std::cout << "Starting program." << std::endl;  
    recursiveFunction();  
    std::cout << "Ending program." << std::endl; // 该行不会执行,因为堆栈溢出  
    return 0;  
}

上述程序定义了一个递归函数 recursiveFunction,该函数在每次调用时都会输出一条消息。由于没有退出递归的条件,因此该函数会无限递归地调用自身,最终导致堆栈溢出。在 main 函数中,我们调用了 recursiveFunction 函数,但是由于堆栈溢出,程序会在输出一些消息后崩溃,而不会输出 "Ending program."。

11,在main执行之前和之后执行的代码可能是什么,有什么区别?

main函数执行之前,主要就是初始化系统相关资源:(栈静全队初传)

(1)设置栈指针(2)初始化静态static变量和global全局变量,即.data段的内容

(3)将未初始化部分的全局变量赋初值:数值型short,int,long等为0,bool为FALSE,指针为NULL等,即.bss段的内容 (4)全局对象初始化,在main之前调用构造函数,这是可能会执行前的一些代码(5)将main函数的参数argc,argv等传递给main函数,然后才真正运行main函数

main函数执行之后: 全局对象的析构函数会在main函数之后执行;

#include <iostream>  
  
// 全局变量初始化  
int globalVariable = initializeGlobalVariable();  
  
int initializeGlobalVariable() {  
    std::cout << "Global variable initialized." << std::endl;  
    return 0;  
}  
  
// 类定义  
class MyClass {  
public:  
    MyClass() {  
        std::cout << "Object created." << std::endl;  
    }  
    ~MyClass() {  
        std::cout << "Object destroyed." << std::endl;  
    }  
};  
  
// 静态变量初始化  
static MyClass staticObject;  
  
int main() {  
    std::cout << "Main function executed." << std::endl;  
    return 0;  
}
输出:
Global variable initialized.  
Object created.  
Main function executed.  
Object destroyed.

在上述程序中,全局变量 globalVariable 的初始化函数 initializeGlobalVariable 会在main函数执行之前被调用,因此会输出 "Global variable initialized."。静态变量 staticObject 的构造函数也会在main函数执行之前被调用,因此会输出 "Object created."。当程序结束时,staticObject 的析构函数会被调用,因此会输出 "Object destroyed."。

12,strlen和sizeof的不同之处?

sizeof是运算符,并不是函数,结果在编译时得到而非运行中获得,所以不能用来得到动态分配(运行时分配)存储空间的大小。strlen是字符处理的库函数。

sizeof参数可以是任何数据的类型或者数据(sizeof参数不退化);strlen的参数只能是字符指针且结尾是'\0'的字符串。 因为sizeof值在编译时确定,

#include <iostream>  
#include <cstring>  
  
using namespace std;  
  
int main() {  
    char str[] = "Hello, world!";  
    int arr[] = {1, 2, 3, 4, 5};  
  
    cout << "strlen(str) = " << strlen(str) << endl;  // 输出字符串str的长度  
    cout << "sizeof(str) = " << sizeof(str) << endl;  // 输出字符串str所占用的内存空间大小  
    cout << "sizeof(arr) = " << sizeof(arr) << endl;  // 输出数组arr所占用的内存空间大小  
  
    return 0;  
}
输出:
strlen(str) = 13  
sizeof(str) = 14  
sizeof(arr) = 20

在上述示例中,通过strlen计算字符串"Hello, world!"的长度为13,不包括末尾的空字符'\0'。而sizeof(str)输出的是字符串在内存中占用的空间大小,包括末尾的空字符'\0',因此为14字节。对于数组arrsizeof(arr)输出的是整个数组所占用的内存空间大小,为20字节(5个int类型元素,每个int占4字节)。

13,在C++中如何初始化指针并赋值:

指针的初始化就是给指针赋初值,&符号可以用来获取对象的内存地址,并且赋值给指针变量。指针变量的初始化和赋值都可以通过运算符“=”来实现。在C/C++中,指针对于数组的操作是通过将数组的地址,通常是第一个数的地址赋值给指针来进行操作的。指针可以操作一维和多维数组。数组指针是一个指针变量,它指向一个数组。而指针数组是一个只包含指针元素的数组,它的元素可以指向相同类型的不同对象。

#include <iostream>  
  
int main() {  
    int num = 10;  // 定义一个整数变量  
    int* ptr = &num;  // 声明指针变量并赋值  
  
    std::cout << "num 的值为:" << num << std::endl;  
    std::cout << "ptr 所指向的值为:" << *ptr << std::endl;  
  
    return 0;  
}
输出:
num 的值为:10  
ptr 所指向的值为:10

在上述示例中,首先定义了一个整数变量 num,然后声明了一个指针变量 ptr,并将 num 的地址赋值给 ptr。通过 *ptr 可以访问 ptr 所指向的变量值,输出结果与 num 的值相同。

14,在C++中指针和引用的不同之处

指针是一个变量,存储的是一个地址,引用跟原来的变量实质上是同一个东西,是原变量的别名

指针可以为空,引用不能为NULL且在定义时必须初始化

指针在初始化后可以改变指向,而引用在初始化之后不可再改变

sizeof指针得到的是本指针的大小,sizeof引用得到的是引用所指向变量的大小

当把指针作为参数进行传递时,也是将实参的一个拷贝传递给形参,两者指向的地址相同,但不是同 一个变量,在函数中改变这个变量的指向不影响实参,而引用却可以。

#include <iostream>  
  
int main() {  
    int num1 = 10;  
    int num2 = 20;  
  
    // 指针示例  
    int* ptr = &num1; // ptr指向num1  
    std::cout << "ptr指向的值:" << *ptr << std::endl; // 输出10  
  
    ptr = &num2; // ptr重新指向num2  
    std::cout << "ptr指向的值:" << *ptr << std::endl; // 输出20  
  
    // 引用示例  
    int& ref = num1; // ref是num1的引用  
    std::cout << "ref引用的值:" << ref << std::endl; // 输出10  
  
    // ref = num2; // 错误!引用不能重新绑定到其他变量  
  
    return 0;  
}
输出:
ptr指向的值:10  
ptr指向的值:20  
ref引用的值:10

在上述示例中,首先定义了两个整数变量num1num2。在指针示例中,ptr先指向num1并输出其值,然后重新指向num2并输出其值。在引用示例中,refnum1的引用,输出其值。尝试将ref重新绑定到num2会导致编译错误,因为引用一旦绑定就不能更改。

15,在传递函数参数时,什么时候该使用指针,什么时候该使用引用呢?

在C++中,选择使用指针或引用作为函数参数主要取决于你的具体需求。以下是一些基本的指导原则:

  1. 当你需要修改传入对象的内容时:如果你需要改变传入对象的内容,而不仅仅是它的副本,你应该使用指针或引用。在这种情况下,使用引用通常更为直观和简洁。例如:
void incrementValue(int& value) {
    value++;
}

在这个例子中,incrementValue函数将增加传入的value的值。如果你传入的是一个整数,函数将不会改变它,因为整数是按值传递的。

  1. 当你需要传递大型对象时:为了避免复制大型对象带来的开销,你可以使用指针或引用。在这种情况下,使用引用通常更为直观和简洁。例如:
class BigObject {
    // ... lots of data ...
};

void processBigObject(BigObject& object) {
    // ... process object ...
}

在这个例子中,processBigObject函数将处理传入的object。如果我们传递的是一个BigObject的实例,复制它可能会很昂贵。通过引用传递可以避免这种开销。

  1. 当你需要处理可能为null的对象时:在这种情况下,你应该使用指针。因为引用不能绑定到null,如果尝试这样做,程序将会崩溃。例如:
void printString(const char* string) {
    if (string != nullptr) {
        std::cout << string << std::endl;
    } else {
        std::cout << "Null string passed" << std::endl;
    }
}

在这个例子中,printString函数将打印传入的字符串,或者如果字符串是null,它将打印一条消息。如果你尝试使用引用来做同样的事情,你将会得到一个程序崩溃,因为引用不能绑定到null。

  1. 当你需要在多个函数之间共享数据时:在这种情况下,你可以使用指针或引用。使用指针可能会更为复杂,因为你必须管理内存的生命周期。使用引用可以使代码更简洁,但你必须确保引用始终绑定到有效的对象。例如:
int sharedData = 0;

void incrementSharedData(int& data) {
    data++;
}

void useSharedData(int& data) {
    std::cout << "Shared data: " << data << std::endl;
}

在这个例子中,sharedData是在多个函数之间共享的。incrementSharedData函数将增加它的值,useSharedData函数将打印它的值。

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

在C++中,数组和指针虽然有一些相似之处,但也有一些重要的区别。以下是数组和指针之间的主要区别:

  1. 存储方式:数组是一种复合数据类型,它在内存中占据一块连续的存储空间,用于存储相同类型的数据元素。而指针是一个变量,用于存储另一个变量的地址。
  2. 可变性:数组的大小在声明时必须确定,并且在其生命周期中无法更改。而指针可以随时改变,指向不同的内存地址。
  3. 操作符:数组使用下标操作符([])来访问元素,该操作符会根据下标计算元素的内存地址。指针使用解引用操作符(*)来访问所指向的变量或数组元素。
  4. 生命周期:当定义一个数组时,其生命周期取决于它是如何定义的。例如,局部数组在函数返回时被销毁,全局数组在程序结束时被销毁。而指针变量的生命周期也取决于它是如何定义的,但它可以指向的内存区域的生命周期取决于该区域是如何分配的。
  5. 数组名是一个常量指针:在C++中,数组名实际上是一个指向数组第一个元素的常量指针。这意味着你可以将数组名作为指针参数传递给函数,但你不能修改数组名指向的内存地址。
#include<iostream>  
using namespace std;  
  
int main() {  
    int array[5] = {1, 2, 3, 4, 5}; // 定义一个大小为5的数组  
    int *ptr = array; // 定义一个指针,让它指向数组的第一个元素  
  
    cout << "Array elements: ";  
    for (int i = 0; i < 5; i++) {  
        cout << array[i] << " "; // 使用数组索引访问元素  
    }  
    cout << endl;  
  
    cout << "Pointer elements: ";  
    for (int i = 0; i < 5; i++) {  
        cout << *(ptr + i) << " "; // 使用指针访问元素  
    }  
    cout << endl;  
  
    // 试图改变数组的大小,这是不允许的  
    // array = new int[10]; // 这会引发编译错误  
  
    // 改变指针的指向  
    int *newPtr = ptr + 2;  
    cout << "New pointer element: " << *newPtr << endl; // 输出3,因为newPtr指向数组的第三个元素  
  
    return 0;  
}

这段代码首先定义了一个数组和一个指向该数组的指针,然后通过索引和指针访问数组的元素。然后,它试图改变数组的大小,这将引发一个编译错误,因为数组的大小是固定的。最后,它改变了指针的指向,使其指向数组的第三个元素。

17,详细解释什么是函数指针,有怎样的使用场景,如何做?

函数指针是指向函数入口地址的指针。在C++中,函数名实际上是一个指向函数第一条指令的常量指针。函数指针的定义方式如下:

返回类型 (*指针变量名)(参数类型列表) = 函数名;

其中,返回类型是函数返回值的类型,指针变量名是你为函数指针定义的名称,参数类型列表是函数的参数类型列表,函数名是你想要指向的函数的名称。

函数指针的使用场景主要有以下几个方面:

  1. 回调函数:函数指针通常用于实现回调函数,也就是将一个函数作为参数传递给另一个函数,让后者在适当的时候调用前者。例如,你可以将一个比较函数传递给排序算法,以便自定义排序规则。
  2. 函数表:函数指针可以用于创建函数表,也称为跳转表或函数数组。这样的表可以在运行时动态地选择要调用的函数。这在某些情况下可以提高性能,比如在实现状态机或解释器时。
  3. 多态性:在某些情况下,函数指针可以实现类似于面向对象编程中的多态性。例如,你可以创建一个函数指针数组,其中每个元素指向一个不同类型的对象的相同名称的成员函数。然后,你可以根据对象的类型动态地选择调用哪个成员函数。

总的来说,函数指针在C++编程中有许多用途,可以使程序更加灵活和高效。然而,使用函数指针也会增加代码的复杂性,因此在某些情况下可能更适合使用其他技术,如类和对象、模板和lambda表达式等。

#include <iostream>  
using namespace std;  
  
// 定义一个函数,用于演示函数指针  
void myFunction() {  
    cout << "Hello, World!" << endl;  
}  
  
int main() {  
    // 定义一个函数指针,指向myFunction函数  
    void (*funcPtr)() = &myFunction;  
  
    // 通过函数指针调用函数  
    funcPtr();  
  
    return 0;  
}

在上面的例子中,我们定义了一个名为myFunction的函数,它输出Hello, World!。然后,在main函数中,我们定义了一个名为funcPtr的函数指针,指向myFunction函数。最后,我们通过funcPtr()调用函数,输出Hello, World!

18, 详细解释C++中函数指针和指针函数的不同之处。

在C++中,函数指针和指针函数有以下区别:

  1. 定义方式:函数指针是指向函数的指针变量,其定义方式如下:
返回类型 (*指针变量名)(参数类型列表);

指针函数则是指返回指针类型的函数,其定义方式如下:

返回类型* 函数名(参数类型列表);
#include <iostream>  
  
// 定义一个函数  
void printHello() {  
    std::cout << "Hello from printHello function!" << std::endl;  
}  
  
int main() {  
    // 定义一个函数指针,指向printHello函数  
    void (*funcPtr)() = &printHello;  
  
    // 通过函数指针调用函数  
    funcPtr();  
  
    return 0;  
}
输出:
Hello from printHello function!

在上面的例子中,funcPtr是一个函数指针,它指向了printHello函数。通过这个函数指针,我们可以调用它所指向的函数。

  1. 用法:函数指针可以用来调用函数或作为函数参数传递,而指针函数则返回一个地址值,这个地址值通常指向一个变量或对象的内存位置。
  2. 作用:函数指针的主要作用是将函数作为参数传递给其他函数或作为数据结构中的元素,以实现回调函数、函数表等功能。指针函数的主要作用是返回一个地址值,以便在其他地方访问和操作该地址指向的变量或对象。

举个例子来说,假设有一个函数指针 fp 和一个指针函数 pf

int (*fp)(int, int);  // 函数指针,指向一个返回类型为 int、接受两个 int 参数的函数
int* pf(int, int);    // 指针函数,返回一个指向 int 类型的指针

你可以这样使用它们:

// 使用函数指针调用函数
fp = &someFunction;  // 将 someFunction 函数的地址赋值给 fp
int result = fp(2, 3);  // 使用 fp 调用 someFunction 函数,并传递参数 2 和 3

// 使用指针函数调用函数并获取返回值的地址
int* pResult = pf(4, 5);  // 调用 pf 函数,并将返回值的地址存储在 pResult 指针中

需要注意的是,函数指针和指针函数在使用时都要注意空指针和野指针的问题,以避免程序崩溃或产生不可预知的行为。

#include <iostream>  
  
// 定义一个指针函数,返回整型指针  
int* createArray(int size) {  
    int *arr = new int[size];  
    for (int i = 0; i < size; ++i) {  
        arr[i] = i;  
    }  
    return arr;  
}  
  
int main() {  
    // 调用指针函数,获取返回的指针  
    int *arrPtr = createArray(5);  
  
    // 输出数组内容  
    for (int i = 0; i < 5; ++i) {  
        std::cout << arrPtr[i] << " ";  
    }  
    std::cout << std::endl;  
  
    // 释放内存  
    delete[] arrPtr;  
  
    return 0;  
}

输出:

0 1 2 3 4

在上面的例子中,createArray是一个指针函数,它返回一个整型指针。我们调用这个函数,并将返回的指针存储在arrPtr中,然后可以通过这个指针访问数组的内容。

19,常量指针和指针常量区别?

在C++中,常量指针和指针常量有以下区别:

  1. 常量指针(Const Pointer):常量指针是指向常量的指针,也就是它所指向的对象的值不能被修改。常量指针的定义方式如下:
const 数据类型 *指针变量名;

例如:

const int *p;  // p 是一个指向整型常量的指针

这意味着,你不能通过常量指针来修改它所指向的对象的值,但你可以将常量指针指向不同的对象。

#include <iostream>  
  
int main() {  
    int num = 10;  
    const int *ptr = &num; // 声明一个指向整型的常量指针  
    std::cout << "ptr指向的内容为:" << *ptr << std::endl;  
    // *ptr = 20; // 尝试修改指针指向的内容,编译会报错  
    return 0;  
}
输出:
ptr指向的内容为:10

在上面的例子中,ptr是一个指向整型的常量指针,它指向了变量num。由于ptr指向的内容是常量,因此无法通过*ptr修改num的值。

  1. 指针常量(Pointer Const):指针常量是指针本身是一个常量,也就是它的值不能被修改,但它所指向的对象的值可以被修改。指针常量的定义方式如下:
数据类型 * const 指针变量名;

例如:

int * const p;  // p 是一个指向整型的指针常量

这意味着,你不能改变指针常量的值,也就是不能让它指向不同的对象,但你可以通过指针常量来修改它所指向的对象的值。

#include <iostream>  
  
int main() {  
    int num1 = 10;  
    int num2 = 20;  
    int *const ptr = &num1; // 声明一个指针常量  
    std::cout << "ptr初始指向的内容为:" << *ptr << std::endl;  
    // ptr = &num2; // 尝试修改指针的值,编译会报错  
    *ptr = 30; // 可以修改指针指向的内容  
    std::cout << "ptr修改后指向的内容为:" << *ptr << std::endl;  
    return 0;  
}

在上面的例子中,ptr是一个指针常量,它的初始值指向了变量num1。由于ptr本身是常量,因此不能将它的值赋给其他指针变量,但可以通过*ptr修改它所指向的内容。

需要注意的是,常量指针和指针常量都是指针,它们都可以指向某个对象,只是限制不同。常量指针限制了对所指向对象的修改,而指针常量限制了指针本身的修改。在使用时,需要根据实际需求选择合适的类型。