C++

C++和C语言的区别:

(1)面向对象特性:C++是一种面向对象的编程语言,可以使用类、继承、多态等面向对象的特性。而C语言则不支持面向对象编程。

(2)C++支持函数重载和运算符重载:C++允许在同一个作用域内有多个同名函数,只要它们的参数不同即可。此外,C++还支持运算符重载,使得程序员可以自定义运算符的行为。C语言不支持函数重载和运算符重载。

(3)C++的标准库更加丰富:C++标准库包含了大量的类和函数,可以用于字符串处理、文件操作、容器等方面。而C语言的标准库相对较少。

(4)C++支持异常处理:C++支持异常处理机制,可以在程序中处理异常情况。C语言不支持异常处理。

(5)内存管理:C++对内存管理进行了改进,支持动态内存分配和释放,可以使用new和delete运算符。C语言只能使用malloc和free函数来完成内存分配和释放。

2:C++和C语言的联系

(1)虽然C++和C语言有很多区别,但它们也有很多共同之处,它们都是编译型语言,可以直接编译成机器语言。C++语言的语法基本上是C语言的超集,C++程序员可以直接使用C语言的代码,而C程序员也可以逐步过渡到C++。

(2)在实际应用中,C++和C语言也有很多联系。例如在操作系统开发中,C语言被广泛应用于内核开发,而C++则被用于开发操作系统的用户界面和应用程序。此外,在游戏开发、嵌入式系统和高性能计算等领域,C++和C语言都是常用的编程语言。

结构体struct和共同体union(联合)的区别?

结构体:将不同类型的数据组合成一个整体,是自定义类型

共同体:不同类型的几个变量共同占用一段内存

1)结构体中的每个成员都有自己独立的地址,它们是同时存在的;

共同体中的所有成员占用同一段内存,它们不能同时存在;

2)sizeof(struct)是内存对齐后所有成员长度的总和,sizeof(union)是内存对齐后最长数据成员的长度、

结构体为什么要内存对齐呢?

1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常

2.硬件原因:经过内存对齐之后,CPU的内存访问速度大大提升

#define和const的区别 ?

#define和const的区别

1)#define定义的常量没有类型,所给出的是一个立即数;const定义的常量有类型名字,存放在静态区域

2)处理阶段不同,#define定义的宏变量在预处理时进行替换,可能有多个拷贝,const所定义的变量在编译时确定其值,只有一个拷贝。

3)#define定义的常量是不可以用指针去指向,const定义的常量可以用指针去指向该常量的地址

4)#define可以定义简单的函数,const不可以定义函数

new、delete、malloc、free之间的关系 ?

new/delete,malloc/free都是动态分配内存的方式:

1)malloc对开辟的空间大小严格指定,而new只需要对象名

2)new为对象分配空间时,调用对象的构造函数,delete调用对象的析构函数

既然有了malloc/free,C++中为什么还需要new/delete呢?

运算符是语言自身的特性,有固定的语义,编译器知道意味着什么,由编译器解释语义,生成相应的代码。

库函数是依赖于库的,一定程度上独立于语言的。编译器不关心库函数的作用,只保证编译,调用函数参数和返回值符合语法,生成call函数的代码。

malloc/free是库函数,new/delete是C++运算符。对于非内部数据类型而言,光用malloc/free无法满足动态对象都要求。new/delete是运算符,编译器保证调用构造和析构函数对对象进行初始化/析构。但是库函数malloc/free是库函数,不会执行构造/析构

C++中delete和delete[]的区别 ?

1. delete 释放new分配的单个对象指针指向的内存;

1)对于简单类型,内存大小已确定,析构时系统可直接通过指针获取实际分配的内存空间并释放;

2)对于类对象数组,仅调用数组首对象的析构函数,剩下对象的空间不能被释放;

2. delete[] 释放new分配的对象数组指针指向的内存。

1)对于简单类型,效果同delete,可以释放数组内存空间;

2)对于类对象数组,将逐一调用数组中每个对象的析构函数,释放了指针指向的全部内存空间。

C++ 关键字static的作用?

1)函数体内: static 修饰的局部变量作用范围为该函数体,不同于auto变量,其内存只被分配一次,因此其值在下次调用的时候维持了上次的值

2)模块内:static修饰全局变量或全局函数,可以被模块内的所有函数访问,但是不能被模块外的其他函数访问,使用范围限制在声明它的模块内

3)类中:修饰成员变量,表示该变量属于整个类所有,对类的所有对象只有一份拷贝

4)类中:修饰成员函数,表示该函数属于整个类所有,不接受this指针,只能访问类中的static成员变量

