C/C++面试八股题(九)

目录:

1.请你简单说说数组存放在哪里?

2.请你介绍一下c/c++中变量的作用域都有哪些?

3.请问sizeof与strlen的区别,简单说说?

4.简述C++有几种传值方式之间的区别?

5.指针用的多不,请问数组指针与指针数组的区别?

6.请问NULL和nullptr区别?

内容:

1.请你简单说说数组存放在哪里?

  • 当数组是作为局部变量定义在函数中时,数组会存放在栈中。
  • 栈中的数组内存是自动分配和释放的,由编译器完成。
  • 数组在函数结束后,栈上的内存会被回收。
  • 栈的大小是有限的,通常在几 MB 左右,不能分配太大的数组。

例子

void func() {
    int* arr = (int*)malloc(sizeof(int) * 10);  // 动态分配,存放在堆中
    arr[0] = 42; 
    free(arr);  // 释放堆内存
}

  • 当使用动态分配(如 malloccallocnew)创建数组时,数组存放在堆中。
  • 堆中的数组内存是手动分配和释放的,需要程序员显式调用 freedelete 释放内存。
  • 堆的大小一般远大于栈,适合分配较大的数组。

例子:

void func() {
    int* arr = (int*)malloc(sizeof(int) * 10);  // 动态分配,存放在堆中
    arr[0] = 42; 
    free(arr);  // 释放堆内存
}

数据段

  • 数据段分为 全局/静态区BSS段,具体取决于变量是否被初始化。

  • 如果数组是全局变量,或者被 static 修饰且已初始化,会存储在 数据段的已初始化区。
  • 程序运行时全程有效。
  • 这些数组会在程序加载时分配,并且内存初始化为指定的值。
int arr[3] = {1, 2, 3};  // 全局数组,存储在数据段的已初始化区
static int s_arr[3] = {4, 5, 6};  // 静态数组,存储在数据段的已初始化区

  • 如果数组是全局变量或被 static 修饰且未初始化,则存储在 BSS段。
  • BSS段的数组会在程序加载时分配,并自动初始化为全 0。
int arr[3];  // 全局数组,未初始化,存储在BSS段
static int s_arr[3];  // 静态数组,未初始化,存储在BSS段

代码段

  • 如果数组是用 const 修饰的,并且是只读数据(常量数组),通常存放在 代码段的只读区 或 常量区。
  • 这些数组是只读的,不能被修改。
const char str[] = "Hello, world!";  // 字符常量数组,可能存储在代码段或常量区

总结

局部数组(自动分配)

动态分配的数组

全局/静态数组(已初始化)

数据段(已初始化区)

全局/静态数组(未初始化)

数据段(BSS段)

const

修饰的只读数组

代码段或常量区

2.请你介绍一下c/c++中变量的作用域都有哪些?

块作用域

  • 变量在某个代码块 {} 内声明,只在该代码块中可见,出了代码块就无法访问。
  • 代码块内的变量是局部变量。
  • 代码块外无法访问代码块内的变量。
  • 变量作用域以 { 开始,以 } 结束。
  • 在函数、循环、条件语句等代码块中定义的变量。

例子:

void example() {
    int x = 10;  // x 的作用域从这里开始
    {
        int y = 20;  // y 的作用域从这里开始
        printf("x = %d, y = %d\n", x, y);
    }
    // printf("%d\n", y);  // 错误,y 超出作用域
}

函数作用域

  • 函数作用域表示变量仅在函数内可见。变量在函数内部声明,只能在同一函数内访问。函数作用域的变量在函数执行时创建,在函数结束时销毁。

例子:

void func1() {
    int x = 10;  // x 的作用域只在 func1 内
}

void func2() {
    int y = 20;  // y 的作用域只在 func2 内
    // printf("%d\n", x);  // 错误,x 不在作用域内
}

全局作用域

  • 全局作用域表示变量在整个程序中都可见。在任何函数或代码块之外声明的变量都具有全局作用域。这些变量在程序开始执行时创建,在程序结束时销毁。

例子:

#include <iostream>
int x = 10; // 全局变量
 
int main() {
    std::cout << x << std::endl; // 输出:10
    return 0;
}


在上面的例子中,变量x在main函数中被访问,因为它具有全局作用域。

