当前位置: 首页 > article >正文

面试速通宝典——9

170. 简述数组和指针的区别?

‌‌‌‌  答:数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任意类型的内存块。

1. 修改内容上的区别

char a[] = “hello”;
a[0] = ‘X’;
char * p = “world”; //注意 p 指向常量字符串
p[0] = ‘X’; // 编译器不能发现该错误,运行时错误

2. 用运算符sizeof可以计算出数组的容量(字节数)。sizeof§,p是指针,得到的是一个指针变量的字节数,而不是p所指的内存容量。

解释

‌‌‌‌  数组和指针是C/C++编程中的两个非常重要的概念,尽管它们有很多相似之处,但它们本质上是不同的东西。以下是数组和指针的一些主要区别:

数组和指针的定义

数组(Array)

  • 数组是一个具有相同数据类型的元素的有序集合。在内存中,数组元素是连续存储的。
  • 数组的大小在声明时是固定的,不能在运行时动态改变。

指针(Pointer)

  • 指针是一个变量,用于存储另一个变量的内存地址。
  • 指针可以指向不同类型的数据,并且可以在运行时改变指向的地址。

区别点

1. 内存分配
  • 数组: 在编译时分配,大小固定。例如 int arr[5]; 分配5个 int 类型的连续内存空间。
  • 指针: 可以在运行时分配,大小可以动态变化。例如 int *ptr = (int*)malloc(5 * sizeof(int));分配5个 int 类型的连续内存空间。
2. 声明和初始化
  • 数组: 声明数组会自动分配指定大小的内存。例如 int arr[5];
  • 指针: 必须手动指定分配内存,初始化时通常需要一个已有的地址,例如 int *ptr = NULL; 或 int *ptr = (int*)malloc(5 * sizeof(int));
3. 访问方式
  • 数组: 可以使用数组下标直接访问元素,例如 arr[2]
  • 指针: 可以使用指针算术操作和解引用访问元素,例如 *(ptr + 2) 或者 ptr[2]
4. 类型信息
  • 数组: 类型包含了数组的大小。例如 int arr[5]; 是包含 5 个 int 类型元素的数组。
  • 指针: 类型只包含指向的数据类型,不包含大小信息。例如 int *ptr 是指向 int 类型数据的指针,但不包含指向多少个 int 元素的信息。
5. 存储方式
  • 数组: 数组名代表数组的地址,但类型上也包含了数组长度的信息(仅在声明时)。
  • 指针: 指针变量独立存在,可以指向不同的数据地址,不包含长度信息。

示例代码

#include <stdio.h>
#include <stdlib.h>

int main() {
    // 数组示例
    int arr[5] = {1, 2, 3, 4, 5};
    printf("数组的第二个元素: %d\n", arr[1]);

    // 指针示例
    int *ptr;
    ptr = (int*)malloc(5 * sizeof(int));
    if (ptr == NULL) {
        printf("内存分配失败\n");
        return -1;
    }

    // 分配值
    for (int i = 0; i < 5; i++) {
        ptr[i] = i + 1;  // 等同于 *(ptr + i) = i + 1;
    }
    printf("指针的第二个元素: %d\n", ptr[1]);

    // 释放内存
    free(ptr);
    return 0;
}

总结

  • 数组是具有固定大小的连续内存块,声明时自动分配和初始化,可以用下标直接访问。
  • 指针是一个变量,存储其他变量的地址,可以动态分配和修改内存地址,通过指针算术和解引用来访问数据。

171. C++函数中值的传递方式

‌‌‌‌  值传递、指针传递、引用传递。

解释
‌‌‌‌  在C++中,函数参数的传递方式主要有三种:值传递Pass by Value)、指针传递Pass by Pointer)和引用传递Pass by Reference)。每种传递方式都有其各自的特点和适用场景。下面逐一进行介绍:

1. 值传递(Pass by Value)

特点:

  • 当函数参数使用值传递时,会在调用函数时将实际参数的值拷贝一份传递给函数。函数内部对参数的任何修改都不会影响到原变量。
  • 主要用于传递简单的数据类型,如基本数据类型(int、char、float等)。

示例代码:

#include <iostream>

void increment(int value) {
    value++;
    std::cout << "Inside function: " << value << std::endl;
}

int main() {
    int num = 10;
    increment(num);
    std::cout << "Outside function: " << num << std::endl;
    return 0;
}

输出:

Inside function: 11
Outside function: 10

