问一下Cpp中的时间函数?

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

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

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

将未初始化部分的全局变量赋初值:数值型short,int,long等为0,bool为FALSE,指针为NULL等等,即.bss段的内容

全局对象初始化,在main之前调用构造函数,这是可能会执行前的一些代码

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

main函数执行之后:

全局对象的析构函数会在main函数之后执行;

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

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

结构体内成员按照声明顺序存储,第一个成员地址和整个结构体地址相同。

未特殊说明时,按结构体中size最大的成员对齐(若有double成员,按8字节对齐。)

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

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

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

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

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

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

引用只是别名,不占用具体存储空间,只有声明没有定义;指针是具体变量,需要占用存储空间。

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

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

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

申请方式不同:栈由系统自动分配;堆是自己申请和释放的。

申请大小限制不同:栈顶和栈底是之前预设好的,栈是向栈底扩展,大小固定,可以通过ulimit -a查看,由ulimit -s修改;堆向高地址扩展,是不连续的内存区域,大小可以灵活调整。

申请效率不同:栈由系统分配,速度快,不会有碎片;堆由程序员分配,速度慢,且会有碎片。

栈就像我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。

堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。

《C++中堆(heap)和栈(stack)的区别》:

5、区别以下指针类型?

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

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

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

int (*p)(int)是函数指针,强调是指针,该指针指向的函数具有int类型参数,并且返回值是int类型的。

6、基类的虚函数表存放在内存的什么区,虚表指针vptr的初始化时间

首先整理一下虚函数表的特征:

虚函数表是全局共享的元素,即全局仅有一个,在编译时就构造完成

虚函数表类似一个数组,类对象中存储vptr指针,指向虚函数表,即虚函数表不是函数,不是程序代码,不可能存储在代码段

虚函数表存储虚函数的地址,即虚函数表的元素是指向类成员函数的指针,而类中虚函数的个数在编译时期可以确定,即虚函数表的大小可以确定,即大小是在编译时期确定的,不必动态分配内存空间存储虚函数表,所以不在堆中

根据以上特征,虚函数表类似于类中静态成员变量。静态成员变量也是全局共享,大小确定,因此最有可能存在全局数据区,结果显示:

虚函数表vtable在/Unix中存放在可执行文件的只读数据段中(rodata),这与微软的编译器将虚函数表存放在常量段存在一些差别

由于虚表指针vptr跟虚函数密不可分,对于有虚函数或者继承于拥有虚函数的基类,对该类进行实例化时,在构造函数执行时会对虚表指针进行初始化,并且存在对象内存布局的最前面。

一般分为五个区域:栈区、堆区、函数区(存放函数体等二进制代码)、全局静态区、常量区

C++中虚函数表位于只读数据段(.rodata),也就是C++内存模型中的常量区;而虚函数则位于代码段(.text),也就是C++内存模型中的代码区。

都可用于内存的动态申请和释放

前者是C++运算符,后者是C/C++语言标准库函数

new自动计算要分配的空间大小,malloc需要手工计算

new是类型安全的,malloc不是。例如:

new调用名为operator new的标准库函数分配足够空间并调用相关对象的构造函数,delete对指针所指对象运行适当的析构函数;然后通过调用名为operator delete的标准库函数释放该对象所用内存。后者均没有相关调用

后者需要库文件支持,前者不用

new是封装了malloc,直接free不会报错,但是这只是释放内存,而不会析构对象

new的实现过程是:首先调用名为operator new的标准库函数,分配足够大的原始为类型化的内存,以保存指定类型的一个对象;接下来运行该类型的一个构造函数,用指定初始化构造对象;最后返回指向新分配并构造后的的对象的指针

delete的实现过程:对指针指向的对象运行适当的析构函数;然后通过调用名为operator delete的标准库函数释放该对象所用内存

malloc和free是标准库函数,支持覆盖;new和delete是运算符,并且支持重载。

malloc仅仅分配内存空间,free仅仅回收空间,不具备调用构造函数和析构函数功能,用malloc分配空间存储类的对象存在风险;new和delete除了分配回收功能外,还会调用构造函数和析构函数。

malloc和free返回的是void类型指针(必须进行类型转换),new和delete返回的是具体类型指针。

delete只会调用一次析构函数。

delete[]会调用数组中每个元素的析构函数。

10、宏定义和函数有何区别?

