C++基础知识笔记

本文概述了C++编程中关键概念,如main函数前后的初始化,内存对齐规则,指针与引用的区别,static和const的作用,struct/class的不同,智能指针的使用,函数调用的压栈,类对象大小,this指针,malloc的工作原理,虚函数和静态_cast。
摘要由CSDN通过智能技术生成

1、 在main执行之前和之后执行的代码可能是什么?

main函数执行之前,主要就是初始化系统相关资源:

        设置栈指针;

        初始化static静态变量和global全局变量,即.data段的内容;

        将未初始化部分的全局变量赋初值,即.bss段的内容;

        将main函数的参数argc,argv等传递给main函数,然后才真正运行main函数。

__attribute__((constructor))

main函数执行之后:

        静态对象的析构;

        全局对象的析构;

        int atexit(void (*func)(void)):若有atexit函数,则执行其注册函数 ;

可以用 atexit 注册一个函数,它会在main 之后执行;

__attribute__((destructor))

2、结构体内存对齐问题?

        结构体内成员按照声明顺序存储,第一个成员地址和整个结构体地址相同。未特殊说明时,按结构体中size最大的成员对齐。C++11以后引入两个关键字 alignas alignof ,alignas可以指定结构体的对齐方式,alignof可以计算出类型的对齐方式。但是alignas指定的对齐单位小于最小自然对齐size时,会被忽略而不生效,比如一字节对齐时:

#pragma pack(1)
/*...struct{};...*/
#pragma pack()
#pragma pack(push,1)
__attribute__((packed))

3、指针和引用的区别

        指针是一个变量,存储的是一个地址;引用跟原来的变量实质上是同一个东西,是原变量的别名。

        指针可以有多级,引用只有一级。

        指针可以为空,引用不能为NULL且在定义时必须初始化。

        指针在初始化后可以改变指向,而引用在初始化之后不可再改变。

        sizeof指针得到的是本指针的大小,sizeof引用得到的是引用所指向变量的大小。

        当把指针作为参数进行传递时,也是将实参的一个拷贝传递给形参,两者指向的地址相同,但不是同一个变量,在函数中改变这个变量的指向不影响实参,而引用却可以。

        引用本质是一个指针,同样会占4字节内存;指针是具体变量,需要占用存储空间。

        引用在声明时必须初始化为另一变量;指针声明和定义可以分开,可以先只声明指针变量而不初始化,等用到时再指向具体变量。

        引用一旦初始化之后就不可以再改变(变量可以被引用为多次,但引用只能作为一个变量引用);指针变量可以重新指向别的变量。

        不存在指向空值的引用,必须有具体实体;但是存在指向空值的指针。

指针数组和数组指针:

        int *p[10] 表示指针数组,强调数组概念,是一个数组变量,数组大小为10,数组内每个元素都是指向int类型的指针变量。

        int (*p)[10] 表示数组指针,强调是指针,只有一个变量,是指针类型,不过指向的是一个int类型的数组,这个数组大小是10。

        int *p(int) 是函数声明,函数名是p,参数是int类型的,返回值是int*类型的。

        int (*p)(int) 是函数指针,强调是指针,该指针指向的函数参数和返回值都是int类型。

4.C++中static的作用

不考虑类的情况

        隐藏:加了static的全局变量和函数只能在该文件所在的编译模块中使用;

        static变量默认初始化为0。全局变量和static变量都存储在静态存储区,在静态数据区,内存中所有的字节默认值都是0x00;

         静态变量在函数内定义,只进行一次初始化,始终存在,具有记忆性。其作用范围与局部变量相同,函数退出后仍然存在,但不能使用。

考虑类的情况

static成员变量:

        只与类关联,不与类的对象关联。

        定义时要分配空间,不能在类声明中初始化,必须在类定义外部初始化,static修饰的变量先于对象存在,初始化时不需要标示为static;

        可以被非static成员函数任意访问;

        在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝。

static成员函数:

        static修饰的类成员属于类,不属于对象,因此不具有this指针,无法访问类对象的非static成员变量和非static成员函数;

        不能被声明为const、virtual和volatile;

        可以被非static成员函数任意访问,但不能被模块外其它函数访问;

5.const

         const修饰变量是也与static有一样的隐藏作用。只能在该文件中使用,其他文件不可以引用声明使用。 因此在头文件中声明const变量是没问题的,因为即使被多个文件包含,链接性都是内部的,不会出现符号冲突。

