面试复习:C和C++常见问题梳理汇总

1 C/C++常见问题

1.1 关键字

1.1.1 volatile

volatile中文意思是易变的
作用:防止编译器对变量进行优化,即每次存取该变量的值的时候都要去内存存取,而不是使用它缓存在寄存器中的值。

什么是编译器优化?
首先理解CPU(寄存器)的读取规则:

c
int a, b; // 为 a 和 b 申请内存
a = 1; // 1 -> CPU
// CPU -> 内存(&a)
b = a; // 内存(&a) -> CPU
// CPU -> 内存(&b)

编译器优化:

C
int a = 1, b, c; // 为 a, b, c 申请内存, a 初始化为1
b = a; // 内存(&a) -> CPU
// CPU -> 内存(&b)
c = a; // 优化:减少这步操作 *内存(&a) -> CPU*
// CPU -> 内存(&c)

在b = a这个语句中,a已经被移入过寄存器(CPU),那么在执行c = a时,就直接将a在寄存器(CPU)中传递给c这样就减少了一次指令的执行,就完成了优化。

volatile的引入:
上边程序中,如果在执行完b = a后,a此时的值存放在CPU中但是a在内存中又发生了变化(比如中断改变了a的值),但是存在CPU中的a是原来未变的a,按理应该是已经变化后的a赋值给c,但是此时却导致未变化的a赋值给了c。

这种问题,就是编译器自身优化而导致的。为了防止编译器优化变量a,引入了volatile关键字,使用该关键字后,程序在执行时c = a时,就会先去a的地址读出a到CPU,再从CPU将a的值赋予给c这样就防止了被优化。

使用volatile的情况:

  • 在与外部设备通信时,通常使用内存映射的方式将设备寄存器与某些内存地址关联。这些地址上的值可能会随着设备的状态改变而变化,因此需要使用 volatile 关键字防止编译器对这些地址的优化操作。
  • 在多线程编程中,一个线程可能会修改某个全局变量,而其他线程会检查该变量的变化。如果不使用 volatile,编译器可能会优化掉对该变量的重复读取,而这可能导致线程读取到错误的值。
  • 在嵌入式开发中,中断服务程序(ISR)可能会修改全局变量,而主程序会使用该变量。如果不使用 volatile,编译器可能会假设该变量不会发生变化,进而优化代码。

常见问题:
问:一个参数既可以是const还可以是volatile吗?
答:可以的,例如只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

问:一个指针可以是volatile 吗?
答:可以。volatile 关键字可以用来修饰指针本身,也可以修饰指针所指向的数据。一个例子是当一个中断服务子程序修改一个指向一个buffer的指针时,必须用volatile来修饰指针。

int * volatile ptr; //指针的地址可能会随时改变,所以每次使用 ptr 时,编译器都会重新读取它的值,而不是假设它不变。
volatile int *ptr; //内存中的数据可能会随时被改变
volatile int * volatile ptr; //既希望指针本身以及它指向的值都是 volatile

问:找错误

int square(volatile int *ptr){
    return *ptr * *ptr;
}

该程序的目的是用来返回指针 *ptr 指向值的平方,但是,由于 *ptr 指向一个volatile型参数,编译器将产生类似下面的代码:

int square(volatile int *ptr){
    int a, b;
    a = *ptr;
    b = *ptr;
    return a * b;
}

由于 *ptr 的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:

long square(volatile int *ptr){
    int a;
    a = *ptr;
    return a * a;
}

注意:频繁地使用volatile很可能会增加代码尺寸和降低性能,因此要合理的使用volatile。

1.1.2 static

问题:static关键词的作用?
static是被声明为静态类型的变量,赋初值存储在data段,不赋初值储存在bss段,自动化初始为0。其生命周期为整个程序。
当在函数内部修饰局部变量时:该变量的生命周期被扩展为整个程序的运行期间,而不仅仅是函数调用期间。这意味着变量的值在函数多次调用之间会被保留,每次调用时不会重新初始化。(延长生命周期)

void func() {
    static int count = 0;
    count++;
    printf("%d\n", count);
}
//每次调用 func() 时,count  的值会保留并累加,而不是每次调用时重新初始化为 0。

当在文件或全局作用域修饰全局变量或函数时:该变量或函数的作用域被限制在当前源文件内,无法被其他文件访问。(限制作用域)

static int global_var = 100;
static void helper_func() {
    // 只在当前文件中可见
}

当在类成员中(C++)中:可以用来修饰类的成员变量或成员函数。修饰为 static 的成员属于整个类,而不是某个特定的对象。这意味着所有对象共享同一个 static 成员变量或函数。