‌‌‌‌  在这个例子中,increment函数接收到的是num的一个拷贝,因此对value的修改不会影响到num本身。

2. 指针传递(Pass by Pointer)

特点:

  • 当函数参数使用指针传递时,传递的是实际参数的地址。函数内部通过指针可以直接访问和修改实际参数的值。
  • 适用于需要在函数内部修改参数值的情况

示例代码:

#include <iostream>

void increment(int *value) {
    (*value)++;
    std::cout << "Inside function: " << *value << std::endl;
}

int main() {
    int num = 10;
    increment(&num);
    std::cout << "Outside function: " << num << std::endl;
    return 0;
}

输出:

Inside function: 11
Outside function: 11

‌‌‌‌  在这个例子中,increment函数接收到的是num的地址,因此通过解引用指针*value可以直接修改num的值。

3. 引用传递(Pass by Reference)

特点:

  • 当函数参数使用引用传递时,传递的是实际参数的引用(别名)。函数内部对参数的任何修改都会直接影响到原变量。
  • 比指针传递更安全,语法上也更简洁
  • 使用引用传递的参数在函数体内像普通变量一样使用,但实际上是对原始数据的引用。

示例代码:

#include <iostream>

void increment(int &value) {
    value++;
    std::cout << "Inside function: " << value << std::endl;
}

int main() {
    int num = 10;
    increment(num);
    std::cout << "Outside function: " << num << std::endl;
    return 0;
}

输出:

Inside function: 11
Outside function: 11

‌‌‌‌  在这个例子中,increment函数直接接收到num的引用,因此对value的任何修改都会反映到num上。

总结

  • 值传递: 函数接收到的是实际参数的一份拷贝,函数内部对参数的修改不会影响原变量。
  • 指针传递: 函数接收到的是实际参数的地址,函数内部可以通过指针直接修改原变量的值。
  • 引用传递: 函数接收到的是实际参数的引用,函数内部对参数的修改会直接影响原变量,用法上更简单且安全。

‌‌‌‌  这三种传递方式各有优劣,选择哪种方式取决于具体的应用场景和需求。如果需要在函数中修改实际参数的值,推荐使用指针传递或引用传递;而对于传递大对象时,引用传递通常比值传递和指针传递更高效。

172. 内存的分配方式有哪几种?

答:有三种。

  1. 静态存储区:是在程序编译时就已经分配好的,在整个运行期间都存在,如全局变量、常量。
  2. 栈上分配:函数内的局部变量就是从这分配的,但分配的内存容量有限。
  3. 堆上分配:也称动态分配,如我们使用new、malloc分配内存,用delete、free来释放内存。

解释

‌‌‌‌  在讨论内存分配方式时,通常提到的三种方式(静态存储区、栈上分配和堆上分配)主要是指程序在运行时分配和管理内存的机制,而代码区的内存分配则是在程序加载时已经确定的,并且不在程序运行时动态分配或释放。因此,代码区的分配方式一般不包含在这三种运行时内存分配方式的讨论中。具体原因如下:

  1. 静态存储区:静态存储区分配的是全局变量和静态变量的内存,这些变量在程序加载时分配,并在程序整个运行期间保持不变。静态存储区的分配和代码区一样,是在程序加载时确定的,但它主要用于存储数据,而不是代码。

  2. 栈上分配:栈上分配用于存储函数的局部变量和函数调用的上下文信息(如返回地址、参数等)。栈上的内存分配是动态的,在函数调用时分配,函数返回时释放。这是一种运行时的内存管理机制。

  3. 堆上分配:堆上分配用于动态分配内存,例如通过mallocnew等函数分配的内存。堆上的内存分配和释放是由程序员在运行时显式管理的,适用于需要动态管理生命周期的对象。

代码区的分配:代码区的内存分配是由操作系统在程序加载时完成的,并且是只读的。在程序运行期间,代码区的内容不会改变,也不会像栈或堆那样动态分配或释放内存。因此,代码区的分配属于程序加载时的静态分配,与运行时的内存分配机制有所不同。

综上所述,代码区的内存分配是在程序加载时完成的静态分配,与运行时的内存分配方式有所区别,因此通常不在讨论运行时内存分配方式时被提及。