类作用域

  • 在类或结构体中声明的变量,其作用域限制在类的范围内。
  • 成员变量和成员函数都属于类作用域。
  • 私有成员(private)只能在类内部访问。
  • 公共成员(public)可以通过类对象访问。
  • 使用 :: 作用域解析符可以访问静态成员变量或成员函数。

例子:

class MyClass {
private:
    int private_var;  // 私有成员,仅在类内部可见

public:
    int public_var;   // 公有成员,可以通过对象访问

    void display() {
        private_var = 10;  // 类内访问私有成员
        std::cout << "Private Var: " << private_var << std::endl;
    }
};

int main() {
    MyClass obj;
    obj.public_var = 20;  // 可以访问公有成员
    // obj.private_var = 10;  // 错误,私有成员不可直接访问
}

命名空间作用域

  • 在命名空间中定义的变量,其作用域限制在命名空间内。
  • 通过 namespace 定义。
  • 命名空间中的变量和函数不会与其他命名空间冲突。
  • 可以通过 using namespacenamespace::name 来访问。

例子:

namespace MyNamespace {
    int x = 10;  // 命名空间作用域
}

int main() {
    std::cout << MyNamespace::x << std::endl;  // 使用作用域解析符
    return 0;
}

函数参数的作用域

  • 函数参数也具有自己的作用域,它们在函数内部有效。函数参数作用域指的是这些参数在整个函数内部可见。

例子:

int x = 10;  // 全局变量

void func(int x) {  // 函数参数
    printf("%d\n", x);  // 输出函数参数 x 的值
}

int main() {
    func(20);  // 输出 20
    return 0;
}

动态作用域

  • 动态分配的变量(如 mallocnew)在内存堆中分配,不受函数或代码块的限制,但需要手动释放。
  • 动态分配的内存只要不释放,可以在程序中任何地方访问。
  • 通常需要使用指针管理。
  • 不释放会导致内存泄漏。

例子:

int* allocate() {
    int* ptr = (int*)malloc(sizeof(int));  // 动态分配内存
    *ptr = 10;
    return ptr;
}

int main() {
    int* p = allocate();
    printf("%d\n", *p);  // 输出 10
    free(p);  // 释放内存
    return 0;
}

3.请问sizeof与strlen的区别,简单说说?

sizeof

  • 定义: 用于获取数据类型或对象的内存大小(单位是字节)。
  • 编译器在编译阶段直接计算,不会在运行时执行。
  • 计算包括结构体、数组(静态分配)、基本数据类型等所占用的完整内存大小。
  • 对于数组,它返回数组的总大小,而不是元素数量。

语法:

sizeof(expression or type)

strlen

  • 定义: 用于计算以 '\0' 结尾的字符串的长度(不包括末尾的 '\0')。
  • 只能用于以 '\0' 结尾的字符串。
  • 计算时从起始地址开始遍历字符,直到遇到 '\0',因此是运行时操作。
  • 不适用于非字符串数据。

语法:

strlen(const char *str)

sizeof

  • 获取类型大小:
  • 对静态数组:
  • 对动态分配的指针:

strlen

  • 获取字符串长度:
  • 使用在指针字符串上:

总结

用途

用于计算数据类型或变量的内存大小。

用于计算以 '\0'结尾字符串的长度。

运行时间

编译时确定(静态)。

运行时计算(动态)。

作用对象

任意类型,包括数组、指针、结构体等。

仅适用于 C 风格字符串(以 '\0'结尾)。

单位

返回字节数。

返回字符串中的字符数(不包含 '\0')。

数组的行为

对数组返回整个数组的大小(单位是节)。

对字符串数组返回实际字符串的长度。

例子

sizeofstrlen 结合使用

#include <stdio.h>
#include <string.h>

int main() {
    char arr[] = "Hello, World!";
    char *ptr = "Hello, World!";
    
    printf("sizeof(arr): %zu\n", sizeof(arr));   // 包括 '\0',输出14
    printf("strlen(arr): %zu\n", strlen(arr));   // 不包括 '\0',输出13
    
    printf("sizeof(ptr): %zu\n", sizeof(ptr));   // 指针大小,输出8(假设指针是8字节)
    printf("strlen(ptr): %zu\n", strlen(ptr));   // 指向字符串的长度,输出13
    
    return 0;
}

输出:

sizeof(arr): 14
strlen(arr): 13
sizeof(ptr): 8
strlen(ptr): 13