class MyClass {
public:
    static int counter;
    static void increment() {
        counter++;
    }
};

int MyClass::counter = 0;

问题:为什么 static变量只初始化一次?
static 变量只初始化一次是由于它的生命周期贯穿整个程序。
对于所有的对象(不仅仅是静态对象),初始化都只有一次,而由于静态变量具有“记忆”功能,初始化后,一直都没有被销毁,都会保存在内存区域中,所以不会再次初始化存放在静态区的变量的生命周期一般比较长,它与整个程序“同生死、共存亡”,所以它只需初始化一次而auto变量,即自动变量,由于它存放在栈区,一旦函数调用结束,就会立刻被销毁。

1.1.3 const

作用:
修饰变量(全局或者局部)为只读变量。(必须初始化)

const int *ptr; //修饰指向的变量
int const *ptr; //修饰指向的变量
int *const ptr; //修饰指针,指针指向的变量可以变
const int *const ptr; //都不能变

修饰函数参数,在函数体内不能修改这个参数的值。

void foo(const int *ptr) {
    *ptr = 10;  // 错误,不能修改
}

修饰函数,返回值类型为指针,指针的内容是不能被修改的,而且这个返回值只能赋给被const修饰的指针。

const char* aaa() {
    return "Hello";  // 返回一个指向字符串常量的指针
}

int main() {
    char *str = aaa();        // 错误,不能将 const char* 赋值给 char*
    const char *str2 = aaa(); // 正确,返回值只能赋给 const char* 类型
    return 0;
}

问题:什么是常量指针?
const int *ptr;不能通过这个指针来改变变量的值。但是可以通过其他方式改变,比如直接赋值。
指向的值不能改变,但是指向的地址可以改变。

问题:什么是指针常量?
int *const ptr;指的是指针不能在指向其他地址,但是指向的变量可以改变。

问题:什么是指向常量的常指针?
const int *const ptr;指针指向的位置不能改变并且也不能通过这个指针改变变量的值,但是依然可以通过其他的普通指针改变变量的值。

问题:const 和 #define 的区别?
#define是预处理阶段进行简单的文本替换,不会进行类型检查。
const是编译时常量,具有类型安全性。

1.1.4 typdef 和 define

区别:
#define 预处理指令,用于定义宏。在预处理阶段进行简单的文本替换,容易引发错误,没有类型检查。
typdef 用来为现有的类型起一个别名。编译时起作用。回进行语法检查,保证类型的正确性。
#define没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用,而 typedef有自己的作用域。

1.2 变量、数组、指针

1.2.1 变量

问题:定义常量谁更好?#define还是 const?
const更好,有类型检查,更安全,作用域可控。#define只是在预处理阶段进行简单的文本替换。

全局变量和局部变量的区别是什么?
全局变量的作用域作用在整个程序,局部变量作用域为当前函数。全局变量存储在data段或bss段,局部变量分配在栈上。全局变量生命周期贯穿整个程序,局部变量随着函数的退出而结束。

全局变量可不可以定义在可被多个.C文件包含的头文件中?
可以,通过extern声明,即只在一个 .c 文件中定义全局变量,而在其他文件通过 extern 进行引用。

static 限定符可以用来限制全局变量的作用域,使其仅在定义它的 .c 文件中可见。通过在不同 .c 文件中使用 static 修饰符,可以定义多个同名的全局变量而不会冲突。但这每个文件里的变量是独立的,互不影响,作用域仅限于各自的 .c 文件。

局部变量能否和全局变量重名?
能,局部变量会屏蔽全局变量。局部变量的作用域结束后,全局变量才会再次生效。

int x = 10;  // 全局变量

void myFunction() {
    int x = 20;  // 局部变量,与全局变量同名
    printf("%d\n", x);  // 输出 20,因为局部变量 x 屏蔽了全局变量 x
}

int main() {
    myFunction();
    printf("%d\n", x);  // 输出 10,此时访问的是全局变量 x
    return 0;
}

1.2.2 数组

数组指针:
即指向数组的指针。

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

// 访问数组元素:
printf("%d\n", (*p)[0]);       // 输出 1
printf("%d\n", (*p)[2]);       // 输出 3

arr 表示数组的首地址(即 \&arr[0]),类型是 int*。
\&arr 表示整个数组的地址,类型是 int (*)[5],也就是指向包含 5 个整数的数组的指针。

题:

#include <stdio.h>
#include <stdlib.h>
void main(){
    int b[12]={1,2,3,4,5,6,7,8,9,10,11,12};
    int(*p)[4];
    p = b;
    printf("%d\n", **(++p));
}
//int (*)[4] -> int[4] -> int