注意和const的区别!!!const强调值不能被修改,而static强调唯一的拷贝,对所有类的对象

static 的第四个作用:C++中的类成员声明 static

1) 函数体内 static 变量的作用范围为该函数体,不同于 auto 变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;

2) 在模块内的 static 全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;

3) 在模块内的 static 函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;

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

5) 在类中的 static 成员函数属于整个类所拥有,这个函数不接收 this 指针,因而只能访问类的 static 成员变量。

类内:

6) static 类对象必须要在类外进行初始化,static 修饰的变量先于对象存在,所以static 修饰的变量要在类外初始化;

7) 由于 static 修饰的类成员属于类,不属于对象,因此 static 类成员函数是没有 this 指针的,this 指针是指向本对象的指针。正因为没有 this 指针,所以 static类成员函数不能访问非 static 的类成员,只能访问 static 修饰的类成员;

8) static 成员函数不能被 virtual 修饰,static 成员不属于任何对象或实例,所以加上 virtual 没有任何实际意义;静态成员函数没有 this 指针,虚函数的实现是为每一个对象分配一个 vptr 指针,而 vptr 是通过 this 指针调用的,所以不能为virtual;虚函数的调用关系,this->vptr->ctable->virtual function

#include #include "file.h" 的区别 ?

#include "file.h":用于非标准头文件

#include :用于标准头文件

是表象。

#include 在程序运行时是要从标准库路径开始

#include "file.h" 是从用户定义的路径下开始

简述C++定义和声明的区别 ?

声明的意义在于告诉编译器程序单元的存在。

定义则明确指示程序单元的意义。

C语言通过extern进行程序单元的声明。

一些程序单元(函数、结构体)在声明时可以省略extern。

严格意义上的声明和定义并不相同。

简述C++的内存管理机制 ?

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

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

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

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

  全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。

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

构造函数为什么一般不定义为虚函数?而析构函数一般写成虚函数的原因 ?

1、构造函数不能声明为虚函数

1)因为创建一个对象时需要确定对象的类型,而虚函数是在运行时确定其类型的。而在构造一个对象时,由于对象还未创建成功,编译器无法知道对象的实际类型,是类本身还是类的派生类等等

2)虚函数的调用需要虚函数表指针,而该指针存放在对象的内存空间中;若构造函数声明为虚函数,那么由于对象还未创建,还没有内存空间,更没有虚函数表地址用来调用虚函数即构造函数了

2、析构函数最好声明为虚函数

首先析构函数可以为虚函数,当析构一个指向派生类的基类指针时,最好将基类的析构函数声明为虚函数,否则可以存在内存泄露的问题。

如果析构函数不被声明成虚函数,则编译器实施静态绑定,在删除指向派生类的基类指针时,只会调用基类的析构函数而不调用派生类析构函数,这样就会造成派生类对象析构不完全。

子类析构时,要调用父类的析构函数吗?

析构函数调用的次序时先派生类后基类的。和构造函数的执行顺序相反。并且析构函数要是virtual的,否则如果用父类的指针指向子类对象的时候,析构函数静态绑定,不会调用子类的析构。

不用显式调用,会自动调用

阐述C++静态绑定和动态绑定 ? 

静态绑定和动态绑定是C++多态性的一种特性

1)对象的静态类型和动态类型

静态类型:对象在声明时采用的类型,在编译时确定

动态类型:当前对象所指的类型,在运行期决定,对象的动态类型可变,静态类型无法更改

2)静态绑定和动态绑定

静态绑定:绑定的是对象的静态类型,函数依赖于对象的静态类型,在编译期确定

动态绑定:绑定的是对象的动态类型,函数依赖于对象的动态类型,在运行期确定

只有虚函数才使用的是动态绑定,其他的全部是静态绑定

引用是否能实现动态绑定,为什么引用可以实现 ?

可以。因为引用(或指针)既可以指向基类对象也可以指向派生类对象,这一事实是动态绑定的关键。用引用(或指针)调用的虚函数在运行时确定,被调用的函数是引用(或指针)所指的对象的实际类型所定义的。

C++的浅拷贝与深拷贝详细阐述 ?

浅拷贝(默认拷贝函数):将原对象或原数组的引用直接赋给新对象,新数组,新对象/新数组只是原对象的一个引用。

深拷贝:创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是引用

深拷贝会在堆内存中另外申请空间来储存数据,从而解决了指针悬挂问题。当数据成员中有指针时,必须要用深拷贝