4.简述C++有几种传值方式之间的区别?

值传递

  • 将实参的值复制一份传递给形参。
  • 形参和实参是两个不同的变量,形参的修改不会影响实参。

例子:

void func(int x) {
    x = 10; // 只修改了形参,不会影响实参
}

int main() {
    int a = 5;
    func(a);
    // a 仍然是 5
    return 0;
}

指针传递

  • 将实参的地址传递给形参(指针)。
  • 通过形参指针可以访问和修改实参的值。

例子:

void func(int *x) {
    *x = 10; // 修改了实参的值
}

int main() {
    int a = 5;
    func(&a); // 传递 a 的地址
    // a 变为 10
    return 0;
}

引用传递

  • 将实参的引用传递给形参。
  • 实参和形参共享同一块内存,修改形参会直接影响实参。

例子:

void func(int &x) {
    x = 10; // 修改了实参的值
}

int main() {
    int a = 5;
    func(a); // 传递 a 的引用
    // a 变为 10
    return 0;
}

总结

值传递

复制实参值,修改形参不影响实参。

基本数据类型,小型数据,不需要修改实参。

指针传递

传递地址,允许修改实参,支持空指针,使用较复杂。

需要修改实参或传递大数据,灵活但需注意安全性。

引用传递

实参和形参共享,允许修改实参,语法简单,不能传 nullptr

需要修改实参,代码简洁,适合对对象的操作。

5.指针用的多不,请问数组指针与指针数组的区别?

数组指针

  • 数组指针是指向数组的指针,它指向的是整个数组的首地址。其定义方式通常是:类型 (*指针名)[数组大小]

特点:

  • 一个数组指针可以指向一个特定大小的数组。
  • 通过数组指针,可以遍历整个数组。

例子:

int arr[5] = {1, 2, 3, 4, 5};   // 定义一个数组
int (*p)[5] = &arr;             // 定义一个数组指针,指向数组 arr

// 使用数组指针访问数组元素
for (int i = 0; i < 5; ++i) {
    std::cout << (*p)[i] << " "; // 通过数组指针解引用访问元素
}

  • p 是一个指针,指向一个 int 类型的数组,该数组有 5 个元素。
  • *p 解引用后就是数组本身,(*p)[i] 就是数组中的第 i 个元素。

指针数组

  • 指针数组是一个数组,数组的每个元素是一个指针。定义方式通常是:类型 *数组名[数组大小]

特点:

  • 一个指针数组包含多个指针,可以分别指向不同的对象或变量。
  • 指针数组的每个元素可以存储一个地址。

例子:

int a = 10, b = 20, c = 30;
int *arr[3] = {&a, &b, &c};     // 定义一个指针数组,每个元素是指向 int 的指针

// 使用指针数组访问变量的值
for (int i = 0; i < 3; ++i) {
    std::cout << *arr[i] << " "; // 解引用指针,获取值
}

  • arr 是一个数组,数组中存放的是指向 int 类型变量的指针。
  • arr[i] 表示数组中第 i 个指针,*arr[i] 表示该指针指向的变量的值。

对比

数组指针的函数传参:

void func(int (*p)[5]) {
    for (int i = 0; i < 5; ++i) {
        std::cout << (*p)[i] << " ";
    }
}

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    func(&arr);  // 将数组的地址传递给数组指针
    return 0;
}

指针数组操作字符串:

int main() {
    const char *strs[3] = {"Hello", "World", "C++"}; // 指针数组,存储字符串地址
    for (int i = 0; i < 3; ++i) {
        std::cout << strs[i] << std::endl; // 打印每个字符串
    }
    return 0;
}

区别总结

定义

int (*p)[N]p 是指向数组的指针。

int *arr[N]arr 是一个指针数组。

含义

指针指向一个特定大小的数组。

数组的每个元素是一个指针,指向不同的对象或变量。

用途

操作整个数组。

操作多个指针,每个指针可以指向不同的变量或数组。

内存结构

单个指针指向数组的首地址。

数组中存放多个指针,每个指针占用一块内存。

示例

int (*p)[5] = &arr;

int *arr[3] = {&a, &b, &c};

访问方式

(*p)[i],通过解引用访问数组元素。

*arr[i],访问每个指针指向的内容。

实际作用