p是一个数组指针int (*)[4],指向包含4个int类型元素的数组,指向b的首地址,++p相当于p所指地址向后移动了4个int。p 指向下一个 4 元素的数组块(即 b[4] 到 b[7]), *(++p)获取这个数组块 {5, 6, 7, 8} 的指针即int[4], **(++p)返回下一个 4 个元素数组中的第一个元素即为5,(int)
指针数组:
即是一个数组,里面包含的元素是指针。

int a = 1, b = 2, c = 3;
int *arr[3] = {&a, &b, &c};  // 定义一个指针数组,每个元素指向一个整数

// 访问指针数组的元素:
printf("%d\n", *arr[0]);     // 输出 1
printf("%d\n", *arr[1]);     // 输出 2

数组下标可以为负数吗?
可以,下标只是给出了一个与当前地址的偏移量。语法上不报错,但是是危险的。

1.2.3 指针

函数指针:
在程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址,而且函数名表示的就是这个地址。既然是地址就可以定义一个指针变量来存放,这个指针变量就叫作函数指针变量,简称函数指针。(函数名就是地址)
定义:

int (*p)(int, int);

定义了一个指向函数的指针变量 p。首先它是一个指针变量,所以要有一个“*”,即 (*p);
其次前面的 int 表示这个指针变量可以指向返回值类型为 int 型的函数;
后面括号中的两个 int 表示这个 指针变量可以指向有两个参数且都是 int 型的函数。
所以合起来这个语句的意思就是:定义了一个指针变量 p,该指针变量可以指向返回值类型为 int 型,且有两个整型参数的函数。
p 的类型为 int(*) (int,int)

函数指针的定义就是将“函数声明”中的“函数名”改成“(指针变量名)”但是这里需要注意的是:“(指针变量名)”两端的括号不能省略,括号改变了运算符的优先级如果省略了括号,就不是定义函数指针而是一个函数声明了,即声明了一个返回值类型为指针型的函数

重要:最后需要注意的是,指向函数的指针变量没有 ++ 和 — 运算

#include <stdio.h>
int Max(int, int); //函数声明

int main()
{
    int (*p)(int, int);
    int a, b, c;
    p = Max;
    //
    c = (*p)(a, b);
    //
}

指针函数:
是一个函数,返回的是一个地址。必须用同类型的指针进行接收返回值。

#include <stdio.h>

// 定义一个返回指向整数的指针的函数
int* getMax(int *a, int *b) {
    // 返回较大数的地址
    if (*a > *b)
        return a;  // 返回 a 的地址
    else
        return b;  // 返回 b 的地址
}

int main() {
    int x = 10, y = 20;

    // 调用返回指针的函数
    int *max = getMax(&x, &y);

    printf("The larger value is: %d\n", *max);  // 使用返回的指针来访问值

    return 0;
}

问题:数组和指针的区别与联系是什么?
区别:
1. 存储方式:
– 数组:是多个相同类型元素组成的集合,在内存中是连续存储的,通常存储在静态存储区或栈上。
– 指针:存储的是某个变量地址的变量,存储位置不固定。可以指向数组的首元素。
2. sizeof的使用:
– 数组:sizeof(数组名) 返回的是数组所占用的内存大小。对于一个数组 arr,sizeof(arr) 会返回整个数组的字节大小。
– 指针:sizeof(指针名) 返回的是指针本身的大小,而不是指针所指向的数据。32 位系统下,指针大小为 4 字节,64 位系统下,指针大小为 8 字节,无论指针指向的数据类型是什么。
3. 数据访问方式
– 数组:数组的访问是直接访问,可以通过下标(如 arr[0])或通过数组名加偏移量(如 (arr + 0))访问特定的元素。arr <== >&arr[0] (&arr[0] + 0)) == arr[0]
– 指针:指针的访问是间接访问,需要使用解引用操作符 * 来访问指向的内容(如 ptr)。
4. 使用场景
– 数组:多用于存储
固定大小的数据集合,例如固定长度的数组。数组元素类型统一且内存分配是静态的。
– 指针:常用于处理
动态数据结构*,例如链表、动态数组等。指针可以指向动态分配的内存,也可以用于函数参数传递来减少内存拷贝。

联系:
– 虽然数组和指针有显著的区别,但它们之间有密切联系。在很多情况下,数组名可以隐式地转换为指向数组首元素的指针(即 arr 和 &arr[0] 是等价的),但是数组名并不是一个普通的指针,是常量指针,它不可以被重新赋值修改,而指针则可以。

