c++使用解惑
- 什么情况下回默认产生构造函数
- 类内成员变量是否可以提供初始值
- 编译器还会自动合成哪些函数
- 构造函数为什么不能被声明为const
- 类的非成员变量定义声明在什么地方
- 编译器编译类成员变量与类成员函数的顺序?
- this指针的类型
- 29. 定义一个空的类型,里面没有任何成员变量和成员函数,对该类型求sizeof,得到的结果是多少
- 28. config.h是干什么的
- 25. inttypes.h是干什么的
- 23. extern c是干什么的
- 21. 顶层const与底层const的区别在哪?
- 20. linux如何进行反编译
- 19. linux如何把cpp生成汇编
- 18. const变量再什么阶段被处理
- 17. 使用引用时注意
- 16. .data与.bss段有什么区别
- 15. 执行默认初始化的情况有哪些
- 14. 列表初始化与其他初始化有什么不同
- 13. 初始化与赋值有什么不同
- 12. 注释在编译过程中什么阶段去除
- 11. cpp中缓冲区是干什么的?什么时间会刷新缓冲区?
- 10. 类型的作用是什么
- 9.main函数的主要作用是什么
- 8. #pragma是干什么的?
- 6. 什么是重载函数
- 5. 在使用%lf与%f时应该注意什么
- 4. 面向对象的基本原则有哪些
- 1. 什么是指针数组和数组指针
- 1. 内联函数
- 2. 预定宏
cpp程序在运行过程中的内存分布是什么样子的
c++ 标准程序 库
effective c++
c++ concurrent in action
linux多线程服务器端编程
c++ 语言99个常见编程错误
大话设计模式
能通过一个实参调用的构造函数定义了一条从构造函数类型向类类型隐式转换的规则
编译器只会自动执行一部类型转换
explicit阻止构造函数隐式转换
explicit构造函数只能直接初始化,不能赋值初始化
隐式转换仅对一个实参有效,有多个实参的构造函数不会自动转换
函数的返回类型位于类作用域外,因此想要使用类内声明符,必须声明其作用域,即带上类名
编译过程中,类会先处理完全部声明后,才进行成员函数定义的编译。
在类中,如果成员使用了外层作用域的某个名字,而该名字代表一种类型,则类不能在之后重新定义该名字,编译器会忽略该报错
如果成员是const,引用或属于某种未提供默认构造函数的类类型时,我们必须通过构造函数初始值列表为这些成员提供初始值
成员初始化顺序与其在类定义中出现的顺序保持一致。
在const成员函数内,需要修改成员变量时,使用mutable声明
类型转换中,非const量可以自动转换成const量
什么情况下回默认产生构造函数
当类没有声明任何构造函数时
使用 =default 关键字时
类内成员变量是否可以提供初始值
是,当类为其成员变量提供初始值后,在类进行初始化的过程中,优先使用其初始值。
编译器还会自动合成哪些函数
拷贝,析构,赋值
构造函数为什么不能被声明为const
- 因为创建一个类的const类对象时,只有当类的构造函数运行完成后才能成功创建const类对象,然而在构造函数运行时,类成员变量可以被修改,因此构造函数不能声明为const。
类的非成员变量定义声明在什么地方
- 如果非成员函数是类接口的组成部分,则这些函数的声明应该与类在同一个文件中
编译器编译类成员变量与类成员函数的顺序?
先编译类成员变量,然后编译类成员函数
this指针的类型
- 可以把this指针理解为常量指针,类比我class* const this
29. 定义一个空的类型,里面没有任何成员变量和成员函数,对该类型求sizeof,得到的结果是多少
1。空类型实例不包含任何信息,本来求解sizeof时应该为0,但是当我们声明该类型实例的时候,它必须在内存中占有一定的空间,否则无法使用这些实例,不同是编译器不同,一般为1
28. config.h是干什么的
- 各种cpu在不同系统间的参数配置,主要用于os编译,其头为:HAVE_CONFIG_H
25. inttypes.h是干什么的
- 可移植的数据类型,主要用于跨平台编译,其头为:_INTTYPES
23. extern c是干什么的
- 其主要作用是让cpp能够正常使用c代码。
- 由于cpp引入了重载机制(函数名一样,但其参数个数,类型,排序不一样的函数互为重载函数,在编译过程中,编译器根据不同的调用方式来确定调用哪个函数),而c语言不支持重载函数,在编译过程中,cpp会将重载函数的入参信息带入链接符号中,而c语言则不会,因此,会出现链接问题。加上extern c告诉编译器,该段代码使用c语言的编译格式,因此不会出现问题
- 使用场景:c++调用c语言代码;在cpp头文件中使用;原代码有很多c语言的代码;多人协作过程中,不同人擅长的语言不同。
21. 顶层const与底层const的区别在哪?
- 顶层cosnt是指变量本身;底层const是变量所指向的对象
20. linux如何进行反编译
- objdump a.out -dSsx > file
19. linux如何把cpp生成汇编
- g++ -E main.cpp
18. const变量再什么阶段被处理
- 在编译初期进行替换
17. 使用引用时注意
- 引用必须初始化
- 引用必须为对象,因此不存在引用的引用
- 引用类型类似于对象别名
16. .data与.bss段有什么区别
- .bss存放未处理的全局变量;.data存放已初始化的全局变量
- .bss不占用程序空间;.data占用程序空间,即程序文件会变大
- .bss由sys进行初始化;.data由程序进行初始化
15. 执行默认初始化的情况有哪些
- 全局变量
- 局部变量是未定义的
- 类变量需要自行定义
14. 列表初始化与其他初始化有什么不同
在编译阶段进行,列表初始化对数值进行检查,判断数据是否丢失的风险,其他方式不检查。
13. 初始化与赋值有什么不同
- 初始化是开辟空间,并在该空间设置相应数据。赋值是直接在已有空间设置相应数据
- 效率:初始化效率更高
- 过程:初始化更简单
12. 注释在编译过程中什么阶段去除
预处理阶段
11. cpp中缓冲区是干什么的?什么时间会刷新缓冲区?
个人理解,输入输出缓冲区就是暂时存放输入输出数据的地方。说到缓冲区,首先应该讨论他的来历,只有知道了他的来历,自然而然就知道它存在的价值了。我们都知道,相对于CPU的速度,外部IO设备的输入输出是十分缓慢的,如果在没有缓冲区的时候,CPU就要等待用户等将相关数据输入到相应的位置,因此cpu的使用率将大大浪费,为了提高cpu的利用率,在外部IO与cpu之间增加了缓冲区这一过程,因此当用户输入的过程中,将相关的数据输入到缓冲区,同时CPU处理其他事情,当满足缓冲区的条件时,缓冲区将向cpu发送一个读数据的请求,这时cpu放下手头上的事务,一次性将缓冲区的数据全部读走,则IO继续工作,cpu则重新回到刚刚正在处理的事务上。这样大大提高了CPU的使用效率。因此缓冲区就存在,即存在即合理,嘻嘻……
缓存区分为输入与输出缓冲区,再c++中输入输出缓冲区可以进行关联(默认是关联的)比如当调用istream对象时,将会自动刷新ostream。
缓冲区根据触发中断的方式分为行缓冲和全缓冲,行缓冲是指在输入输出遇到换行符时才进行刷新,而全缓冲是指当缓冲区满时,才刷新缓冲区,cpp中iostream流默认是行缓冲模式。
既然有默认的刷新方式,也会有手动刷新的情况,以下是刷新缓冲区的情况:
- endl:换行并刷新缓冲区
- ends:输入添加空字符后,刷新缓冲区
- 缓冲区满
- fflush
- unitbuf:每次输出后都刷新
- nounitbuf:关闭上述
如果再windows平台中要注意,其缓冲区大小约为4K,大于4k则缓冲区就满了
还有,如果程序正常关闭,则刷新缓冲区,如果不是正常关闭,则不会自动刷新缓冲区
10. 类型的作用是什么
任何一种语言都会有类型,那么类型是干什么的呢?其他类型有两个作用:
- 定义了数据类型能够存储的内容,同时确定了在内存中占用的空间
- 定义了能够在该数据类型上进行的操作,即运算
9.main函数的主要作用是什么
个人理解,main函数是程序的入口,即程序通过main函数从系统获取输入,同时返回给系统相应的输出。这也解释了为什么cpp要求必须含有main函数,且main函数的写法基本固定的原因。
8. #pragma是干什么的?
- pragma是预处理编译指令,指示编译器进行哪些动作
- pragma message:用于在编译阶段显示相关信息,有利于debug程序
- pragma once:该头文件仅编译一次,其作用与#ifndef类似,不过#ifndef是标准,支持夸编译器,而#pragma once不支持夸编译器,而#pragma once编译效率更高
- pragma pack:指定内存的对齐方式,一般用于结构体中。
6. 什么是重载函数
- 重载函数是函数名一样,功能类似的一批函数
- 条件:函数名一样;参数类型、个数或者排列顺序不一样,仅有这三个点,返回值不能作为判断点
- 编译过程:筛选出函数名一样的函数并创建函数表;筛选出参数个数一样的候选函数列表(含默认值的也在内);根据数据转化法则进行函数适配从而选出最佳适配函数
- 顶层const不能重载,底层const可以重载
- 当我们传递一个非常量的对象或指向非常量对象的指针时,编译器有限调用非常量版本的函数
- 局部作用域中的重载函数
5. 在使用%lf与%f时应该注意什么
- 当printf时,double与float都可以使用%f这个不做区分,而使用scanf时必须做区分;个人理解,当scanf为读取函数,在进行读取时涉及到读取的字节长度,由于float与double所占空间长度不一样,scanf无法区分,而printf为输出函数,在进行输出时,double与float是可以区分的开的因此,两个可以通用。
4. 面向对象的基本原则有哪些
- 单一职责原则:一个类应该只有一个引起变化的原因,个人理解,一个类只做一件事情,其核心思想是高内聚低耦合
- 开放封闭原则:软件实体应该是可扩展的,而不可修改。个人理解,当一个类完成后,这个类可以为了其需求进行扩展,而不是需要更改这个类来进行适配需求,因此,这需要在满足需求时应该具有前瞻性,尽可能将该类抽象出来,下次来新的需求,直接使用其他类进行扩展,而不是在原来类基础上进行源码修改,这样同时满足类的单一职责原则。开放指的是类的扩展性(个人理解为继承性和抽象性),封闭指的是类本身不做修改。
- 里氏替换原则:任何基类出现的地方,子类一定能够替换。个人理解,结合开放封闭原则,基类就是一个抽象类,而其子类使用继承和多态的方式实现各个功能,因此在函数进行调用的时候直接使用基类进行,通过虚函数表来进行不同情况的适配。因此,只有这样才能保证任何基类出现的地方,子类一定能够替换。同时子类中继承的基类的非虚函数不能进行更改,否则,将会影响基类中的替换。子类只能在其基础上进行扩展。
- 接口隔离原则:接口端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小接口上。个人理解就是接口要单一职责化,不要一个接口里面处理很复杂的逻辑。
- 依赖倒置原则:高层模块不应该依赖低层次的模块,他们都应该依赖于抽象;抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
- 注:这块理解的不是很好,注意以后在实践过程中在回来学习
1. 什么是指针数组和数组指针
- 指针数组:int *p[4],从字面上理解,指针在前,数组在后,因此其本质是数组,而这个数组里面存放的是int类型的指针。
- 数组指针:int (*p)[4],从字面上理解,数组在前,指针在后,因此其本质是指针,而这个指针指向一个长度为4 的int类型数组。
1. 内联函数
1.1 什么是内联函数
- 内联函数是在代码编译过程中将该函数的代码直接在调用位置展开的函数
- 条件:内联函数需要逻辑简单代码短小;无递归形式(如有递归形式容易在编译过程中出现无限循环的情况);不能在循环中使用(由于内联函数是在调用处直接展开该代码,因此如在循环中使用,将会使程序冗余,增加程序存储空间的开销)
- 优点:减少函数调用的时间上的开销,使用空间来换时间
- 类内定义的函数,隐含地认为是内联函数
- 注意事项:一般编译器,如果内联函数过于复杂,将会内联函数进行优化,使其直接成为普通函数,而非内联函数。
1.2 内联函数与宏的区别是什么
- 替换展开时间不同:内联函数在编译阶段进行,而宏在预编译阶段
- 发生概率不同:宏是100%替换,而内联函数由编译器检查优化决定
- 友好程度不同:宏不会检查参数等,而是直接进行替换,而内联函数将会检查参数是否合法
1.3 虚函数为什么不能是内联函数
- 内联函数是在编译阶段即进行替换,而虚函数需要在运行阶段选择调用哪个函数,因此虚函数在编译阶段并不知道调用哪个函数,因此无法进行替换,但当对象可以确定调用那个函数时,个人认为,也可以是虚函数。
- 个人理解,内联函数与类的权限无关,其仅是建议告诉编译器此函数可以替换,而不是强制编译器进行,因此所有函数均可以定义为内联函数,但是否替换由编译器决定。
- 类内定义的函数默认为内联函数,但具体替换不替换由编译器决定
2. 预定宏
2.1 __VISTA是干什么的
- 判断平台是否为vista系统
2.2 宏 __ANDROID__是干什么的
- 使用android平台的数据格式进行编译。
2.3 宏 __WINDOWS是干什么的
- 使用windows平台的数据格式进行编译。
2.4 宏 __cplusplus是干什么的
- cpp中自定义的宏,表示这一段代码为cppdiam,常用于与c语言进行混合编译。主要用于头文件中。
2.5 宏 __MSC_VER是干什么的
- 主要用于跨平台编译 ,该宏表示微软vs编译器的版本号。