C++函数值的调用 & C++语法

声明:声明是用来告诉编译器变量的名称和类型,而不分配内存。

定义:定义是为了给变量分配内存,可以为变量赋初值。

即使是extern,如果给变量赋值了,就是定义了。

全局变量或静态变量初始值为0,局部变量初始化为随机值。

在C/C++中,变量的声明和定义区别并不大,定义和声明往往是同时发生的,变量定义时,会根据变量类型分配空间。(定义也是声明;如果声明有初始化式,就被当作定义;除非有extern关键字,否则都是变量的定义)

extern int var; //声明
extern int ble = 10;    //定义
typedef int INT;    //声明
struct Node;    //声明
int value;  //定义
struct Node{
    int left;
    int right;
};

以下三个实体的定义也可以放到头文件中

变量的存储位置

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

栈区stack:由编译器自动分配释放,存放函数的参数值,局部变量的值等。我们可以把堆栈看成一个寄存、交换临时数据的内存区。它是由操作系统分配的,内存的申请与回收都是由OS管理。这个栈的操作方式类似于数据结构中的栈堆区heap:(在内存开辟另一块存储区域, 通常是指用来存放程序中进程运行时被动态分配的内存段,动态分配:malloc/new, 动态释放:free/delete)一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收,它与数据结构中的堆不同,分配方式类似于链表全局区(静态区)static:(编译器编译时即分配内存)全局变量和静态变量的存储是放在一起的。初始化的全局变量和静态变量在一块区域,未初始化的全局变量和静态变量又放在相邻的另一块区域中。程序结束后由系统释放。文字常量区:常量字符串存储在这里,程序结束后由系统释放。程序代码区:存放函数体的二进制代码

堆(heap)和栈(stack)的区别

参考:堆(heap)和栈(stack)的区别

申请方式申请后系统的响应申请大小的限制申请效率的比较堆和栈中的存储内容

根据变量的位置可以分为全局变量和局部变量

根据变量的静态属性可以分为静态变量和非静态变量

根据变量的const属性可以分为const变量和非const变量

针对上述的变量分类,变量的初始化分为以下几种:

全局变量和局部变量的初始化:

全局变量和局部变量的不同主要体现在变量的作用范围上,全局变量的初始化分为两种,非类对象的变量初始化发生在函数的编译阶段,如果我们没有显示的初始化,编译器会默认初始化,类对象的全局变量初始化发生在程序运行阶段的main函数之前。

对于局部变量,不会执行默认初始化,因此在使用局部变量之前必须先进行变量的初始化。

静态变量和非静态变量的初始化:

静态变量根据其位置可以分为三种:全局静态变量、定义在函数中的静态变量以及定义在类中的静态变量。

静态变量的初始化:编译器要求不同,有的要求必须主动完成初始化,有的编译器会完成默认初始化,但是值不确定。所以,在使用静态变量的时候,我们一般要求必须在定义的同时完成初始化。对于g++编译器,如果静态变量是全局或者函数的局部变量,都会完成默认初始化;但是如果类包含静态数据成员,C++要求这个静态数据成员仅可以在类内部完成声明,然后在类外,且任何函数之外,完成静态成员的初始化,初始化可以赋初始值,也可以不赋值,不赋值会默认初始化。

由于类的静态数据成员所有对象共有,所以类在分配内存的时候并不会主动分配这个静态数据成员的内存,因此要求我们必须主动要求分配内存。必须在类外完成初始化的原因是,这个静态数据成员保存在数据段(全局区),如果在函数内初始化,意味着要求编译器在栈区为这个变量分配内存,因而错误。但是如果类的静态数据成员是const,那么这个变量必须在类内完成初始化。

const对象和非const对象的初始化:

const对象必须在定义的时候进行初始化,引用的本质是一个const指针,因此引用也必须在定义的时候进行初始化。

程序示例:

int a = 0;  //全局初始化区
char *p1;   //全局未初始化区
int main()
{
    int b;  //栈
    char s[] = "abc";   //栈
    char *p2;   //栈
    char *p3 = "123456";    //p3在栈上,"123456"在常量区
    static int c = 0;   //全局(静态)初始化区
    p1 = (char *)malloc(10);
    p2 = (char *)malloc(20);
    //分配得来的10和20字节的区域在堆区
    strcpy(p1, "123456");   //"123456"在常量区
    
    return 0;
}

C++的异常处理机制

异常处理就是处理程序中的错误。(throw、try、catch、finally)

程序运行时常会碰到一些异常情况(对于异常情况,不能发现加以处理,会导致程序崩溃),例如

当发生异常,程序无法沿着正常的顺序执行下去的时候,立即结束程序可能并不妥当,我们需要给程序提供另外一条可以安全退出的路径。在结束前做一些必要的工作,如将内存中的数据写入文件、关闭打开的文件、释放动态分配的内存空间等。

C/C++的编译器 主流C/C++编译器简介

MSVC(MSVC是微软Windows平台Visual Studio自带的C/C++编译器)