问题:指针常量,常量指针,指向常量的常量指针有什么区别?
1. 指针常量 int *const p
– 指针指向的地址不可以修改,但是这个地址上存储的值可已修改。
2. 常量指针 const int *p int const *p
– 不可以通过这个指针修改地址上存储的值,但是可以改变指针的指向。
3. 指向常量的常量指针 const int *const p
– 既不可以修改指针的值,也不可以修改指针指向的值

问题:指针和引用的异同是什么?如何相互转换?
1. 相同:
– 都是地址概念,指针指向某一内存、内容是所指内存的地址;引用则是某块内存的别名。
– 从内存分配上看:两者都占内存,程序为指针会分配内存,一般是8个字节;而引用的本质是指针常量,指向对象不能变,但指向对象的值可以变,两者都是地址概念,所以本身都会占用内存。
2.区别:
– 指针是实体,引用是别名。
– 指针和引用的自增(++)运算符意义不同,指针是对内存地址自增,引用是对值的自增。
– 引用使用时无需解引用(),指针语言解引用。
– 引用只能在定义时被初始化一次,之后不可变;指针可变。
– 引用不能为空,指针可以为空。
– “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小。
3. 转换:
– 指针转引用:把指针用
就可以转换成对象,可以用在引用参数当中。
– 引用转指针:把引用类型的对象用&取地址就获得指针了。

问题:野指针是什么?
指那些指向无效或未定义内存地址的指针。
1.未初始化的指针变量在声明时未被赋予有效的内存地址,默认情况下指向一个随机的内存位置。

int *ptr; // 未初始化
*ptr = 10; // 未定义行为,可能导致程序崩溃

2.当动态分配的内存被释放(如使用free或delete),但指针仍然指向该内存地址,此时指针成为野指针。(释放了没有置NULL)

int *ptr = (int *)malloc(sizeof(int));
*ptr = 5;
free(ptr);
// ptr 现在是一个野指针
*ptr = 10; // 未定义行为

3.局部变量在函数或代码块结束后被销毁,如果指针仍然指向该局部变量的地址,就会成为野指针。

int* getPointer() {
    int local = 42;
    return &local; // 返回指向局部变量的指针
}

int main() {
    int *ptr = getPointer();
    // ptr 现在指向的内存已被释放
    printf("%d\n", *ptr); // 未定义行为
    return 0;
}

4.错误的指针运算可能导致指针指向非法的内存地址,从而形成野指针。(越界)

int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr;
ptr += 10; // 超出数组范围,ptr 现在指向非法内存
printf("%d\n", *ptr); // 未定义行为

5.同一个指针被多次释放会导致第一次释放后指针成为野指针,第二次释放时操作的是一个已经释放的内存地址。

int *ptr = (int *)malloc(sizeof(int));
free(ptr);
free(ptr); // 第二次释放,ptr 已是野指针

6.不正确的类型转换可能导致指针指向不正确的内存地址,形成野指针。

int a = 10;
double *ptr = (double *)&a; // 错误的类型转换
*ptr = 20.5; // 未定义行为

7.在使用指针之前没有检查其是否指向有效的内存地址,可能导致操作野指针。

int *ptr = NULL;
*ptr = 10; // 尝试解引用空指针,导致崩溃

问题:如何避免野指针?
1.初始化指针:声明指针时尽量初始化为NULL或有效的内存地址。

//将指针初始化为NULL
int *p = NULL;
//malloc分配内存
int *p = (int *)malloc(sizeof(int));
//使用合法的内存地址对指针进行初始化
char num[30] = {0};
char *p = num;

2.及时置空:在释放指针后,将其置为NULL,避免再次使用。

free(ptr);
ptr = NULL;

注:malloc函数分配完内存后需注意:
1.检查是否分配成功(若分配成功,返回内存的首地址;分配不成功,返回NULL。可以通过if语句来判断)
2.清空内存中的数据(malloc分配的空间里可能存在垃圾值,用memset或bzero函数清空内存)

问题:C++中的智能指针是什么?
智能指针是一个类,用来存储指针(指向动态分配对象的指针)。
C++程序设计中使用堆内存是非常频繁的操作,堆内存的申请和释放都由程序员自己管理程序员自己管理堆内存可以提高了程序的效率,但是整体来说堆内存的管理是麻烦的,C++11中引入了智能指针的概念,方便管理堆内存使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常时内存泄露等问题等,使用智能指针能更好的管理堆内存。