‌‌‌‌  代码区的分配是在程序编译和链接的过程中完成的。具体来说,代码区(又称文本区)存储的是程序的可执行指令,这些指令在程序运行时是只读的。代码区的分配过程可以概括为以下几个步骤:

  1. 编译阶段:在源代码编译时,编译器将源代码翻译成机器代码,并生成目标文件(通常是.o或.obj文件)。这些目标文件中包含了程序的机器指令和一些元数据。

  2. 链接阶段:链接器将一个或多个目标文件与库文件链接,生成最终的可执行文件。在这个过程中,链接器将所有目标文件中的代码段合并,并安排它们在最终可执行文件中的位置。

  3. 加载阶段:当程序被加载到内存中运行时,操作系统的加载器将可执行文件中的代码段加载到内存中的一个专门区域,这个区域就是代码区。加载器负责将代码段映射到内存中的一个只读区域,以确保代码在运行时不会被修改。

总结起来,代码区的分配是在编译和链接过程中由编译器和链接器完成的,最终在程序加载到内存时由操作系统的加载器负责实际的内存分配和映射。

173. extern"C"有什么作用?

答:
‌‌‌‌  Extern"C"是由C++提供的一个连接交换指定符号,用于告诉C++这段代码是C函数。这是因为C++编译后库中函数名会变得很长,与C生成的不一致,造成C++不能直接调用C函数,加上extern"C"后,C++就能直接调用C函数了。

‌‌‌‌  Extern"C"主要使用正规DLL函数的引用和导出,在C++包含C函数或头文件的时候使用。使用时在前面加上extern "C"关键字即可。可以用一句话概括extern"C"这个声明的真实目的:实现C++与C以及其它语言的混合编程。

174. 用什么函数开启新线程、进程?

答:
‌‌‌‌  新线程:CreateThread / AfxBegin Thread 等。
‌‌‌‌  新进程:CreateProcess 等。

175. SendMessage 和 PostMessage 有什么区别?

答:
‌‌‌‌  SendMessage阻塞的,等消息被处理后,代码才能走到SendMessage的下一行。
‌‌‌‌  PostMessage非阻塞的,不管消息是否已经被处理,代码马上走到PostMessage下一行。

解释

‌‌‌‌  这段话描述了SendMessagePostMessage在消息处理机制中的不同行为,特别是在Windows编程中。这两者都是用来将消息发送到指定的窗口或线程,但它们在执行时的行为有所不同。

SendMessage

‌‌‌‌  SendMessage函数是同步的(阻塞的),这意味着它会将消息发送到目标窗口,并等待该消息被处理完毕之后,才返回并继续执行下一行代码。具体来说:

  1. SendMessage将消息发送到目标窗口的消息队列
  2. 目标窗口的窗口过程(Window Procedure)接收并处理该消息。
  3. 处理完消息之后,SendMessage返回结果。
  4. 代码继续执行SendMessage之后的代码行。

‌‌‌‌  因为SendMessage会等待消息被处理完毕,所以它是阻塞的。这意味着如果消息处理需要很长时间,调用SendMessage的线程会被阻塞,直到消息处理完成。

PostMessage

‌‌‌‌  PostMessage函数是异步的(非阻塞的),这意味着它将消息放入目标窗口的消息队列后,立即返回并继续执行下一行代码,而不等待消息被处理。具体来说:

  1. PostMessage将消息放入目标窗口的消息队列。
  2. PostMessage立即返回,不等待消息处理结果。
  3. 代码继续执行PostMessage之后的代码行。
  4. 目标窗口的消息队列会在合适的时间调度并处理该消息。

因为PostMessage不等待消息被处理,所以它是非阻塞的。这意味着调用PostMessage的线程可以继续执行,而不会因为消息处理的时间而被阻塞。

理解的关键点

  • 阻塞和非阻塞SendMessage是阻塞的,调用线程必须等待消息被处理完才会继续执行。PostMessage是非阻塞的,调用线程无需等待消息处理结果,可以立即继续执行。
  • 消息处理机制SendMessage要求同步处理消息,适合需要立即获得处理结果的场景。PostMessage则是异步处理消息,适合不需要立即获得处理结果,或者希望减少线程等待时间的场景。

示例代码

‌‌‌‌  下面是一个简化的示例代码,展示了SendMessagePostMessage的不同行为:

// SendMessage 示例
SendMessage(hWnd, WM_COMMAND, wParam, lParam);
// 只有在消息处理完成后,才会执行下一行代码
printf("SendMessage 后的代码");

// PostMessage 示例
PostMessage(hWnd, WM_COMMAND, wParam, lParam);
// 立即返回并执行下一行代码,而不等待消息处理
printf("PostMessage 后的代码");