(1)如果拷贝的对象里的元素只有值,没有引用,那么深拷贝与浅拷贝是相同的。都会对原有对象复制一份,产生一个新对象,对新对象里的值进行修改不会影响原有对象,新对象与原对象完全分离开。

(2)如果拷贝的对象里的元素包含引用(像一个列表中储存着另一个列表,存的就是另一个列表的引用),那么浅拷贝和深拷贝是不同的。

浅拷贝虽然将原有对象复制一份,但是依然保存的是引用,所以对新对象里的引用里的值进行修改,依然会改变原对象里的列表的值,新对象与原对象并没有完全分离开。

深拷贝不同,它会将原对象里的引用也新创建一个,即新建一个列表,然后放的是新列表的引用,这样就可以将新对象和原对象完全分开。

2.为什么要用深拷贝

在改变新的数组(对象)时,不会改变原数组(对象)

3.有指针时,必须用深拷贝

当数据成员中含有指针时,必须用深拷贝

当用浅拷贝时,新对象的指针与原对象的指针指向了堆上的同一块儿内存,新对象和原对象析构时,新对象先把其指向的动态分配的内存释放了一次,而后原对象析构时又将这块已经释放过的内存再释放一次。对同一块动态内存执行2次以上释放的结果是未定义的,所有会导致内存泄漏或程序崩溃。

所以需要深拷贝来解决问题,当拷贝对象中有对其他资源(如堆、文件、系统等)的引用时(引用可以是指针或引用)时,对象开辟一块新的资源,而不再对拷贝对象中对其他资源的引用的指针或引用进行单纯的赋值。

class MyStrig

{

private:

char *str;

public:

MyString(const char *p=nullstr)//缺省构造函数

:str(nullptr)

{

if(p!=nullptr)

{

int len=strlen(p)+1;

str=new char[len];

strcpy_s(str,lrn,p);

}

}

MyString(const MyString& ms)//拷贝构造函数,深拷贝

{

int n = strlen(ms.str) + 1;

*str = new char[n];

strcpy_s = (str, n, ms.str);

//int *str

// this->str=new int(*ms.str)

}

~MyString()//析构函数

{

}

};

阐述C++的四种强制转换 ?

类型转化机制可以分为隐式类型转换和显示类型转化(强制类型转换)

(new-type) expression

new-type (expression)

隐式类型转换比较常见,在混合类型表达式中经常发生;四种强制类型转换操作符:

static_cast、dynamic_cast、const_cast、reinterpret_cast

1)static_cast :编译时期的静态类型检查

static_cast < type-id > ( expression )

该运算符把expression转换成type-id类型,在编译时使用类型信息执行转换,在转换时执行必要的检测(指针越界、类型检查),其操作数相对是安全的

2)dynamic_cast:运行时的检查

用于在集成体系中进行安全的向下转换downcast,即基类指针/引用->派生类指针/引用

dynamic_cast是4个转换中唯一的RTTI操作符,提供运行时类型检查。

dynamic_cast如果不能转换返回NULL

dynamic_cast转为引用类型的时候转型失败会抛bad_cast

源类中必须要有虚函数,保证多态,才能使用dynamic_cast(expression)

3)const_cast

去除const常量属性,使其可以修改 ; volatile属性的转换

4)reinterpret_cast

通常为了将一种数据类型转换成另一种数据类型

C++ 中的extern“C”作用 ? 

extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般之包括函数名。

这个功能十分有用处,因为在C++出现以前,很多代码都是C语言写的,而且很底层的库也是C语言写的,为了更好的支持原来的C代码和已经写好的C语言库,需要在C++中尽可能的支持C,而extern "C"就是其中的一个策略。

这个功能主要用在下面的情况:

C++代码调用C语言代码

在C++的头文件中使用

在多个人协同开发时,可能有的人比较擅长C语言,而有的人擅长C++,这样的情况下也会有用到

看一个简单的例子:

有moduleA、moduleB两个模块,B调用A中的代码,其中A是用C语言实现的,而B是利用C++实现的,下面给出一种实现方法

//moduleA头文件

#ifndef __MODULE_A_H //对于模块A来说,这个宏是为了防止头文件的重复引用

#define __MODULE_A_H

int fun(int, int);

#endif

//moduleA实现文件moduleA.C //模块A的实现部分并没有改变

#include"moduleA"

int fun(int a, int b)

{

return a+b;

}

//moduleB头文件

#idndef __MODULE_B_H //很明显这一部分也是为了防止重复引用

#define __MODULE_B_H

#ifdef __cplusplus //而这一部分就是告诉编译器,如果定义了__cplusplus(即如果是cpp文件, extern "C"{ //因为cpp文件默认定义了该宏),则采用C语言方式进行编译

