C/C++面试总结

一、关键字static、const、extern、volatile作用

1、const

1.修饰常量

用const修饰的变量是不可变的,修饰后的变量只能使用,不能修改。

2.修饰指针

如果const位于*的左侧,eg:const int* a,则const就是用来修饰指针所指向的变量,此时为常量指针,即指针指向*a为常量;

如果const位于*的右侧,eg:int* const a,const就是修饰指针本身,此时为指针常量,即指针本身a是常量。

const int* p = &a; // 常量指针;指针指向的内容不能变,*p不能变

int* const p = &a; // 指针常量;指针本身不能变, p不能变

3.修饰函数参数

用const修饰函数参数,传递过来的参数在函数内不可以改变。

void func (const int &n)
{
     n = 10;        // 编译错误 
}

2、static (静:是一种约束,不能乱跑,乱动)

1.修饰变量

(1)修饰局部变量

局部变量没有被static修饰: 存储在栈中 自动初始值不固定

局部变量被static修饰: (叫做局部静态变量)存储在全局数据区 初始化为0

代码运行,分析:

子函数变量中不加static进行修饰

#include <stdio.h>
 
void test()
{
    static int a = 0;
    a++;
    printf("%d ", a);
}
int main()
{
    int i = 0;
    for (i = 0; i < 8; i++)
    {
        test();
    }
    return 0;
}

子函数中加static进行修饰

#include <stdio.h>
 
void test()
{
    static int a = 0;
    a++;
    printf("%d ", a);
}
int main()
{
    int i = 0;
    for (i = 0; i < 8; i++)
    {
        test();
    }
    return 0;
}

(2)修饰全局变量

全局变量定义在函数体外部,在全局数据区分配存储空间,且编译器会自动对其初始化。

**全局变量没有被static修饰:**整工程所有文件可以用这个全局变量(其它文件使用时候需要extern外部声明)也就是说其他文件不能再定义一个与其相同名字的变量了(否则编译器会认为它们是同一个变量)。

**全局变量被static修饰:**静态全局变量仅对当前文件可见,其他文件不可访问,其他文件可以定义与其同名的变量,两者互不影响。

**static定义全局变量的作用:**在定义不需要与其他文件共享的全局变量时,加上static关键字能够有效地降低程序模块之间的耦合,避免不同文件同名变量的冲突,且不会误使用。

2.修饰函数

在函数的返回类型前加上static,就是静态函数。

**静态函数:**只能在声明它的文件中可见,其他文件不能引用该函数,不同的文件可以使用相同名字的静态函数,互不影响。

**非静态函数:**可以在另一个文件中直接引用,甚至不必使用extern声明。

3.C++面向对象中static用法

(1)类内静态数据成员

**作用:**实现多个对象共享数据的目标。

static 成员变量属于类,不属于某个具体的对象,即使创建多个对象,也只为 m_total (下面程序中)分配一份内存,所有对象使用的都是这份内存中的数据。当某个对象修改了 m_total,也会影响到其他对象。

class Student{
public:
    Student(char *name, int age, float score);
    void show();
public:
    static int m_total;  //静态成员变量
private:
    char *m_name;
    int m_age;
    float m_score;
};

(2)使用方法

初始化静态成员变量:

static 成员变量的内存既不是在声明类时分配,也不是在创建对象时分配,而是在(类外)初始化时分配。反过来说,没有在类外初始化的 static 成员变量不能使用。

int Student::m_total = 0;

静态成员变量访问:

static 成员变量既可以通过对象来访问,static 成员变量不占用对象的内存,而是在所有对象之外开辟内存,即使不创建对象也可以访问。具体来说,static 成员变量和普通的 static 变量类似,都在内存分区中的全局数据区分配内存。

//通过类类访问 static 成员变量
Student::m_total = 10;
//通过对象来访问 static 成员变量
Student stu("小明", 15, 92.5f);
stu.m_total = 20;
//通过对象指针来访问 static 成员变量
Student *pstu = new Student("李华", 16, 96);
pstu -> m_total = 20;

3、extern

1.修饰变量

若一个变量需要在同一个工程中不同文件里直接使用或修改,则需要将变量做extern声明。只需将该变量在其中一个文件中定义,然后在另外一个文件中使用extern声明便可使用,但两个变量类型需一致。

方式一:

在1.c中定义全局变量
int i; 
 
在2.c和3.c中都用 
extern int i;
 
声明一下就可以使用了

方式 二:

在头文件a.h 中声明 
extern int i; 
 
在其他某个c文件中定义
int i =0; 
 
其他要使用i变量的c源文件只需要include"a.h"就可以

2.修饰函数

在定义函数时,如果在函数首部的最左端冠以关键字extern,则表示此函数是外部函数,可以供其他文件调用。C语言规定,如果在定义函数时省略extern,则隐含为外部函数。在文件中要调用其他文件中的外部函数,则需要在文件中用extern声明该外部函数,然后就可以使用。

和定义全局变量 方式二比较相同呢。

4、volatile

1.修饰变量

编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问,这点在单片机这种存储器有限制的程序中需要注意。

声明时语法:
int volatile vInt;

volatile int i=10;
int a = i;
...
// 其他代码,并未明确告诉编译器,对 i 进行过操作
int b = i;

当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。

volatile 指出 i 是随时可能发生变化的,每次使用它的时候必须从 i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在 b 中。而优化做法是,由于编译器发现两次从 i读数据的代码之间的代码没有对 i 进行过操作,它会自动把上次读的数据放在 b 中。而不是重新从 i 里面读。这样以来,如果 i是一个寄存器变量或者表示一个端口数据就容易出错(可能某些寄存器值,被中断程序改变),所以说 volatile 可以保证对特殊地址的稳定访问。