问题:this指针是什么?
this 指针是 C++ 中一个特殊的指针,它在类的成员函数内部隐式存在,用于指向调用该成员函数的对象本身。通过 this 指针,成员函数可以访问调用它的对象的成员变量和成员函数。
1.this指针指向当前对象,可以访问当前对象的所有成员变量包括private、protected、public
2.this指针是const指针,一切企图修改该指针的操作,如赋值(改变指向)、增减都是不允许的!
3.this指针只有在成员函数中才有定义因此,在创建一个对象后,也不能通过对象使用this指针所以,我们也无法知道一个对象的this指针的位置(只有在成员函数里才有this指针的位置)当然,在成员函数里,你是可以知道this指针的位置的(可以&this获得),也可以直接使用的
4.只有创建对象后,this指针才有意义
5.static静态成员函数不能使用this指针原因静态成员函数属于类,而不属于某个对象,所以static静态成员函数压根就没有this指针
6.this在成员函数的开始执行前构造的,在成员函数的执行结束后清除至于如何清除的,由编译器实现,程序员不关心this是通过函数参数的首参数来传递

1.3 内存

问题:C语言中内存分配的方式有几种?
三种,分别是静态内存分配、栈内存分配、动态内存分配。
1.静态内存分配
全局变量:在所有函数之外声明的变量,存储在静态存储区(数据段)中。它们在程序开始时分配内存,直到程序结束时释放。
静态变量:使用static关键字在函数内部或外部声明的变量。它们的生命周期与全局变量相同,但作用域受限于声明的位置。局部静态变量:仅限于声明它的函数内部。全局静态变量:仅限于声明它的源文件内部。
特点:
生命周期:整个程序运行期间都存在。
作用域:全局变量的作用域为整个程序,静态变量的作用域受限于声明的位置。
初始化:如果未显式初始化,静态变量和全局变量会被自动初始化为零。

2.栈内存分配
局部变量:在函数内部声明的变量,存储在栈上。它们在函数调用时分配内存,函数返回时释放。
函数参数:函数参数也存储在栈上,类似于局部变量。
特点:
生命周期:随着函数的调用和返回而自动管理。
分配和释放速度快:因为是基于栈的结构,分配和释放内存的速度非常快。
空间有限:栈的大小通常较小,过多或过大的局部变量可能导致栈溢出(Stack Overflow)。

3.动态内存分配
动态内存分配允许在程序运行时根据需要分配和释放内存,存储在堆(Heap)上。这种方式提供了更大的灵活性,但需要程序员手动管理内存,以避免内存泄漏和其他问题。

问题:堆与栈有什么区别?
申请方式:
栈空间由系统主动分配和释放,堆空间需要手动分配和释放。
内存大小限制:
栈比较小,堆相对大。
申请效率:
栈效率高,堆速度慢且容易产生内存碎片,但用起来方便。

问题:C语言函数参数压栈顺序是怎样的?
从右至左

问题:什么是内存泄漏?
指在程序运行过程中,动态分配的内存由于某种原因未被正确释放,导致这部分内存无法再被使用或访问。
主要原因:
1.忘记释放内存
在动态分配内存后,程序员忘记调用free或delete释放内存,导致内存无法被重新利用。
2.覆盖指针导致无法访问已分配内存
当指针被重新赋值,原先指向的内存地址无法被访问,从而无法释放这部分内存。
3.循环中重复分配内存但未释放
在循环中不断分配内存而不释放,导致内存不断增长。

问题:new/delete与malloc/free的区别是什么?
new的底层就是用malloc实现的。

malloc如果申请空间失败返回NULL,new如果申请空间失败抛出异常 bad_alloc();
new就是按类型开辟空间的,而malloc是按字节为单位开辟空间的。
new/delete 是C++的关键字,malloc/free是库函数。
new可以在分配空间的同时进行初始化,malloc不可以。
当new分配的空间以int pc = new int();,这种形式的时候,如果没有初始化,将用0进行初始化。如果是以int pc = new int形式出现的,则将是随机值。
malloc分配的空间如果没有初始化,是随机值,需要手动调用memset或者bzero函数来清空。
new会根据类型自动计算分配空间的大小,malloc需要手动sizeof计算,传参。
new是要什么类型,就返回什么类型的指针,malloc返回void *,需要进行强转。

对自定义类型来讲:
new 不仅开辟空间,还会调用自定类中的构造函数,malloc不会。
delete 不仅释放空间,还会在释放空间前先调用类中析构函数,free不会。

1.4 预处理

问题:预处理器标识#error的目的是什么?
#error预处理指令的作用是,编译程序时,只要遇到#error就会生成一个编译错误提示消息,并停止编译其语法格式为:#error "error-message"

//序中往往有很多的预处理指令
#ifdef XXX
...
#else
#endif