#include"moduleA.h"

#endif

… //其他代码

#ifdef __cplusplus

}

#endif

#endif

//moduleB实现文件 moduleB.cpp //B模块的实现也没有改变,只是头文件的设计变化了

#include"moduleB.h"

int main()

{cout< }

C++ typdef和define区别 ?

#define是预处理命令,在预处理是执行简单的替换,不做正确性的检查

typedef是在编译时处理的,它是在自己的作用域内给已经存在的类型一个别名

typedef (int*) pINT;

#define pINT2 int*

效果相同?实则不同!实践中见差别:pINT a,b;的效果同int *a; int *b;表示定义了两个整型指针变量。而pINT2 a,b;的效果同int *a, b;表示定义了一个整型指针变量a和整型变量b。

C++将“引用”作为函数返回值类型的格式、好处和需要遵守的规则? 

问题

在函数定义的时候,经常看到使用引用作为函数的参数或者返回值,这样做的好处在哪里?

引用就是某一变量(目标)的别名,对引用的操作与对变量直接操作完全一样。

引用作为函数参数

作为函数参数时引用有种原因:

在函数内部会对此参数进行修改

提高函数调用和运行效率

关于第一点:我们都知道C++里提到函数就会提到形参和实参。函数的参数实质就是形参,不过这个形参的作用域只是在函数体内部,也就是说实参和形参是两个不同的东西,要想形参代替实参,肯定有一个值的传递。函数调用时,值的传递机制是通过“形参=实参”来对形参赋值达到传值目的,产生了一个实参的副本。即使函数内部有对参数的修改,也只是针对形参,也就是那个副本,实参不会有任何更改。函数一旦结束,形参生命也宣告终结,做出的修改一样没对任何变量产生影响。而如果我们将形参定义成引用,那么此时的赋值后形参只是实参的一个别名而已,此时函数体内对形参的任何修改都同样作用与实参。

关于第二点:可以结合第一点分析,形参是实参的引用,不用经过值的传递机制,已经有了实参值的信息。所以 没有了传值和生成副本的时间和空间消耗 。当程序对效率要求比较高时,这是非常必要的.

引用作为函数返回值

以引用返回函数值,定义函数时需要在函数名前加 &

用引用返回一个函数值的最大好处是:在内存中不产生被返回值的副本

#include

using namespace std;

float temp; //定义全局变量temp

float fn1(float r); //声明函数fn1

float &fn2(float r); //声明函数fn2

float fn1(float r) //定义函数fn1,它以返回值的方法返回函数值

{

temp=(float)(r*r*3.14);

return temp;

}

float &fn2(float r) //定义函数fn2,它以引用方式返回函数值

{

temp=(float)(r*r*3.14);

return temp;

}

int main()

{

float a=fn1(10.0); //第1种情况,系统生成要返回值的副本(即临时变量)

//float &b=fn1(10.0); //第2种情况,编译不通过,左值引用不能绑定到临时值

float &d=fn2(10.0); //第3种情况,系统不生成返回值的副本,可以从被调函数中返回一个全局变量的引用

float c=fn2(10.0); //第4种情况,变量c前面不用加&号,这种也是可以的

cout< return 0;

}

引用作为返回值,必须遵守一下规则:

不能返回局部变量的引用。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。

不能返回函数内部new分配的内存的引用。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一 个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。

引用与一些操作符的重载。流操作符<<和>>,这两个操作符常常希望被连续使用,例如:cout << "hello" << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。

C++中什么是野指针 ?

1.什么是野指针

所谓野指针(wild pointer),简单讲是指指向不可用的内存区域的指针。需要注意的一点是,野指针与NULL空指针是不同的。NULL指针一般比较好判断,直接用if (p==NULL)语句判断即可。但是野指针指向的是垃圾内存区域的指针,一旦使用往往会造成不可预测的结果,这种随机不可预测的结果才是最可怕的。

2.未初始化

造成野指针最常见的情况之一就是指针未被正确初始化。任何指针在被创建的时候,不会自动变成NULL指针,他的default值是随机的。所以一个比较好的习惯是,指针刚创建的时候,要么设置为NULL空指针,要么指向合理的内存区域。

看一下一个简单例子

void func() {

int* p;

cout<<*p< }

我们在main方法中调用该函数,会得到输出

1901647869

不难看出得到的就是一个随机值。

3.悬垂指针