GCC原名GNU C Compiler、G++,后来逐渐支持更多的语言编译(C++、Fortran、Pascal、Objective-C、Java、Ada、Go等),是一套由GNC工程开发的支持多种编程语言的编译器。GCC是大多数类Unix(如Linux、BSD、Mac OS X等)的标准编译器,而且适用于Windows(借助其他移植项目实现的,比如MingW、Cygwin等)。GCC支持多种计算机体系芯片,如x86、ARM,并已移植到其他多种硬件平台。

Clang,是一个由Apple主导编写,基于LLVM(Low Level Virtual Machine的简称)的C/C++/Objective-C编译器,主要用于Mac OS X平台的开发。

编程开发工具选择

集成开发环境

代码编辑器 + MinGW

直接使用GCC

适合于有linux使用经验且对vim比较熟悉的人,对于gcc编译,需要了解makefile的编写。

安装编译器在命令行:# apt-get install gcc g++

编写代码: # vim helloworld.cpp

编译在命令行执行 # g++ code.cpp -o exefile

C++中auto的使用

参考:c++ auto基本用法

总述:

auto的原理就是根据后面的值,来自己推测前面的类型是什么。

auto的作用就是为了简化变量初始化,如果这个变量有一个很长很长的初始化类型,就可以用auto代替。

注意点:

1.用auto声明的变量必须初始化(auto是根据后面的值来推测这个变量的类型,如果后面没有值,自然会报错)

2.函数和模板参数不能被声明为auto(原因同上)

3.因为auto是一个占位符,并不是一个他自己的类型,因此不能用于类型转换或其他一些操作,如sizeof和typeid

4.定义在一个auto序列的变量必须始终推导成同一类型

auto x1 = 5, x2 = 5.0;    //错误, 必须推导为同一类型
auto x1 = 5, x2 = 6;    //正确

int main()
{
    std::vector ve;
    ve.push_back("wqsd");
    std::vector::iterator it = ve.begin();
    auto it_a = ve.begin();    //auto 可以用来代替长类型
    cout << *it << endl;
    cout << *it_a << endl;
    return 0;
}

对于数组或者容器(可用迭代器的对象)情况下

参考:C++ for(auto &a:b)、for(auto a:b)、for(const auto &a:b)

b为数组或容器,是被遍历的对象

for(auto &a:b),循环体中修改a,b中对应内容也会修改

for(auto a:b),循环体中修改a,b中内容不受影响

for(const auto &a:b),a不可修改,用于只读取b中内容

int main()
{
    int arr[5] = {1, 2, 3, 4, 5};
                        //for(const auto &a:b)  a不可修改,用于只读取b中内容
    for(auto a: arr)    //for(auto a:b) 循环体中修改a, b中内容不受影响
    {
        cout << a << ' ';
        a++;
    }
    cout << endl;
    for(int i = 0; i < 5; i++)
    {
        cout << arr[i] << ' ';
    }
    cout << endl;
    
    for(auto &a: arr)   //for(auto &a: b) 循环体中修改a, b中对应内容也会修改
    {
        cout << a << ' ';
        a++;
    }
    cout << endl;
    for(int i = 0; i < 5; i++)
    {
        cout << arr[i] << ' ';
    }
    cout << endl;
    return 0;
}

C++的专项联系

1.

a++ 和 ++a 都属于自增运算符,区别在于对变量a的值进行自增的时机不同。a++是先进行取值,再进行自增;++a是先进性自增,再进行取值。++运算符优先级较高

2.

逗号运算符和逗号分隔符, 赋值运算符的优先级别高于逗号运算符,逗号运算符(a, b)会先执行a再执行b。

3.

struct st{
    int *p;
    int i;
    chat a;
};
int sz = sizeof(struct st);

结构体的总大小,即为sizeof的结果,必须是其内部最大成员的“最宽基本类型成员”的整数倍,不足的要补齐。

4.C++中class和struct的区别

【C++】struct和class的区别_忽晴忽雨江湖的博客-CSDN博客_struct和class的区别

C++语法

答案:超全面的后端开发C/C++面经整理

C++中,内存分成5个区域, 堆, 栈, 自由存储区, 全局/静态存储区、常量存储区

(2)堆和栈的区别

(3)堆块还是栈快

(4)new和delete如何实现的, new和malloc的异同处

new和malloc都会分配空间,但是new还会调用对象的构造函数进行初始化, malloc需要给定空间大小, 而new只需要对象名

(5)既然有malloc和free, C++中为什么还需要new/delete呢

(6)C和C++的区别

C++中还有函数重载和引用等概念, C没有

(7)delete和delete[]的区别

(8)C++、Java的联系与区别, 包括语言特性、垃圾回收、应用场景

(9)C++和python的区别

python的基本数据类型只有 数字、布尔值、字符串、列表、元组等等

(10)struct和class区别

(11)define和const的联系区别

(12)在C++中const的用法

(13)C++中的static的用法和意义

(15)C++的STL实现

内存管理allocator、函数、实现机理、多线程实现等、

算法、容器、迭代器

(16)STL源码中的hash表的实现