//当程序比较大时,往往有些宏定义是在外部指定的(如makefile),或是在系统头文件中指定的,当你 不太确定当前是否定义了 XXX 时,就可以改成如下这样进行编译:
#ifdef XXX
...
#error "XXX has been defined"
#else
#endif

//如果编译时出现错误,输出了XXX has been defined,表明宏XXX已经被定义了

问题:如何使用 define声明个常数,用以表明1年中有多少秒(忽略闰年问题)

define YEAR_SECOND (365*24*60*60)UL

考虑到可能存在数据溢出问题,更加规范化的写法是使用长整型类型,即UL类型,告诉编译器这个常数是长整型数

问题:#include和#include” filename. h”有什么区别?
<>编译器优先从标准库路径开始搜索,使系统文件调用更快。””优先从用户工作路径开始搜索,在去系统路径,使自定义文件较快。

问题:头文件的作用有哪些?
通过头文件调用库功能,使源代码保密。
加强类型检测,当使用接口时如果与头文件中声明的不一致,编译器会报错。

问题:在头文件中定义静态变量是否可行,为什么?
技术上可行:在头文件中定义静态变量是允许的,C编译器不会阻止这种做法。然而,这种做法通常不推荐,因为它会导致每个包含该头文件的源文件(翻译单元)都拥有独立的静态变量副本。

问题:写一个”标准”宏MIN ,这个宏输入两个参数并返回较小的一个。

#define MIN ((A) < (B) ? (A) : (B))

1.5 其他

问题:C语言宏中“#”和“##”的用法
#作用:#可以把一个宏参数直接转换成相应的字符串比如有下面这个宏:

#define CONVERT(a) #a

int test_convert = 10;
printf(CONVERT(test_convert));

等价于:
int test_convert = 10;
printf("test_convert");

##作用:将宏定义的多个形参转换成一个实际参数名

#define CAT(a, b) a##b