用于函数参数传递,操作整个数组。

用于存储多个指针,可以灵活管理多个对象。

6.请问NULL和nullptr区别?

NULL

  • 通常,NULL 被定义为整数值 0
#define NULL 0

  • 所以说NULL实际上是一个空指针,如果在C语言中写入以下代码,编译是没有问题的,因为在C语言中把空指针赋给int和char指针的时候,发生了隐式类型转换,把void指针转换成了相应类型的指针。
int  *pi = NULL;


char *pc = NULL;

  • 问题:由于 NULL 是整数常量 0,在C++中重载函数中容易导致歧义。可能引发隐式类型转换错误。
void func(int);
void func(char*);

func(NULL);  // 可能调用 func(int),而不是 func(char*)

nullptr

  • nullptr 是 C++11 引入的新关键字,用于表示空指针。其实本质nullptr 是一种特殊的类型 std::nullptr_t,专门设计用于表示指针类型的空值。最重要的是nullptr 只能用于指针,而不能隐式转换为整数类型。
void func(int);
void func(char*);

func(nullptr);  // 明确调用 func(char*)

  • 不会引发因类型不匹配导致的歧义。
  • nullptr 可以隐式转换为任何指针类型,但不能转换为整数。
int* p = nullptr;    // 正确
int val = nullptr;   // 错误:不能将 nullptr 转换为整数

#秋招##嵌入式##面经##牛客激励计划##春招#

嵌入式/C++八股 文章被收录于专栏

本人双飞本,校招上岸广和通。此专栏覆盖嵌入式常见面试题,有C/C++相关的知识,数据结构和算法也有嵌入式相关的知识,如操作系统、网络协议、硬件知识。本人也是校招过来的,大家底子甚至项目,可能都不错,但是在面试准备中常见八股可能准备不全。此专栏很适合新手学习基础也适合大佬备战复习,比较全面。最终希望各位友友们早日拿到心仪offer。也希望大家点点赞,收藏,送送小花。这是对我的肯定和鼓励。 持续更新中

全部评论

相关推荐

头像
12-26 09:11
已编辑
北京大学 嵌入式软件开发
1.&nbsp;C++中的引用和指针有什么区别?2.&nbsp;什么是&nbsp;RAII?它有什么好处?3.&nbsp;C++11引入的nullptr有什么作用?4.&nbsp;如何实现一个单例模式?5.&nbsp;C++中的const关键字有什么用?如何使用?6.&nbsp;什么是内存对齐?为什么需要内存对齐?7.&nbsp;C++中的虚表和虚指针是什么?8.&nbsp;解释一下深拷贝和浅拷贝的区别。9.&nbsp;如何使用std::shared_ptr和std::unique_ptr管理动态内存?10.&nbsp;什么是函数模板和类模板?它们有什么区别?11.&nbsp;C++中的友元函数和友元类有什么作用?12.&nbsp;解释一下C++中的多重继承及其问题。13.&nbsp;C++中的类型转换有哪些方式?分别是什么?14.&nbsp;什么是模板特化?如何使用?15.&nbsp;C++中的异常处理机制是怎样的?16.&nbsp;什么是&nbsp;lambda&nbsp;表达式?它的用途是什么?17.&nbsp;C++中的std::move和std::forward的区别是什么?18.&nbsp;解释一下C++中的析构函数的作用和使用场景。19.&nbsp;C++中的标准库容器有哪些?它们的特点是什么?20.&nbsp;什么是C++中的命名空间?它有什么作用?21.&nbsp;C++中的类型推导是什么?如何使用auto关键字?22.&nbsp;C++中的static关键字有什么作用?23.&nbsp;C++中的虚函数表是如何工作的?24.&nbsp;解释一下C++中的内存泄漏及其解决方法。25.&nbsp;C++中的std::thread是如何实现的?26.&nbsp;什么是条件变量?它在多线程中有什么作用?27.&nbsp;解释一下C++中的编译期和运行期多态。28.&nbsp;C++中如何实现运算符重载?29.&nbsp;C++中的std::pair和std::tuple有什么区别?30.&nbsp;什么是C++中的智能指针?它们的类型和使用场景是什么?答案附在面经中&nbsp;&nbsp; http://daxprogram.com
点赞 评论 收藏
分享
评论
5
8
分享
牛客网
牛客企业服务