unordered_map的底层实现时hashtable, 采用开链法解决哈希冲突。一定情况下, 自动转为红黑树进行组织

(17)解决哈希冲突的方式

(18)STL中的unordered_map 和 map的区别

unordered_map使用哈希实现, 占用内存多,查询速度快,内部无序

map底层采用红黑树实现, 插入删除时间复杂度O(log(n)), 内部有序

(19)STL的vector实现

(20)频繁对vector调用push_back()对性能影响和原因

(21)C++中的vector和list的区别

(22)C++中的重载和重写的区别

重载(overload)是指函数名相同,参数列表不同的函数实现方法,其返回值可以不同。

重写(overwrite)是指函数名相同, 参数列表相同,只有方法体不相同的实现方法, 用于子类继承父类时对父类方法的重写。子类的同名方法屏蔽了父类方法的现象称为隐藏。

(23)C++内存管理

在C++中,内存分成5个区,他们分别是堆、栈、全局/静态存储区和常量存储区和代码区。

(24)面向对象的三大特性

(25)多态的实现

C++ 多态包括编译时多态和运行时多态,编译时多态体现在函数重载和模板上,运行时多态体现在虚函数上。

【26 - 31】虚函数相关

(26)C++虚函数相关(虚函数表, 虚函数指针),虚函数的实现原理

(27)实现编译器处理虚函数表应该如何操作

(28)基类的析构函数一般写成虚函数的原因

如果析构函数不被声明成虚函数, 则编译器实现静态绑定,在删除指向子类的父类指针时, 只会调用父类的析构函数而不调用子类析构函数(造成内存泄漏)

(29)构造函数为什么一般不定义为虚函数

(31)纯虚函数

(32)静态绑定和动态绑定

(33)深拷贝和浅拷贝的区别(举例说明深拷贝的安全性)

深拷贝可以避免重复释放和写冲突。例如使用浅拷贝的对象进行释放后,对原对象的释放会导致内存泄漏或程序崩溃。

(34)对象复用的了解,零拷贝的了解

(35)C++所有的构造函数

C++中的构造函数主要有三种类型:默认构造函数、重载构造函数和拷贝构造函数

(36)什么情况下会调用拷贝构造函数(三种情况)

(37)结构体内存对齐方式和为什么要进行内存对齐?

因为结构体的成员可以有不同的数据类型,所占的大小也不一样。同时,由于CPU读取数据是按块读取的,内存对齐可以使得CPU一次就可以将所需的数据读进来。

(38)内存泄露的定义,如何检测与避免?

动态分配内存所开辟的空间,在使用完毕后未手动释放,导致一直占据该内存,即为内存泄漏。

造成内存泄漏的几种原因:

1)类的构造函数和析构函数中new和delete没有配套

2)在释放对象数组时没有使用delete[],使用了delete

3)没有将基类的析构函数定义为虚函数,当基类指针指向子类对象时,如果基类的析构函数不是virtual,那么子类的析构函数将不会被调用,子类的资源没有正确释放,因此造成内存泄露

4)没有正确的清楚嵌套的对象指针

避免方法:

malloc/free要配套

使用智能指针;

将基类的析构函数设为虚函数;

(39)C++的智能指针有哪些

C++中的智能指针有auto_ptr,shared_ptr,weak_ptr和unique_ptr。智能指针其实是将指针进行了封装,可以像普通指针一样进行使用,同时可以自行进行释放,避免忘记释放指针指向的内存地址造成内存泄漏。

auto_ptr是较早版本的智能指针,在进行指针拷贝和赋值的时候,新指针直接接管旧指针的资源并且将旧指针指向空,但是这种方式在需要访问旧指针的时候,就会出现问题。

unique_ptr是auto_ptr的一个改良版,不能赋值也不能拷贝,保证一个对象同一时间只有一个智能指针。

shared_ptr可以使得一个对象可以有多个智能指针,当这个对象所有的智能指针被销毁时就会自动进行回收。(内部使用计数机制进行维护)

weak_ptr是为了协助shared_ptr而出现的。它不能访问对象,只能观测shared_ptr的引用计数,防止出现死锁。

(40)调试程序的方法

(45)C11特性

自动类型推导auto:auto的自动类型推导用于从初始化表达式中推断出变量的数据类型。通过auto的自动类型推导,可以大大简化我们的编程工作

nullptr

:nullptr是为了解决原来C++中NULL的二义性问题而引进的一种新的类型,因为NULL实际上代表的是0,而nullptr是void*类型的

lambda表达式:它类似Javascript中的闭包,它可以用于创建并定义匿名的函数对象,以简化编程工作。Lambda的语法如下:

[函数对象参数](操作符重载函数参数)mutable或exception声明->返回值类型{函数体}

thread类和mutex类

新的智能指针 unique_ptr和shared_ptr

(48)string的底层实现

string继承自basic_string,其实是对char*进行了封装,封装的string包含了char*数组,容量,长度等等属性。

string可以进行动态扩展,在每次扩展的时候另外申请一块原空间大小两倍的空间(2*n),然后将原字符串拷贝过去,并加上新增的内容。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
来说点什么吧!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容