宏在编译时完成替换,之后被替换的文本参与编译,相当于直接插入了代码,运行时不存在函数调用,执行起来更快;函数调用在运行时需要跳转到具体调用函数。

宏定义属于在结构中插入代码,没有返回值;函数调用具有返回值。

宏定义参数没有类型,不进行类型检查;函数参数具有类型,需要检查类型。

宏定义不要在最后加分号。

宏主要用于定义常量及书写复杂的内容;typedef主要用于定义类型别名。

宏替换发生在编译阶段之前,属于文本插入替换;typedef是编译的一部分。

宏不检查类型;typedef会检查数据类型。

宏不是语句,不在在最后加分号;typedef是语句,要加分号标识结束。

12、变量声明和定义区别?

声明仅仅是把变量的声明的位置及类型提供给编译器,并不分配内存空间;定义要在定义的地方为其分配存储空间。

相同变量可以在多处声明(外部变量extern),但只能在一处定义。

13、哪几种情况必须用到初始化成员列表?

初始化一个const成员。

调用一个基类的构造函数,而该函数有一组参数。

调用一个数据成员对象的构造函数,而该函数有一组参数。

sizeof是运算符,并不是函数,结果在编译时得到而非运行中获得;strlen是字符处理的库函数。

sizeof参数可以是任何数据的类型或者数据(sizeof参数不退化);strlen的参数只能是指针且结尾是‘’的字符串。

因为sizeof值在编译时确定,所以不能用来得到动态分配(运行时分配)存储空间的大小。

15、常量指针和指针常量区别?

常量指针是一个指针,读成常量的指针,指向一个只读变量。如int const *p或const int *p。

指针常量是一个不能给改变指向的指针。指针是个常亮,不能中途改变指向,如int *const p。

a是数组名,是数组首元素地址,+1表示地址值加上一个int类型的大小,如果a的值是0x,加1操作后变为0x。*(a + 1) = a[1]。

&a是数组的指针,其类型为int (*)[10](就是前面提到的数组指针),其加1时,系统会认为是数组首地址加上整个数组的偏移(10个int型变量),值为数组a尾元素后一个元素的地址。

若(int *)p ,此时输出 *p时,其值为a[0]的值,因为被转为int *类型,解引用时按照int类型大小来读取。

17、数组名和指针(这里为指向数组首元素的指针)区别?

二者均可通过增减偏移量来访问数组中的元素。

数组名不是真正意义上的指针,可以理解为常指针,所以数组名没有自增、自减等操作。

当数组名当做形参传递给调用函数后,就失去了原有特性,退化成一般指针,多了自增、自减操作,但sizeof运算符不能再得到原数组的大小了。

18、野指针和悬空指针

都是是指向无效内存区域(这里的无效指的是“不安全不可控”)的指针,访问行为将会导致未定义行为。

野指针,指的是没有被初始化过的指针

因此,为了防止出错,对于指针初始化时都是赋值为 nullptr,这样在使用时编译器就会直接报错,产生非法内存访问。

悬空指针,指针最初指向的内存已经被释放了的一种指针。

此时 p和p2就是悬空指针,指向的内存已经被释放。继续使用这两个指针,行为不可预料。需要设置为p=p2=nullptr。此时再使用,编译器会直接保错。

避免野指针比较简单,但悬空指针比较麻烦。c++引入了智能指针,C++智能指针的本质就是避免悬空指针的产生。

野指针:指针变量未及时初始化 =》 定义指针变量及时初始化,要么置空。

悬空指针:指针free或delete之后没有及时置空 =》 释放操作后立即置空。

19、迭代器失效的情况

1、尾后插入:size 《 capacity时,首迭代器不失效尾迭代失效(未重新分配空间),size == capacity时,所有迭代器均失效(需要重新分配空间)。

2、中间插入:中间插入:size 《 capacity时,首迭代器不失效但插入元素之后所有迭代器失效,size == capacity时,所有迭代器均失效。

尾后删除:只有尾迭代失效。

中间删除:删除位置之后所有迭代失效。

标准C++中的字符串类取代了标准C函数库头文件中的字符数组处理函数(C中没有字符串类型)。

C++中用来做控制态输入输出的tream类库替代了标准C中的stdio函数库。

在C++中,允许有相同的函数名,不过它们的参数类型不能完全相同,这样这些函数就可以相互区别开来。而这在C语言中是不允许的。也就是C++可以重载,C语言不允许。