悬垂指针也是野指针常见的一种。当我们显式地从内存中删除一个对象或者返回时通过销毁栈帧,并不会改变相关的指针的值。这个指针实际仍然指向内存中相同位置,甚至该位置仍然可以被读写,只不过这时候该内存区域完全不可控,因为你不能保证这块内存是不是被别的程序或者代码使用过。

void dangling_point() {

char *p = (char *)malloc(10);

strcpy(p, "abc");

cout< free(p);

if (p != NULL) cout< strcpy(p, "def");

}

我们执行free§语句时,p所指向的内存被释放,此时该内存区域变成不可用内存。但是,p此时指向的地址并没有发生改变,而且也不会为空,所以if(p != NULL)判断为真,会继续执行后面的语句。

但是strcpy(p, “def”); 这一句,虽然代码不会报错崩溃,但此时正在篡改动态内存区,会产生不可预料的结果,此操作相当危险,尤其是debug时候非常不好排查,往往会让人崩溃…

为了避免悬垂指针的问题,一般做法是在free/delete指针以后,再将p=NULL,这样就可以避免上述问题。

4.返回栈内存指针或引用

看一个例子

int* return_ret() {

int result = 1;

return &result;

}

int main(int argc, char const *argv[])

{

int *p = return_ret();

cout<<*p< cout<<*p< return 0;

}

首先,这段代码能编译通过。不过IDE会提醒warning:

test_code.cc:80:13: warning: address of stack memory associated with local variable 'result' returned [-Wreturn-stack-address]

return &result;

^~~~~~

1 warning generated.

什么意思?就是我们返回了一个局部变量的栈内内存地址。

代码也能正常运行:

32767

我们注意看,第一个打印语句,甚至还能输出"正确"的结果1。

但是到了第二个打印语句,此时输出的结果就不可控了,因为此时该内存区域的内容,已经发生了变化。此时该内存地址已经变得不"可靠",操作该内存区域将会相当危险。

5.特殊的空指针

空指针为一特殊指针,是唯一一个对任何指针类型都合法的指针值。为了提高程序可读性,标准库定义了一个与0等价的符号常量NULL。p = 0;p = NULL;都是把p置为空指针值,上面两种写法等价。

6.free操作的真相

以下部分内容来自网络:

内存管理有以下几个层次(从高到低):C程序 - C库(malloc)- 操作系统 - 物理内存

首先,操作系统保证每个进程都有独立的虚拟内存空间(32bit上应该是4G吧,一般进程也用不了这么多)。当然实际上物理内存是所有进程共享的,所以当你需要动态内存时,需要向操作系统申请,这时候虽然从你程序的角度,内存是连续的,其实是被操作系统映射到某一块物理内存而已。程序用完内存归还后,实际归还的部分可能被操作系统分配给其他进程。

要注意,上面说的“归还”是malloc库的行为。malloc库会使用一些策略来提高内存使用的效率,比如程序需要使用10K内存时,malloc实际可能上会申请1M,因为一次系统调用开销很大;再比如即使你调用了free“归还“了程序使用的内存,malloc库也可能并未真正把这些内存归还给操作系统,因为将来程序可能还会再申请动态内存。

malloc库有多种实现,我知道的一种是使用标记(tag)来存储内存的元信息。比如你申请了8个byte,得到的头指针地址是0x1001(实际内存为0x1001-0x1008),malloc会在0x1000(也就是头指针-1的位置)保存8,即这段内存的长度。等释放时,程序将头指针地址传给free,malloc库从头指针-1的位置发现需要释放的内存长度,释放内存(实际的操作可能只是将tag清空)。这就解释了:1. 为什么和malloc不同,free的参数只有一个头指针而不需要长度;2. free后内存实际上可能并未归还给操作系统。

所以,访问被(程序)释放的内存是一种undefined行为,就是说结果是不确定的。在malloc库未将此内存归还给操作系统也未进行下一次动态分配时,这块内存事实上仍属于程序。而当malloc库不清理归还的内存时(多数实现都是如此),你能访问到的值仍是原来的值。这和函数调用完毕而未清理栈帧、后续调用函数可以访问到之前已经设置的局部变量值是一个道理。

但是,当malloc库已经将内存归还给系统时,再去访问原来的地址(更别说写),由于这段地址已经不属于程序了,就会出现经典的segmentation fault。

说到底,这些现象还是C语言库为了更有效率的实现而妥协的结果

简述线程安全和线程不安全机制 ?

1、线程安全:

指多个线程在执行同一段代码的时候采用加锁机制,使每次的执行结果和单线程执行的结果都是一样的,不存在执行程序时出现意外结果。

2、线程不安全:

是指不提供加锁机制保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。