不考虑类的情况

        const常量在定义时必须初始化,之后无法更改;

        const形参可以接收const和非const类型的实参。

考虑类的情况

const成员变量:

        只能在类声明时初始化,或者通过构造函数初始化列表进行初始化。

const成员函数:

        const对象不可以调用非const成员函数,非const对象都可以调用;

        不可以改变非mutable(用该关键字声明的变量可以在const成员函数中被修改)数据的值;

        只有引用传递和指针传递可以用是否加const来重载,一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来。
常量指针和指针常量:

指针常量: const int *p; 指针常量是常量,指针指向的内容不能变。

常量指针: int * const p; 常量指针是指针,指针指向的地址不能变。

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

相同点:
        两者都拥有成员函数、公有和私有部分;

        任何可以使用class完成的工作,同样可以使用struct完成。

不同点:

        两者中如果不对成员不指定公私有,struct默认是公有的,class则默认是私有的;

        class默认是private继承, 而struct默认是public继承。

C++和C的struct区别:

        C中struct是用户自定义数据类型(UDT);C++中struct是抽象数据类型(ADT),支持成员函数的定义,C++中的struct能继承,能实现多态。

        C中struct是没有权限的设置的,且struct中只能是一些变量的集合体,可以封装数据却不可以隐藏数据,而且成员不可以是函数;C++中,struct增加了访问权限,且可以和类一样有成员函数,成员默认访问说明符为public。

        struct作为类的一种特例是用来自定义数据结构的。一个结构标记声明后,在C中必须在结构标记前加上struct,才能做结构类型名;C++中结构体标记可以直接作为结构体类型名使用。结构体struct在C++中被当作类的一种特例。

7.implicit/explicit

        C++ 构造函数默认是implicit,可以隐式转换的;而explicit关键字的作用就是防止类构造函数的隐式自动转换。

        explicit关键字只对有一个参数的类构造函数有效, 如果类构造函数参数大于或等于两个时, 是不会产生隐式转换的, 所以explicit关键字也就无效了。种例外:如果构造函数的其他参数都有默认值即使参数个数大于1个explicit也是生效的。

        提供了带参的构造函数后,将不再有默认构造函数。如需要则自己编写不带参的构造函数。

8.static_cast/dynamic_cast

        对于上行转换,static_cast和dynamic_cast效果一样,都安全;

        对于下行转换dynamic_cast是安全的,static_cast则不一定。

        static_cast类型转换只是简单的截断,在编译时做类型检查,没有运行时类型检查来保证转换的安全性。dynamic_cast类型转换具有运行时类型检查,可以保证下行转换的安全性。即转换成功就返回转换后的正确类型指针,如果转换失败,则返回NULL,之所以说static_cast在下行转换时不安全,是因为即使转换失败,它也不返回NULL。当类没有虚函数表的时候,dynamic_cast就不能用RTTI来确定类的具体类型,于是就直接不通过编译。

9.虚函数和纯虚函数

        类中virtual定义的函数就是虚函数,包含虚函数的类就是虚类;

        "=0"的虚函数就是纯虚函数:virtual void fun() = 0。

        纯虚函数一定没有定义,用来规范派生类的行为,即接口。

        包含纯虚函数的类就是抽象类,抽象类不能实例化,但可以声明指向实现该抽象类的具体类的指针或引用。

10.malloc()的工作原理

malloc 申请内存的时候,会有两种方式向操作系统申请堆内存。

方式一:申请小于128KB(可由M_MMAP_THRESHOLD选项调节)时,通过 brk() 系统调用从堆分配内存,将数据段的指针(_edata)向高地址移动,获得新的内存空间。free 释放内存的时候,并不会把内存归还给操作系统,而是缓存在 malloc 的内存池中,待下次使用;当最高地址空间的空闲内存超过128K时,执行内存紧缩操作(trim)。