C++语言中,允许变量定义语句在程序中的任何地方,只要在是使用它之前就可以;而C语言中,必须要在函数开头部分。而且C++允许重复定义变量,C语言也是做不到这一点的

在C++中,除了值和指针之外,新增了引用。引用型变量是其他变量的一个别名,我们可以认为他们只是名字不相同,其他都是相同的。

《C语言与C++有什么区别?》

define是在编译的预处理阶段起作用,而const是在编译、运行的时候起作用

define只做替换,不做类型检查和计算,也不求解,容易产生错误,一般最好加上一个大括号包含住全部的内容,要不然很容易出错

const常量有数据类型,编译器可以对其进行类型安全检查

define只是将宏名称进行替换,在内存中会产生多分相同的备份。const在程序运行中只有一份备份,且可以执行常量折叠,能将复杂的的表达式计算出结果放入常量表

宏替换发生在编译阶段之前,属于文本插入替换;const作用发生于编译过程中。

宏不检查类型;const会检查数据类型。

宏定义的数据没有分配内存空间,只是插入替换掉;const定义的变量只是值不能改变,但要分配内存空间。

隐藏。所有不加static的全局变量和函数具有全局可见性,可以在其他文件中使用,加了之后只能在该文件所在的编译模块中使用

默认初始化为0,包括未初始化的全局静态变量与局部静态变量,都存在全局未初始化区

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

static成员变量:只与类关联,不与类的对象关联。定义时要分配空间,不能在类声明中初始化,必须在类定义体外部初始化,初始化时不需要标示为static;可以被非static成员函数任意访问。

static成员函数:不具有this指针,无法访问类对象的非static成员变量和非static成员函数;不能被声明为const、虚函数和volatile;可以被非static成员函数任意访问

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

const形参可以接收const和非const类型的实参,例如

const成员变量:不能在类定义外部初始化,只能通过构造函数初始化列表进行初始化,并且必须有构造函数;不同类对其const数据成员的值可以不同,所以不能在类中声明时初始化

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

顶层const:指的是const修饰的变量本身是一个常量,无法修改,指的是指针,就是 * 号的右边

底层const:指的是const修饰的变量所指向的对象是一个常量,指的是所指变量,就是 * 号的左边

执行对象拷贝时有限制,常量的底层const不能赋值给非常量的底层const

使用命名的强制类型转换函数const_cast时,只能改变运算对象的底层const

int *const a,依旧是指针类型,表示a为指向整型数据的常指针。(看成const(a),对指针const)

26、类的对象存储空间?

非静态成员的数据类型大小之和。

编译器加入的额外成员变量(如指向虚函数表的指针)。

为了边缘对齐优化加入的padding。

当在父类中使用了虚函数时候,你可能需要在某个子类中对这个虚函数进行重写,以下方法都可以:

如果不使用override,当你手一抖,将foo()写成了foo()会怎么样呢?结果是编译器并不会报错,因为它并不知道你的目的是重写虚函数,而是把它当成了新的函数。如果这个虚函数很重要的话,那就会对整个程序不利。所以,override的作用就出来了,它指定了子类的这个虚函数是重写的父类的,如果你名字不小心打错了的话,编译器是不会编译通过的:

当不希望某个类被继承,或不希望某个虚函数被重写,可以在类名和虚函数后添加final关键字,添加final关键字后被继承或重写,编译器会报错。例子如下:

29、初始化和赋值的区别

对于简单类型来说,初始化和赋值没什么区别

对于类和复杂数据类型来说,这两者的区别就大了,举例如下:

综上,总结出使用方法,在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern “C”声明,在.c文件中包含了extern “C”时会出现编译语法错误。所以使用extern “C”全部都放在于cpp程序相关文件或其头文件中。

(1)C++调用C函数:

38、浅拷贝和深拷贝的区别

浅拷贝只是拷贝一个指针,并没有新开辟一个地址,拷贝的指针和原来的指针指向同一块地址,如果原来的指针所指向的资源释放了,那么再释放浅拷贝的指针的资源就会出现错误。

深拷贝不仅拷贝值,还开辟出一块新的空间用来存放新的值,即使原先的对象被析构掉,释放内存了也不会影响到深拷贝得到的值。在自己实现拷贝赋值的时候,如果有指针变量的话是需要自己实现深拷贝的。

39、内联函数和宏定义的区别

内联(inline)函数和普通函数相比可以加快程序运行的速度,因为不需要中断调用,在编译的时候内联函数可以直接嵌入到目标代码中。