1、引起线程安全问题的原因:

线程安全问题都是由全局变量及静态变量引起的。

若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

2、解决多线程并发访问资源安全问题的方法:

(1)synchronized

synchronized关键字,就是用来控制线程同步的,保证我们的线程在多线程环境下,不被多个线程同时执行,确保我们数据的完整性,使用方法一般是加在方法上。

(2)Lock

Lock是在Java1.6被引入进来的,Lock的引入让锁有了可操作性,是指我们在需要的时候去手动的获取锁和释放锁,甚至我们还可以中断获取以及超时获取的同步特性,从使用上synchronized使用起来比Lock更方便。

C++中的基本数据类型及派生类型? 

1)整型 int

2)浮点型 单精度float,双精度double

3)字符型 char

4)逻辑型 bool

5)控制型 void

基本类型的字长及其取值范围可以放大和缩小,改变后的类型就叫做基本类型的派生类型。派生类型声明符由基本类型关键字char、int、float、double前面加上类型修饰符组成。

类型修饰符包括:

>short 短类型,缩短字长

>long 长类型,加长字长

>signed 有符号类型,取值范围包括正负值

>unsigned 无符号类型,取值范围只包括正值

简述C++友元函数和友元类 ?

友元的概念:

遵循一定规则而使对象以外的软件系统能够不经过消息方式而直接访对象内封装的数据成员的技术方法便是友元。

友元是面向对象系统与过程系统衔接纽带。

友元有:友元函数,友元成员,友元类

友元可以放在类的公有,私有,保护部分。

友元函数

1.友元函数可访问类的私有和保护成员,但不是类的成员函数

2.友元函数不能用const修饰

3.友元函数可以在类定义的任何地方声明,不受类访问限定符限制

4.一个函数可以是多个类的友元函数

在类的定义中用friend声明了一个外部函数或其他类的成员函数(public和private均可)后,这个外部函数称为类的友元函数。

1由干友元函数不是类的成员,所以没有this指针,访问该类的对象的成员时,必须使用对象名,而不能直接使用类的成员名。

虽然友元函数是在类中说明的,但其名字的作用域在类外,作用域的开始点在说明点、结束点和类名相同。因此,友元说明可以代替该函数的函数说明。

#include

using namespace std;

class Point {

private:

int x, y;

friend float dis(Point &p1, Point &p2);

//可以是public,也可是private

public:

Point(int i, int j) { //构造函数,带缺省参数值

x = i;

y = j;

}

void disp() {

cout << "(" << x << "," << y << ")";

}

};

float dis(Point &p1, Point &p2) { //友元函数的实现

//友元函数中可访问类的private成员

float d = sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));

return d;

}

int main() {

Point p1(1, 2), p2(4, 5); //声明两个Point类的对象p1和p2

p1.disp(); //显示点p1的信息

cout << "与";

p2.disp(); //显示点p2的信息

cout << "距离=" << dis(p1, p2) << endl; //利用友元函数计算两点举例

return 0;

}

友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。

友元关系是单向的,不具有交换性。

友元关系不能传递->如果B是A的友元,C是B的友元,则不能说明C时A的友元。

class Date; // 前置声明

class Time

{

friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成

员变量

public:

Time(int hour, int minute, int second):_hour(hour) , _minute(minute) , _second(second)

{}

private:

int _hour;

int _minute;

int _second;

};

class Date

{

public:

Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month) , _day(day)

{}

void SetTimeOfDate(int hour, int minute, int second)

{

// 直接访问时间类私有的成员变量

_t._hour = hour;

_t._minute = minute;

_t.second = second;

}

private:

int _year;

int _month;

int _day;

Time _t;

};

#include

using namespace std;

class Box

{

double width;

public:

friend void printWidth(Box box);

friend class BigBox;

void setWidth(double wid);

};

class BigBox

{

public :

void Print(int width, Box &box)

{

// BigBox是Box的友元类,它可以直接访问Box类的任何成员

box.setWidth(width);

cout << "Width of box : " << box.width << endl;

}

};

// 成员函数定义

void Box::setWidth(double wid)

{

width = wid;

}

// 请注意:printWidth() 不是任何类的成员函数

void printWidth(Box box)

{

/* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */

cout << "Width of box : " << box.width << endl;

}

// 程序的主函数

int main()

{

Box box;

BigBox big;

// 使用成员函数设置宽度

box.setWidth(10.0);

// 使用友元函数输出宽度

printWidth(box);

// 使用友元类中的方法设置宽度

big.Print(20, box);

getchar();

return 0;

}

友元成员