参考文章:

c语言中static关键字用法详解_static在c语言中的用法_guotianqing的博客-CSDN博客

C++ static静态成员变量详解 (biancheng.net)

二、内存分配&堆和栈

1、堆和栈

1.栈(stack=stand)

我叫它客栈,人来人往,只是大家伙(变量)临时休息的地方。

(1)编译器自动申请和释放空间

(2)栈向着内存减小的方向生长。

(3)栈有静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由 malloc 函数进行分配,但是栈的动态分配和堆是不同的,它的动态分配是由编译器进行释放,无需我们手工实现。

(4)栈的效率比堆高很多

2.堆(heap)

(1)程序员手动申请和释放

(2)堆生长方向是向上的,也就是向着内存地址增加的方向

(3)堆都是动态分配的,没有静态分配的堆

2、内存分配

1.一个C/C++编译的程序占用内存分为以下5个部分

栈区(stack): 由编译器自动分配与释放,存放为运行时函数分配的局部变量、函数参数、返回数据、返回地址等。其操作类似于数据结构中的栈。
堆区(heap): 一般由程序员自动分配,如果程序员没有释放,程序结束时可能有OS回收。其分配类似于链表。
全局区(静态区static): 存放全局变量、静态数据、常量。程序结束后由系统释放。全局区分为已初始化全局区(data)和未初始化全局区(bss)。

代码区: 存放函数体(类成员函数和全局区)的二进制代码。
常量区(文字常量区): 存放常量字符串,程序结束后有系统释放。

2.动态内存分配

主要说明两种方式:malloc/free和malloc/free不同之处

(1)malloc/free是C/C++标准库的函数,new/delete是C++操作符

(2)申请内存位置不同

new操作符是从自由存储区上为对象动态分配内存空间的,malloc函数是从堆上动态分配内存。

自由存储区是C++基于new操作符的一个抽象概念, 凡是通过new操作符进行内存申请的, 该内存称为自由存储区。 而自由存储区的位置取决于operator new的实现细节。自由存储区不仅可以是堆, 也可以是静态存储区, 取决operator new在哪里为对象分配内存

(3)定制内存大小不同

malloc/free需要手动计算类型大小,而new/delete编译器可以自己计算类型大小。

(4)返回值类型不同

new操作符内存分配成功时, 返回的是对象类型的指针,而malloc返回的是void*指针, 需要通过强转才能转成我们所需要的类型。new内存分配失败时会直接抛bac_alloc异常, 它不会返NULL, malloc分配内存失败时返回NULL

(5)malloc/free只是动态分配内存空间/释放空间。

new/delete除了分配空间还会调用构造函数和析构函数进行初始化与清理(清理成员)

(6)malloc/free和malloc/free使用方法

使用中多数遇到 C++类对象的内存空间动态分配用new/delete,而C/C++中指针对象空间动态分配用malloc/free。

3、内存分配重要问题

(1)C++中new申请的内存, 可以用free释放吗

不可以,new对应delete不可以张冠李戴。malloc/free,new/delete必需配对使用。

malloc与free是c++、c语言的标准库函数,new、delete是c++的运算符。它们都可用用申请动态内存和释放内存。对于非内部数据类型的对象而言,光用malloc/free无法满足动态对象的要求。对象在创建的同时要自动执行函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此c++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。new和delete的本质是一个函数,malloc和free只是这两个函数中的一句调用语句,

创建对象和释放对象具体调用是,new分配内存时,先调用malloc后调用构造函数,释放空间时,先调用析构函数,后调用free。

参考文章:

C/C++程序内存的分配_c++内存分配_cherrydreamsover的博客-CSDN博客

C语言:内存分配 - 知乎 (zhihu.com)

C语言的内存分配{静态内存&动态内存&堆栈}_c语言内存_梅尔文的博客-CSDN博客

C/C++动态内存管理malloc/new、free/delete的异同_cherrydreamsover的博客-CSDN博客

相关推荐
IT技术分享社区21 分钟前
C#实战:使用腾讯云识别服务轻松提取火车票信息
开发语言·c#·云计算·腾讯云·共识算法
极客代码23 分钟前
【Python TensorFlow】入门到精通
开发语言·人工智能·python·深度学习·tensorflow
疯一样的码农30 分钟前
Python 正则表达式(RegEx)
开发语言·python·正则表达式
&岁月不待人&1 小时前
Kotlin by lazy和lateinit的使用及区别
android·开发语言·kotlin
StayInLove1 小时前
G1垃圾回收器日志详解
java·开发语言
TeYiToKu1 小时前
笔记整理—linux驱动开发部分(9)framebuffer驱动框架
linux·c语言·arm开发·驱动开发·笔记·嵌入式硬件·arm
无尽的大道1 小时前
Java字符串深度解析:String的实现、常量池与性能优化
java·开发语言·性能优化
互联网打工人no11 小时前
每日一题——第一百二十四题
c语言
爱吃生蚝的于勒1 小时前
深入学习指针(5)!!!!!!!!!!!!!!!
c语言·开发语言·数据结构·学习·计算机网络·算法
羊小猪~~1 小时前
数据结构C语言描述2(图文结合)--有头单链表,无头单链表(两种方法),链表反转、有序链表构建、排序等操作,考研可看
c语言·数据结构·c++·考研·算法·链表·visual studio