方式二:申请大于128KB时,那就不是去推_edata指针了,而是通过mmap()系统调用,从堆和栈的中间分配一块内存。free 释放内存的时候,会把内存归还给操作系统,内存得到真正的释放。
malloc() 分配的是虚拟内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。如果分配后的虚拟内存没有被访问,则虚拟内存是不会映射到物理内存的,这样就不会占用物理内存了。
malloc() 在分配内存的时候,并不是按用户预期申请的字节数来分配内存空间大小,而是会预分配更大的空间作为内存池。

        为什么不全部使用 mmap 来分配内存?频繁通过 mmap 分配的内存话,不仅每次都会发生运行态的切换,还会发生缺页中断(在第一次访问虚拟地址后),这样会导致 CPU 消耗较大。

        为什么不全部使用 brk 来分配?随着系统频繁地 malloc 和 free ,尤其对于小块内存,堆内将产生越来越多不可用的碎片,导致“内存泄露”。而这种“泄露”现象使用 valgrind 是无法检测出来的。

        brk分配的内存需要等到高地址内存释放以后才能释放(这就是内存碎片产生的原因),而mmap分配的内存可以单独释放。所以,malloc 实现中,充分考虑了 brk 和 mmap 行为上的差异及优缺点,默认分配大块内存才使用 mmap 分配内存空间。

11.智能指针

        智能指针是C++11引入的,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象,防止堆内存泄漏。

        智能指针是个模板类,可以指定类型,传入指针通过构造函数初始化,也可以使用make_shared函数初始化。不能将指针直接赋值给一个智能指针,一个是类,一个是指针。常用的智能指针有 **shared_ptr、unique_ptr和weak_ptr**,它们包含在头文件<memory>中。auto_ptr已被淘汰,不要使用。

        shared_ptr允许多个指针指向同一个对象。shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。拷贝使得对象的引用计数增加1,赋值使得原对象引用计数减1,当计数为0时,自动删除所指向的堆内存。shared_ptr内部的引用计数是线程安全的,但是对象的读取需要加锁。

        在实际编程过程中,应该尽量避免出现智能指针的循环引用,如果不可避免,可以使用使用弱指针——weak_ptr,它不增加引用计数,只要出了作用域就会自动析构。

        unique_ptr“唯一”拥有其所指对象,同一时刻只能有一个unique_ptr指向给定对象,不允许通过复制转移所有权(会编译报错)。如果需要转移其所有权,要通过std::move()函数来实现。相比与原始指针unique_ptr用于其RAII的特性,使得在出现异常的情况下,动态资源能得到释放。unique_ptr指针本身的生命周期:从unique_ptr指针创建时开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)。unique_ptr指针与其所指对象的关系:在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、通过reset方法重新指定、通过release方法释放所有权、通过移动语义转移所有权。

        weak_ptr是为了辅助shared_ptr的存在,它只提供了对管理对象的一个访问手段,不控制对象的生命周期,同时也可以实时动态地知道指向的对象是否存活。weak_ptr只可以由一个 shared_ptr 或另一个 weak_ptr 对象构造,它的构造和析构不会引起引用shared_count记数的增加或减少,只会引起weak_count的增加或减少。

12.函数调用的压栈过程

        发生函数调用时,先将当前函数的返回地址压入栈中,保证调用完成后知道从哪里继续执行;然后将被调用函数的局部变量压入栈中,调用结束后被释放;如果函数有参数,则函数参数从右到左依次入栈。然后函数跳转到被调用函数入口地址开始执行。

13.类对象的大小

        类对象的大小受非静态成员变量的大小、内存对齐额外分配的内存以及虚函数表指针vptr的影响。静态成员和成员函数不类对象的空间,它们分别存储在全局/静态存储区和代码区。当类是派生类时,基类的成员变量也要计算在内。

        空类对象的大小通常为1,而不是0。因为C++标准规定对象的大小不能是0,因为两个不同的对象需要各自不同的地址。

14.this指针

        this指针指向当前对象实例。this指针是一个隐含指针,可以在每一个非静态成员函数中直接使用,不需要定义。

        this指针是在对象构造时创建时的。this指针不是对象的一部分,不占对象存储空间。

        this指针在调用非静态成员函数时,由编译器自动传递,是一个隐含参数。在非静态成员函数以外,无法使用this指针。

        在成员函数中,不能修改this指针的值,但是可以delete this指针。delete this指针后,类对象的内存空间会被释放,可以继续调用其他的成员函数,但是不能调用涉及到类成员或者虚函数的成员函数,因为对象内存已被释放,将会导致未定义行为。

        注意:永远不要在析构函数中调用delete this;因为这将导致递归调用析构函数,最终导致程序崩溃。

        

  • 7
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值