‌‌‌‌  在SendMessage的情况下,只有当消息被处理完毕,才会打印"SendMessage 后的代码";而在PostMessage的情况下,消息一发送就立即打印"PostMessage 后的代码",不管消息是否被处理。

176. CMemoryState 的主要功能是什么?

‌‌‌‌  CMemoryState查看内存使用情况,解决内存泄露的问题

解释

‌‌‌‌  CMemoryState 是 MFC(Microsoft Foundation Class)库中的一个类,用于内存调试。它主要用于检测和报告内存泄漏。在调试版本的应用程序中,CMemoryState 可以保存内存的当前状态,并在需要时与另一状态进行比较,以检测内存的分配和释放情况是否一致。

CMemoryState 的主要功能

  1. 保存内存状态
    • 使用 CMemoryState::Checkpoint 方法保存当前的内存状态。
    • 通过保存多个内存状态,可以在不同的时间点记录内存使用情况。
  2. 比较内存状态
    • 使用 CMemoryState::Difference 方法比较两个内存状态。
    • 可以检测出两个检查点之间的内存泄漏情况。
  3. 报告内存泄漏
    • 使用 CMemoryState::DumpStatistics 方法报告内存状态,包括内存泄漏信息。
    • 可以输出内存泄漏的详细信息,帮助开发者定位和修复内存泄漏。

代码示例

以下是一个使用 CMemoryState 检测内存泄漏的简单示例:

#include <afx.h>
#include <iostream>

void TestMemoryLeak()
{
    // 创建一个内存块用于测试
    char* pMemory = new char[100];
    // 注意,这里故意没有删除内存,以测试内存泄漏
}

int main()
{
    // 检查点1:在调用函数之前保存内存状态
    CMemoryState oldState, newState, diffState;
    oldState.Checkpoint();

    // 调用可能产生内存泄漏的函数
    TestMemoryLeak();

    // 检查点2:在调用函数之后保存内存状态
    newState.Checkpoint();

    // 比较两个检查点之间的内存状态
    if (diffState.Difference(oldState, newState))
    {
        std::cout << "Memory leaks detected:" << std::endl;
        diffState.DumpStatistics();
    }
    else
    {
        std::cout << "No memory leaks detected." << std::endl;
    }

    return 0;
}

代码解释

  1. 初始化内存状态
    • CMemoryState oldState, newState, diffState; 创建三个 CMemoryState 对象,用于保存不同时间点的内存状态。
  2. 保存内存状态
    • oldState.Checkpoint(); 在调用 TestMemoryLeak 之前保存当前的内存状态。
    • newState.Checkpoint(); 在调用 TestMemoryLeak 之后保存当前的内存状态。
  3. 比较内存状态
    • diffState.Difference(oldState, newState); 比较两个检查点之间的内存状态。
    • 如果检测到内存泄漏,Difference 方法将返回 TRUE,并且 diffState 将包含泄漏的详细信息。
  4. 报告内存泄漏
    • diffState.DumpStatistics(); 输出内存泄漏的详细信息。

通过这种方式,CMemoryState 可以帮助开发者在调试阶段检测和修复内存泄漏,确保应用程序的内存管理更加健壮和高效。


http://www.kler.cn/news/331809.html

相关文章:

  • CORE MVC 过滤器 (筛选器)《2》 TypeFilter、ServiceFilter
  • 科技展厅方案新视角:布局优化促进深度互动体验?
  • HTTP【网络】
  • ajax的原理,使用场景以及如何实现
  • 编程思维之函数返回函数
  • 网站可疑问题
  • Python并发编程挑战与解决方案
  • ChatGPT+R语言助力生态环境数据统计分析!回归与混合效应模型、多元统计分析、结构方程模型(SEM)(lavaan)、Meta分析、贝叶斯回归等
  • 机器学习基本上就是特征工程——《特征工程训练营》
  • 每日学习一个数据结构-树
  • 世邦通信股份有限公司IP网络对讲广播系统RCE
  • 线程池的实现和讲解:解决多线程并发服务器创建销毁线程消耗过大的问题
  • [Web安全 网络安全]-XXE 外部实体注入攻击XML
  • UbuntuServer22.04.4安装Docker Compose
  • 云计算Openstack Swift
  • 数据结构(栈和队列的实现)
  • wordpress重置密码的方法
  • chatgpt学术科研prompt模板有哪些?chatgpt的学术prompt有哪些?学术gpt,学术科研
  • SpringBoot实战:构建学科竞赛管理系统
  • QCamera6.7笔记