除了一般的函数可以作为某个类的友元外,另一个类的成员函数也可以作为某个类的友元。声明时需在友元成员函数前加上其所在的类名。定义友元成员的好处是两个类可以以某种方式相互合作、协调工作,完成某一任务。

另一个类的成员函数可以作为某个类的友元,只是声名友元函数时要加上成员函数所在的类名,称为友元成员。

声名:

friend 函数返回类型 类名::成员函数名(形参列表)

#include

using namespace std;

class date;

class time

{

public:

time(int, int, int);

void display(date &);

private:

int hour;

int minute;

int sec;

};

class date

{

public:

date(int, int, int);

friend void time::display(date &);

private:

int month;

int day;

int year;

};

time::time(int h, int m, int s)

{

hour = h;

minute = m;

sec = s;

}

void time::display(date &d)

{

cout << d.month << "." << d.day << "." << d.year << endl;

cout << hour << ":" << minute << ":" << sec << endl;

}

date::date(int m, int d, int y)

{

month = m;

day = d;

year = y;

}

int main ()

{

time t1(10, 51, 56);

date d1(3, 20, 2018);

t1.display(d1);

return 0;

}

简述C++函数库中一些实用的函数?

  1. __gcd(x, y)求两个数的最大公约数,如__gcd(6, 8)就返回2。
  2. reverse(a + 1, a + n + 1)将数组中的元素反转。a 是数组名,n是长度,跟 sort 的用法一样。值得一提的是,对于字符型数组也同样适用。
  3. unique(a + 1, a + n + 1)去重函数。跟sort的用法一样。不过他返回的值是最后一个数的地址,所以要得到新的数组长度应该这么写: _n = unique(a + 1, a + n + 1) - a - 1.

4.lower_bound(a + 1, a + n + 1, x); upper_bound(a + 1, a + n + 1, x)lower_bound是查找数组中第一个小于等于x的数,返回该地址,同理也是 pos = lower_bound(a + 1, a + n + 1, x) - aupper_bound是查找第一个大于x的数,用法和lower_bound一样复杂度是二分的复杂度,O(logn)。(其实就是代替了手写二分)

5.fill(a + 1, a + n + 1, x)例如int数组:fill(arr, arr + n, 要填入的内容);vector也可以:fill(v.begin(), v.end(), 要填入的内容);fill(vector.begin(), cnt, val); // 从当前起始点开始,将之后的cnt个元素赋值为val。memset(arr, val, cnt); // 在头文件里。

将数组a中的每一个元素都赋成x,跟memset的区别是,memset函数按照字节填充,所以一般memset只能用来填充char型数组,(因为只有char型占一个字节)如果填充int型数组,除了0和-1,其他的不能

简述C++函数调用的过程?

如下结构的代码

int main(void)

{

...

d = fun(a, b, c);

cout< ...

return 0;

}

调用fun()的过程大致如下:

main()

1).参数拷贝(压栈),注意顺序是从右到左,即c-b-a;

2).保存d = fun(a, b, c)的下一条指令,即cout<

3).跳转到fun()函数,注意,到目前为止,这些都是在main()中进行的; fun()=====

4).移动ebp、esp形成新的栈帧结构;

5).压栈(push)形成临时变量并执行相关操作;

6).return一个值;

7).出栈(pop);

8).恢复main函数的栈帧结构;

9).返回main函数; main()

C++左值和右值的概念 ?

左值与右值的定义:

一个 lvalue 是通常可以放在等号左边的表达式,左值

一个 rvalue 是通常只能放在等号右边的表达式,右值

一个 glvalue 是 generalized lvalue,广义左值

一个 xvalue 是 expiring lvalue,将亡值

一个 prvalue 是 pure rvalue,纯右值

左值 lvalue 是有标识符、可以取地址的表达式,最常见的情况有:变量、函数或数据成员的名字返回左值引用的表达式,如 ++x、x = 1、cout << ’ '字符串字面量如 "hello world"在函数调用时,左值可以绑定到左值引用的参数,如 T&。一个常量只能绑定到常左值引用,如 const T&。

反之,纯右值 prvalue 是没有标识符、不可以取地址的表达式,一般也称之为“临时对象”。最常见的情况有:返回非引用类型的表达式,如 x++、x + 1、make_shared(42)除字符串字面量之外的字面量,如 42、true

函数调用时:

左值: 可以绑定到左值引用的参数,如 T&

右值: 在 C++11 之前,右值可以绑定到常左值引用,如 const T&

C++形参与实参的区别? 