使用宏定义的地方都可以使用inline函数

作为类成员接口函数来读写类的私有成员或者保护成员,会提高效率

为什么不能把所有的函数写成内联函数

内联函数以代码复杂为代价,它以省去函数调用的开销来提高执行效率。所以一方面如果内联函数体内代码执行时间相比函数调用开销较大,则没有太大的意义;另一方面每一处内联函数的调用都要复制代码,消耗更多的内存空间,因此以下情况不宜使用内联函数:

函数体内的代码比较长,将导致内存消耗代价

函数体内有循环,函数执行时间要比函数调用开销大

内联函数在编译时展开,宏在预编译时展开

内联函数直接嵌入到目标代码中,宏是简单的做文本替换

内联函数有类型检测、语法判断等功能,而宏没有

内联函数是函数,宏不是

宏定义时要注意书写(参数要括起来)否则容易出现歧义,内联函数不会产生歧义

内联函数代码是被放到符号表中,使用时像宏一样展开,没有调用的开销,效率很高;

在使用时,宏只做简单字符串替换(编译前)。而内联函数可以进行参数类型检查(编译时),且具有返回值。

内联函数本身是函数,强调函数特性,具有重载等功能。

内联函数可以作为某个类的成员函数,这样可以使用类的保护成员和私有成员,进而提升效率。而当一个表达式涉及到类保护成员或私有成员时,宏就不能实现了。

40、构造函数、析构函数、虚函数可否声明为内联函数

首先,将这些函数声明为内联函数,在语法上没有错误。因为inline同register一样,只是个建议,编译器并不一定真正的内联。

register关键字:这个关键字请求编译器尽可能的将变量存在内部寄存器中,而不是通过内存寻址访问,以提高效率

言下之意就是普通的new,就是我们常用的new,在C++中定义如下:

因此plain new在空间分配失败的情况下,抛出异常std::bad_alloc而不是返回NULL,因此通过判断返回值是否为NULL是徒劳的,举个例子:

nothrow new在空间分配失败的情况下是不抛出异常,而是返回NULL,定义如下:

这种new允许在一块已经分配成功的内存上重新构造对象或对象数组。placement new不用担心内存分配失败,因为它根本不分配内存,它做的唯一一件事情就是调用对象的构造函数。定义如下:

palcement new的主要用途就是反复使用一块较大的动态分配的内存来构造不同类型的对象或者他们的数组

placement new构造起来的对象数组,要显式的调用他们的析构函数来销毁(析构函数并不释放对象的内存),千万不要使用delete,这是因为placement new构造起来的对象或数组大小并不一定等于原来分配的内存大小,使用delete会造成内存泄漏或者之后释放内存时出现运行时错误。

算是为了与C语言进行兼容而定义的一个问题吧

NULL来自C语言,一般由宏定义实现,而 nullptr 则是C++11的新增关键字。在C语言中,NULL被定义为(void*)0,而在C++语言中,NULL则被定义为整数0。编译器一般对其实际定义如下:

在C++中指针必须有明确的类型定义。但是将NULL定义为0带来的另一个问题是无法与整数的0区分。因为C++中允许有函数重载,所以可以试想如下函数定义情况:

那么在传入NULL参数时,会把NULL当做整数0来看,如果我们想调用参数是指针的函数,该怎么办呢?。nullptr在C++11被引入用于解决这一问题,nullptr可以明确区分整型和指针类型,能够根据环境自动转换成相应的指针类型,但不会被转换为任何整型,所以不会造成参数传递错误。

nullptr的一种实现方式如下:

以上通过模板类和运算符重载的方式来对不同类型的指针进行实例化从而解决了(void*)指针带来参数类型不明的问题,另外由于nullptr是明确的指针类型,所以不会与整形变量相混淆。但nullptr仍然存在一定问题,例如:

在这种情况下存在对不同指针类型的函数重载,此时如果传入nullptr指针则仍然存在无法区分应实际调用哪个函数,这种情况下必须显示的指明参数类型。

48、简要说明C++的内存分区

C++中的内存分区,分别是堆、栈、自由存储区、全局/静态存储区、常量存储区和代码区。如下图所示

栈:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限

堆:就是那些由 new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个 delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收

自由存储区:就是那些由malloc等分配的内存块,它和堆是十分相似的,不过它是用free来结束自己的生命的