int num5 = 10;
printf("%d\n", CAT(num, 5);

等价于:
int num = 10;
printf("%d\n", num5);

问题:extern”C”的作用是什么?
在 C++ 中使用 extern “C” 可以告诉编译器按照 C 的方式对函数名进行链接,避免名称修饰。这使得 C++ 代码能够与 C 代码或使用 C 链接约定的库进行互操作。

// c_library.h
#ifndef C_LIBRARY_H
#define C_LIBRARY_H

#ifdef __cplusplus //检查是否是c++
extern "C" {
#endif

void c_function();

#ifdef __cplusplus
}
#endif

#endif // C_LIBRARY_H

问题:strlen(“\0”) =? sizeof(“\0”)=? 两者结果与区别
strlen(“\0”) = 0,sizeof(“\0”) = 2 <==> /0, /0
strlen用来计算字符串的长度(在C/C++中,字符串是以”\0″作为结束符的),它从内存的某个位置(可以是字符串开头,中间某个位置,甚至是某个不确定的内存区域)开始扫描直到碰到第一个字符串结束符\0为止,然后返回计数器值

sizeof是C语言的关键字,它以字节的形式给出了其操作数的存储大小,操作数可以是一个表达式或括在括号内的类型名,操作数的存储大小由操作数的类型决定。

问题:C语言中struct与union的区别是什么?
struct(结构体)与 union(联合体)是C语言中两种不同的数据结构,两者都是常见的复合结构,区别主要表现在以下两个方面:
1.结构体与联合体虽然都是由多个不同的数据类型成员组成的,但不同之处在于联合体中所有成员共用一块地址空间,即联合体只存放了一个被选中的成员,而结构体中所有成员占用空间是累加的, 其所有成员都存在,不同成员会存放在不同的地址在计算一个结构型变量的总长度时,其内存空间大小等于所有成员长度之和(需要考虑字节对齐),而在联合体中,所有成员不能同时占用内存空间,它们不能同时存在,所以一个联合型变量的长度等于其最长的成员的长度
2.对于联合体的不同成员赋值,将会对它的其他成员重写,原来成员的值就不存在了,而对结构体的不同成员赋值是互不影响的。

typedef union {
    double i;
    int k[5];
    char c;
}DATE;

typedef struct data {
    int cat;
    DATE cow;
    double dog;
} too;

DATE max;
printf("%d, sizeof(too) + sizeof(max));

假设为32位机器,int型占4个字节, double型占8个字节,char型占1个字节,而DATE是一个联合型变 量,联合型变量共用空间,uion里面最大的变量类型是int[5],所以占用20个字节,它的大小是20,而 由于 union中,double占了8个字节,因此 union是要8个字节对齐,所占内存空间为8的倍数为了实现 8个字节对齐,所占空间为24。
data是一个结构体变量,每个变量分开占用空间,依次为sizeof(int) + sizeof(DATE)+ sizeof( double)=4+24+8=36按照8字节对齐,占用空间为40,所以结果为 40+24=64

问题:左值和右值是什么?
左值是指可以出现在等号左边的变量或表达式,它最重要的特点就是可写(可寻址)也就是说,它的值可以被修改,如果一个变量或表达式的值不能被修改,那么它就不能作为左值。
右值是指只可以出现在等号右边的变量或表达式它最重要的特点是可读一般的使用场景都是把一个右值赋值给一个左值通常,左值可以作为右值,但是右值不一定是左值。

问题:有符号数和无符号数的运算?
有符号数会转换成无符号数进行运算。

问题:短路原则?
||前面为真,后面不在执行。&&前面为假后面不在执行。

问题:什么是大端和小端?
大端:地址高位存数据低位。
小端:地址地位存数据低位。

问题:++a和a++有什么区别?两者是如何实现的?
++a 先赋值在运算
a++ 先运算在赋值

1.6 C++部分

问题:C++中类成员的访问权限?
public :公有的,类中或类外对象均可直接访问。
private:私有的,只有类中可以访问,类外或子类之中均不可以访问。
protected:受保护的,只有类中或子类的类中可以访问,类外是不可以访问的。

问题:什么是构造函数?
构造函数是一种特殊的函数,用于创建和初始化对象。
它在创建对象时被调用,用于设置对象的初始状态和属性。构造函数的名称通常与类的名称相同,且没有返回类型声明。
构造函数可以有多个重载版本,每个版本允许接受不同类型和数量的参数。通过调用不同的构造函数,可以根据需要创建不同种类的对象。
在C++中,构造函数名称与类名称相同,没有返回类型声明,并且可以是公有、私有或受保护的。当创建对象时,会自动调用适当的构造函数来初始化对象。如果未明确定义构造函数,编译器将提供一个默认的无参数构造函数。

主要功能:
分配内存空间:负责为对象分配足够的内存空间,以存储对象的数据成员。

class MyClass {
private:
    int* data;
public:
    MyClass(int size) {
        data = new int[size];  // 动态分配内存
    }
};

初始化对象成员:确保在对象创建时,类的所有成员都有一个确定的初始状态,避免出现未初始化的变量。

class MyClass {
public:
    int x;
    MyClass(int val) {
        x = val;  // 构造函数初始化成员变量
    }
};

构造函数重载:支持不同参数类型或数量的初始化方式,从而允许对象根据不同的需求进行不同的初始化操作。

class MyClass {
public:
    MyClass() { /* 默认构造函数 */ }
    MyClass(int x) { /* 带参数的构造函数 */ }
};

在继承中调用父类构造函数:派生类的构造函数可以调用基类的构造函数,以确保基类部分的成员被正确初始化。

class Base {
public:
    Base() {
        // 基类构造函数
    }
};

class Derived : public Base {
public:
    Derived() : Base() {  // 调用基类构造函数
        // 派生类构造函数
    }
};

隐式调用:对象在创建时自动调用相应的构造函数,这使得对象能够自动初始化。

MyClass obj(10);  // 显示自动调用构造函数
MyClass obj = 10;  // 隐式自动调用构造函数

支持初始化列表:使用初始化列表对成员变量进行初始化。对于常量、引用类型成员,必须使用初始化列表。

class MyClass {
public:
    const int x;
    MyClass(int val) : x(val) {  // 初始化列表初始化常量成员
    }
};

定义对象的拷贝方式:通过拷贝构造函数可以控制对象如何被复制。

class MyClass {
public:
    MyClass(const MyClass& other) {
        // 拷贝构造函数
    }
};

问题:构造函数的分类是怎样的?
无参构造:Person(){}
有参构造:Person(int a){}
拷贝构造函数:Person(const Person& p){}

问题:构造函数的调用规则是怎样的?
C++编译器至少给一个类添加3个函数
默认构造函数(无参)
默认析构函数(无参)
默认拷贝构造函数,对属性进行值拷贝

问题:什么是析构函数?
~类名(void){}
在对象消亡的时候,用来做释放空间等善后工作
注:析构函数不能有参数,因此不可以发生重载。

问题:引用注意事项?
引用格式:数据类型 &别名 = 原名
引用必须初始化,引用在初始化后不可以改变;函数传参时,可以利用引用让形参修饰实参;引用可以作为函数的返回值,但是不要返回局部变量。引用的本质在C++内部实现一个指针常量。

问题:函数重载是什么?
重载满足条件:同一个作用域下;函数名称相同;函数参数类型不同或者个数不同或者顺序不同。

问题:什么是深浅拷贝?
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
当在类里面涉及到指针操作时,如果采用浅拷贝,则执行拷贝构造函数后。会导致拷贝出两个指针指向同一个内存空间,则进行析构函数时,就会对该空间释放两次,然后导致报错。因此需要进行深拷贝,对于指针重新再开辟一段空间。

问题:静态成员
静态成员变量:静态成员变量属于类本身,而不是某个具体的对象。所有对象共享同一份数据; 在编译阶段分配内存;类内声明,类外初始化
静态成员函数:
静态成员函数属于类本身,而不是某个具体的对象。所有对象共享同一个函数;静态成员函数只能访问静态成员变量。
关于两者内存:如果只声明了类而未定义对象,则类的一般成员变量是不占用内存空间的,只有在定义对象的时候,才为对象的成员变量分配空间。
静态成员不占用类内空间;静态成员函数在类内声明,类外初始化。

问题:继承是什么?
基于一个已有的类,去重新定义一个新的类,这种方式就叫做继承。
语法: class 子类:继承方式 父类 如:class A : public B
多继承语法: class 子类:继承方式 父类, 继承方式 父类1
继承过程中,父类的私有成员也被子类继承,只是由编译器隐藏后访问不到。
子类对象可以直接访问到子类中同名成员。
子类对象加作用域可以访问到父类同名成员
当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数。

问题:菱形继承是什么?
两个派生类继承同一个基类;又有某个类同时继承了两个派生类;这种继承称为菱形继承。
子类中就会有多份公共基类的成员,访问起来会有歧义。
解决办法虚继承:就是在公共基类生成中间子类时,继承方式前加上关键字 virtual

问题:虚函数是什么?
使用virtual关键字修饰的成员函数被成为虚函数。
虚函数只能是类的成员函数, 而不能将类外的普通函数声明为虚函数. 虚函数的作用是允许在派生类中对基类的虚函数重新定义 (函数覆盖), 只能用于类的继承层次结构中.
这样就可以通过父类的指针或引用访问到子类的函数,不然只能访问父类的函数。

问题:什么是多态?
多态是指不同继承关系的类对象,去调用同一函数,产生了不同的行为。在继承中要想构成多态需要满足两个条件:
必须通过基类的指针或者引用调用虚函数。
被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写。

问题:纯虚函数是什么?
纯虚函数不需要在父类中实现,必须在子类中实现
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数。
纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0
当类中有了纯虚函数,这个类也称为抽象类。
特点:无法实例化对象, 子类必须重写抽象类中的纯虚函数,否则也属于抽象类。

问题:重载和覆盖有什么区别?
重载是指同一个类中函数名相同但参数不同,是编译时的行为,用于提供多种实现方式。
覆盖是指派生类函数覆盖基类中的虚函数,是运行时的行为,用于实现多态,使得派生类能够通过基类接口提供不同的实现。

问题: 析构函数可以为 virtual 型,构造函数则不能,为什么?
如果基类的析构函数是 virtual,当通过基类指针 delete 派生类对象时,首先会调用 Derived 类的析构函数,然后再调用 Base 类的析构函数,确保派生类和基类的资源都能正确释放。
如果基类的析构函数不是 virtual,则会导致只调用基类的析构函数,派生类的析构函数不会被调用,从而可能导致派生类的资源没有正确释放,产生内存泄漏或其他未定义行为。
因此,析构函数被设计为 virtual 是为了确保多态删除派生类对象时能够正确调用派生类的析构函数,避免资源泄漏。

构造函数在对象创建时执行,用于初始化对象。而多态性只有在对象已经被构造完成后才能发挥作用。因此,构造函数不涉及多态的动态绑定问题。

问题:结构体和类的区别?
默认访问权限:
类(class):默认情况下,类的成员是私有的(private)。
结构体(struct):默认情况下,结构体的成员是公有的(public)。
面向对象特性:
类:类主要用于面向对象编程,可以使用继承、封装、多态等特性。
结构体:在C++中,结构体与类几乎可以一样地使用,包括继承和多态,但从设计角度来看,结构体通常被用于存储数据。
继承时的默认访问权限:
类:当类继承另一个类时,默认继承方式是私有继承(private)。
结构体:当结构体继承另一个类或结构体时,默认继承方式是公有继承(public)。

问题:什么时候使用 struct,什么时候使用 class?
如果你只是想定义一个包含数据成员的简单结构体,而不需要复杂的行为或封装,通常使用 struct。
如果你需要完整的面向对象特性,如封装、继承、多态等,并且希望定义行为(成员函数)和数据,则使用 class。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