形参变量只有在被调用时才分配内存单元,在调用结束时, 即刻释放所分配的内存单元。因此,形参只有在函数内部有效。 函数调用结束返回主调函数后则不能再使用该形参变量。

实参可以是常量、变量、表达式、函数等, 无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值, 以便把这些值传送给形参。 因此应预先用赋值,输入等办法使实参获得确定值,会产生一个临时变量。

实参和形参在数量上,类型上,顺序上应严格一致, 否则会发生“类型不匹配”的错误。

函数调用中发生的数据传送是单向的。 即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。 因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。

当形参和实参不是指针类型时,在该函数运行时,形参和实参是不同的变量,他们在内存中位于不同的位置,形参将实参的内容复制一份,在该函数运行结束的时候形参被释放,而实参内容不会改变。

值传递:有一个形参向函数所属的栈拷贝数据的过程,如果值传递的对象是类对象 或是大的结构体对象,将耗费一定的时间和空间。(传值)

指针传递:同样有一个形参向函数所属的栈拷贝数据的过程,但拷贝的数据是一个固定为4字节的地址。(传值,传递的是地址值)

引用传递:同样有上述的数据拷贝过程,但其是针对地址的,相当于为该数据所在的地址起了一个别名。(传地址)

效率上讲,指针传递和引用传递比值传递效率高。一般主张使用引用传递,代码逻辑上更加紧凑、清晰

C++语言中int fun() 和 int fun(void)的区别?

在c中,int fun() 会解读为返回值为int(即使前面没有int,也是如此,但是在c++中如果没有返回类型将报错),输入类型和个数没有限制, 而int fun(void)则限制输入类型为一个void。 在c++下,这两种情况都会解读为返回int类型,输入void类型。

简述const成员函数的理解和应用?

const Stock & Stock::topval (②const Stock & s) ③const

处const:确保返回的Stock对象在以后的使用中不能被修改

处const:确保此方法不修改传递的参数 S

处const:保证此方法不修改调用它的对象,const对象只能调用const成员函数,不能调用非const函数

指针和const的用法

当const修饰指针时,由于const的位置不同,它的修饰对象会有所不同。

int *const p2中const修饰p2的值,所以理解为p2的值不可以改变,即p2只能指向固定的一个变量地址,但可以通过*p2读写这个变量的值。顶层指针表示指针本身是一个常量

int const *p1或者const int *p1两种情况中const修饰*p1,所以理解为*p1的值不可以改变,即不可以给*p1赋值改变p1指向变量的值,但可以通过给p赋值不同的地址改变这个指针指向。底层指针表示指针所指向的变量是一个常量。

int const *const p;

mutable

如果需要在const成员方法中修改一个成员变量的值,那么需要将这个成员变量修饰为mutable。即用mutable修饰的成员变量不受const成员方法的限制;

可以认为mutable的变量是类的辅助状态,但是只是起到类的一些方面表述的功能,修改他的内容我们可以认为对象的状态本身并没有改变的。实际上由于const_cast的存在,这个概念很多时候用处不是很到了。

C++__stdcall和__cdecl的区别? 

__stdcall

__stdcall是函数恢复堆栈,只有在函数代码的结尾出现一次恢复堆栈的代码;在编译时就规定了参数个数,无法实现不定个数的参数调用;

__cdecl

__cdecl是调用者恢复堆栈,假设有100个函数调用函数a,那么内存中就有100端恢复堆栈的代码;可以不定参数个数;每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用__stacall函数大

C++头文件中的 ifndef/define/endif 作用?

相同点:

它们的作用是防止头文件被重复包含。

不同点

ifndef 由语言本身提供支持,但是 program once 一般由编译器提供支持,也就是说,有可能出现编译器不支持的情况(主要是比较老的编译器)。

通常运行速度上 ifndef 一般慢于 program once,特别是在大型项目上, 区别会比较明显,所以越来越多的编译器开始支持 program once。

ifndef 作用于某一段被包含(define 和 endif 之间)的代码, 而 program once 则是针对包含该语句的文件, 这也是为什么 program once 速度更快的原因。

如果用 ifndef 包含某一段宏定义,当这个宏名字出现“撞车”时,可能会出现这个宏在程序中提示宏未定义的情况(在编写大型程序时特性需要注意,因为有很多程序员在同时写代码)。相反由于program once 针对整个文件, 因此它不存在宏名字“撞车”的情况, 但是如果某个头文件被多次拷贝,program once 无法保证不被多次包含,因为program once 是从物理上判断是不是同一个头文件,而不是从内容上。

全部评论

相关推荐

2 10 评论
分享
牛客网
牛客企业服务