全局/静态存储区:全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量和静态变量又分为初始化的和未初始化的,在C++里面没有这个区分了,它们共同占用同一块内存区,在该区定义的变量若没有初始化,则会被自动初始化,例如int型变量自动初始为0

常量存储区:这是一块比较特殊的存储区,这里面存放的是常量,不允许修改

代码区:存放函数体的二进制代码

49、C++的异常处理的方法

在程序执行过程中,由于程序员的疏忽或是系统资源紧张等因素都有可能导致异常,任何程序都无法保证绝对的稳定,常见的异常有:

动态分配空间时空间不足

如果不及时对这些异常进行处理,程序多数情况下都会崩溃。

C++中的异常处理机制主要使用try、throw和catch三个关键字,其在程序中的用法如下:

代码中,对两个数进行除法计算,其中除数为0。可以看到以上三个关键字,程序的执行流程是先执行try包裹的语句块,如果执行过程中没有异常发生,则不会进入任何catch包裹的语句块。如果发生异常,则使用throw进行异常抛出,再由catch进行捕获,throw可以抛出各种数据类型的信息,代码中使用的是数字,也可以自定义异常class。

catch根据throw抛出的数据类型进行精确捕获(不会出现类型转换),如果匹配不到就直接报错,可以使用catch(…)的方式捕获任何异常(不推荐)。

当然,如果catch了异常,当前函数如果不进行处理,或者已经处理了想通知上一层的调用者,可以在catch里面再throw异常。

(2)函数的异常声明列表

有时候,程序员在定义函数的时候知道函数可能发生的异常,可以在函数声明和定义时,指出所能抛出异常的列表,写法如下:

这种写法表名函数可能会抛出int,double型或者A、B、C三种类型的异常,如果throw中为空,表明不会抛出任何异常,如果没有throw则可能抛出任何异常

C++ 标准库中有一些类代表异常,这些类都是从 exception 类派生而来的,如下图所示

bad_typeid:使用typeid运算符,如果其操作数是一个多态类的指针,而该指针的值为 NULL,则会拋出此异常,例如:

bad_cast:在用 dynamic_cast 进行从多态基类对象(或引用)到派生类的引用的强制类型转换时,如果转换是不安全的,则会拋出此异常

bad_alloc:在用 new 运算符进行动态内存分配时,如果没有足够的内存,则会引发此异常

out_of_range:用 vector 或 string的at 成员函数根据下标访问元素时,如果下标越界,则会拋出此异常

原文标题:《逆袭进大厂》之 C++ 篇 49 问 49 答(绝对的干货)

文章出处:【微信公众号:Linux爱好者】欢迎添加关注!文章转载请注明出处。

版权声明:本文为博主原创文章,遵循 版权协议,转载请附上原文出处链接和本声明。

1.首先建一个project,选择建Project,然后再选择file添加你所需要的类型文件。

记住选择完你需要的类型之后,继续的时候,要保存在你建的project下就行,保持默认。一定要选上debug和release,不然等会该文件是不能用的。

2.头文件有些不一样的地方,当你按上面做完之后,会发现头文件的字是灰色,此时是不能编译的,这里需要再介绍个菜单栏

3.将所有文件图标都点亮之后,就可以写程序了。

引用本程序头文件中的要用#include"  ",记住是双引号,<>是编译器自带的头文件。

对于头文件是“文件名.h”,对于同一编译单元下的cpp文件,除了函数接口外,就是用#include ”文件名.cpp“。

转的一个模型,可以用这个实例做一下,熟悉一下环境。

以下是一个cmp函数文件的.h和.cpp文件

但程序编译时会出现以下错误:

但程序在编译时会出现以下错误:

export 不能实现,将被忽略

但是以上的两种不支持并不影响模板在CodeBlocks中的使用,因为以下两种方式编写模板文件在CodeBlocks均可以正常使用。

1.只为模板文件建立一个.h文件,将实现代码全部放在.h文件之中。

2.将.h文件和.cpp文件分开,在使用时这样使用


假设我们有一些命名为enums的

我在google上搜索的是一个脚本(任何语言),它扫描我的项目中的所有头文件,并在每个enum中生成一个带有一个函数的头文件。

这个陷阱实际上是使用了typedefenums和无名C风格的enum。有人知道吗?

编辑:解决方案不应该修改我的源代码,除了生成的函数。枚举位于API中,因此使用到目前为止提出的解决方案是不可能的。

我要回帖

更多关于 datepart函数的使用 的文章

 

随机推荐