实例对象调用构造函数的静态方法会输出什么

JRE :英文名称(Java Runtime Environment),我们叫它Java运行环境,包括Java虚拟机和Java程序所需的核心类库等。如果想要运行一个开发好的Java程序,计算机只需要安装JRE即可。

JDK :英文名称(Java Development Kit),JDK是提供给Java考法人员使用的,其中包含了Java的开发工具,也包括了JRE。所以安装了JDK,就不用了在单独安装JRE了。其中的开发工具:编译工具(javac.exe)、打包工具(jar.exe)等。

2.Java中一个char类型是否可以存储一个汉字?

java采用unicode,2个字节(16位)来表示一个字符, 无论是汉字还是数字字母,或其他语言,char 在java中是2个字节。所以可以存储中文。

4.自动类型转换规则是什么?

规则1:如果一个操作数为double型,则整个表达式可提升为double型

规则2:满足自动类型转换的条件

数值类型(整型和浮点型)互相兼容

5.运算符优先级顺序是什么?

算术运算符>关系运算符>逻辑运算符

6.if和switch选择结构的区别是什么?

都是用来处理多分支条件的结构

switch选择结构只能处理等值条件判断的情况

多重if选择结构适合某个变量处于某个连续区间时的情况

b.初始情况不满足循环条件时

While循环一次都不会执行

continue一般用于循环结构中

b.作用(循环结构中)

Break语句终止本层循环,程序跳转到循环块外的下一条语句

Continue跳出本次循环,进入下一次循环

3.赋值(边声明边赋值;动态从键盘录入信息赋值)

10.类和对象的关系?

1.类是一个抽象的概念,它不存在于现实中的时间/空间里,类只是为所有的对象定义了抽象的属性与行为。

2.对象是类的一个具体。它是一个实实在在存在的东西。

*简单来说:类是对象的模板,对象是类的实例化。

11.类的开发步骤是什么?

12.局部变量和成员变量的区别是什么?

成员变量作用在整个类内都是可见的;局部变量仅限于它定义的方法内。

会为成员变量赋初值;不会给局部变量赋初值。

在同一个类中,成员变量和局部变量同名时,局部变量具有更高的优先级。

成员变量类内方法外,局部变量在方法内

13.静态变量和实例变量的区别?

a.静态变量是被 static 修饰符修饰的变量,在内存中只有一个内存空间,在加载类的过程中完成静态变量的内存分配,可以直接通过(类名.)来访问。

b.实例变量必须依存于某一实例,需要先创建对象然后通过对象才能访问到它,每创建一个实例对象,就会为实例变量分配不同的内存,各个对象访问自己的实例变量。

c.无论创建了一个类的多少个对象,静态变量只初始化一次,所有的实例都可以访问此静态变量,而且可以通过类名直接访问。

不可以。String 类是由final修饰的,所以不可以被继承。

15.String类中常用的方法都有哪些?

获得字符串的长度:length()

将所有英文字符转换为小写字母:toLowerCase()

将所有英文字符转换为大写字母:toUpperCase( )

String:适用于少量的字符串操作的情况

StringBuilder:线程不安全,适用于单线程下在字符缓冲区进行大量操作的情况

StringBuffer:线程安全,效率低适用多线程下在字符缓冲区进行大量操作的情况

(操作就是对字符串的拼接)JDBC中拼接sql

17.你对面向对象4大特性的理解?

封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。(减少代码冗余)

继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。

多态:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)

抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。

18.方法的重载和重写的区别是什么?(方法的重载是发生在一个类里面的,方法的重写是发生在父子类之间的)

方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;

重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求。

1.方法名一致,参数列表中参数的顺序,类型,个数不同。

2.重载与方法的返回值无关,发生在一个类里面的。

3.可以抛出不同的异常,可以有不同修饰符。

1.参数列表、返回类型必须完全与被重写方法的一致。

2.构造方法不能被重写,声明为 final 的方法不能被重写,声明为 static 的方法不能被重写,但是能够被再次声明。

3. 发生在父子类之间的,访问权限子类不能严于父类。

20.多重继承下初始化顺序是什么?

先父类后子类,先属性后构造方法。

21.使用运行时多态开发的思路是什么?

b.编写子类,子类重写父类方法

c.运行时,使用父类的类型,子类的对象

22.类中向上转型和向下转型的区别是什么?

向下转型:父类转换为子类(使用instanceof强制类型转换)(对象 instanceof 类或接口)

向上转型:子类转换为父类(自动进行类型转换)使用父类的类型,子类的对象

23.接口和抽象类的区别?

1.继承实现方面((抽象类单继承,接口多实现)接口也可以继承其他接口)

①.抽象类中可以有普通成员变量,接口中没有普通成员变量

②.抽象类和接口中都可以包含静态成员变量

①.抽象类可以有构造方法,接口中不能有构造方法。

②.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。

③.抽象类中的抽象方法的访问类型可以是public,protected默认类型 , 但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。

④.抽象类中可以包含静态方法,接口中不能包含静态方法。

抽象类所体现的是一种继承关系,要想使得继承关系合理,父类和派生类之间必须存在"is-a"关系,即父类和派生类在概念本质上应该是相同的。对于接口则不然,并不要求接口的实现者和接口定义在概念本质上是一致的,仅仅是实现了接口定义的契约而已,"has-a"的关系。

接口在设计时要注意稳定性,不能随意改动方法的参数和返回值。

抽象层次不同,抽象类是对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。抽象类是自底向上抽象而来的,接口是自顶向下设计出来的。

24.java中常见的异常类型?

25.多重异常编写的注意事项?

a.排列catch语句的顺序:先子类后父类

b.发生异常时按顺序逐个匹配

c.只执行第一个与异常类型匹配的catch语句

(不要“吃异常”,父类兜底)

在catch中要输出异常信息和打印异常发生的堆栈信息,如果没有输出,则会造成异常发生了,但因为没有相关错误信息的输出,不知道异常发生的情况。这种情况简称“吃异常”。

Error类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢出等。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和预防,遇到这样的错误,建议让程序终止。

Exception类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。

运行时异常(Runtime Exception)编译能通过,但是一运行就终止了,程序不会处理运行时异常,出现这类异常,程序会终止。(bug的来源,出现运行时异常要排查)(父类RunTimeException)

受检查的异常(Checked Exception)要么用 try。。。catch 捕获,要么用 throws 字句声明抛出,交给它的父类处理,否则编译不会通过。(父类Exception)

final:用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。

finally:异常处理语句结构的一部分,表示总是执行。

finalize:Object 类的一个方法,在垃圾回收器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。这是一个被动的方法(其实就是回调方法),不需要我们调用。

a.语句用在方法体内,表示抛出异常,由方法体内的语句处理。

b.是具体向外抛出异常的动作,所以它抛出的是一个异常实例,执行 throw 一定是抛出了某种异常。

a.是声明异常语句是用在方法声明后面,表示如果抛出异常,由该方法的调用者来进行异常的处理。

b.主要是声明这个方法会抛出某种类型的异常,让它的使用者要知道需要捕获的异常的类型。

c. 表示出现异常的一种可能性,并不一定会发生这种异常

30.什么是装箱和拆箱,包装类的应用场景?

拆箱——包装类的变量可以被赋值为一个基本类型

装箱——基本类型的变量可以被赋值为一个包装类

主要应用在集合类中.(集合不允许存放基础数据类型,存放数字时,要用到包装类型)

a、集合不允许存放基础数据类型,存放数字时,要用包装类型;

b、包装类提供一系列实用的方法

①.List 和 Set 是存储单列数据的集合,Map 是存储键和值这样的双列数据的集合;

②.List 中存储的数据是有顺序,并且允许重复;

③.Map 中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的。

④.Set 中存储的数据是无序的,且不允许有重复,但元素在集合中的位置由元素的hashcode决定,位置是固定的(Set集合根据hashcode来进行数据的存储,所以位置是固定的,但是位置不是用户可以控制的,所以对于用户来说set中的元素还是无序的)

ArrayList实现了长度可变的数组,在内存中分配连续的空间。遍历元素和随机访问元素的效率比较高。

LinkedList采用链表存储方式。插入、删除元素时效率比较高。

33.如果一个HashMap存放有很多值,但是在不知道KEY是什么的情况下,如何遍历出所有的KEY和VALUE?

entrySet(),返回此映射中包含的映射关系(键-值)

取出所有key的集合、获取Iterator对象、取出key、根据key取出对应的值

34.进程和线程的区别?

进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。

线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程。

进程包含了线程,线程必须属于某个进程,进程中的多个线程共享进程的内存,二者之间是一对多的关系。

35.线程的实现方式有哪几种?

特点:编写简单,可直接操作线程;适用于单继承

特点:避免单继承局限性;便于共享资源

特点:需要实现call()方法,call方法与run方法不同,call有返回值.

36.多线程中调用start方法和run方法的区别?

调用run()方法:只有主线程一条执行路径;不启动新线程(普通方法调用)。

调用start()方法:多条执行路径,主线程和子线程并行交替执行;启动一个线程。

调用start方法就是线程启动,调用后线程进入了就绪状态,

Run方法编写了线程要实现的功能,一般不直接调用,由系统来调用,如果调用就是普通的方法调用。

37.多线程中的问题?

①.程序需要同时执行两个或多个任务。例如:手机淘宝中"我的界面"的后台程序,开启多个线程从不同系统获取数据,组合发给前端APP。

②.程序需要实现一些需要等待的任务,例如:导出过大的Excel文件。

③.需要一些后台运行的程序,例如:Java垃圾回收器程序。

1、出现线程安全问题:当多线程有共享数据的时候,由于一个线程对共享数据的操作尚未完成,其它线程就参与进来,于是就产生了数据篡改的问题。

2、如何解决线程安全的问题:要保证一个线程操作共享数据的时候,其它线程必须在外边等候,直到操作共享数据的线程执行完,其它线程才可以操作共享数据。

3、同步方法的锁就是当前对象this,同步代码块需要指定锁对象(必须保证唯一);相比较而言,同步代码块能够更加精准地控制所要管理的代码。

另:调试多线程的排查手段是多输出日志,不再是Debug,使用log4j来输出中间处理结果,来进行问题的排查。

Hashtable是线程安全的,效率低,键和值都不允许为空。

Hashmap是非线程安全的,效率高,键和值都允许为空。

Hashtable为什么效率低就是线程安全的:

线程安全是以牺牲效率为代价的,Hashtable是synchronized(一种同步锁,用来共享数据,一次仅有一个线程能够访问共享数据)的,所以线程安全,多个线程访问时不需要为他自己的方法实现同步。

Yield:(礼让)当前线程贡献出对CPU的使用权,但是当前线程还是有可能再次获得CPU的使用权。

Join:当出现该方法时,暂停当前线程,执行其他进程,等待其他线程执行完后再执行暂停的进程。

线程在一定条件下,状态会发生变化。线程一共有以下几种状态:

1、新建状态(New):新创建了一个线程对象。

2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权。即在就绪状态的进程除CPU之外,其它的运行所需资源都已全部获得。

3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。

4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。

(1)、等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒。

(2)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。

(3)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

41.获取Class对象的三种方法是什么?

Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。

能够动态获取类的信息以及动态调用对象的方法。

反射机制主要提供以下功能

在运行时判断任意一个对象所属的类。

在运行时构造任意一个类的对象。

在运行时判断任意一个类所具有的成员变量和方法。

在运行时调用任意一个对象的方法。

1、平时我们创建对象的时候,都是通过New某某类来创建的,但是通过反射技术,我们可以通过Class.forName(类的全路径)来创建实例,例如:加载JDBC驱动的时候,就是采用反射技术。

2、反射在平时开发中主要应用于框架层的开发,例如Spring框架内部就采用了反射技术来创建实例。

43.工厂模式和单例模式?

单例模式:应用该模式的类一个类只能有一个实例。它必须自行创建这个实例,必须自行向整个系统提供这个实例。从具体实现角度来说:一是单例模式的类只提供私有的构造函数;二是类定义中含有一个该类的静态对象私有对象;三是该类提供了一个静态的公有函数用于创建或获取它本身的静态私有对象。

工厂模式:就简单说,对象的创建由传统的自身创建交付给一个工厂类创建,然后自身通过工厂类的工厂方法获得对象即可,适用场景是对象的创建步骤繁琐,过程比较复杂。

工厂模式和单例模式的区别:

区别1:工厂模式可以创建不同种类的对象实例,单例模式只能创建一种的对象实例;

区别2:工厂模式创建的对象实例在内存中没有只能创建一份的要求,单例模式创建的对象实例在内存中只有一份。

首先创建一个子类对象,然后调用父类构造函数,将从父类继承过来的成员变量进行初始化,然后用子类的构造函数初始化成员变量------------------------是这样的吗?

问题是:如果父类中有不能被继承的成员变量,那么在子类的构造函数中用super(参数1,参数2)调用父类的构造函数时,仍然会通过super向不能被继承的成员变量赋值,可是有什么意义吗?



strcpy:将由source指针指示的C 字符串(包括结尾字符)复制到destination指针指示的区域中。该函数不允许source和destination的区域有重叠,当src指针指向为‘\0’时将会停止字符串的复制,同时,为了避免溢出,destination区域应该至少和source区域一样大。 strcpy只用于字符串复制,并且它不仅复制字符串内容之外,还会复制字符串的结束符;(是个深复制)

strncpy:复制source的前num字符到destination。如果遇到null字符(’\0’),且还没有到num个字符时,就用(num - n)(n是遇到null字符前已经有的非null字符个数)个null字符附加到destination。注意:并不是添加到destination的最后,而是紧跟着由source中复制而来的字符后面。下面举例说明:

memcpy:将source区域的前num个字符复制到destination中。该函数不检查null字符(即将null字符当作普通字符处理),意味着将复制num个字符才结束。该函数不会额外地引入null字符,即如果num个字符中没有null字符,那么destination中相应字符序列中也没有null字符。同strcpy的区别:允许将source中null字符后面的字符也复制到destination中,而strcpy和strncpy则不可以。

重载(Overloading):函数名相同,函数的参数个数、参数类型或参数顺序三者中必须至少有一种不同。函数返回值的类型可以相同,也可以不相同。发生在一个类内部。

重写(override):也叫做覆盖,一般发生在子类和父类继承关系之间。子类重新定义父类中有相同名称和参数的 虚函数。

重定义:也叫做隐藏,子类重新定义父类中有相同名称的非虚函数 (参数列表可以不同 ) ,指派生类的函数屏蔽了与其同名的基类函数。发生在继承中。

1、 被重写的函数不能是static的。必须是virtual的

2 、重写函数必须与被重写函数有相同的类型,名称和参数列表,返回的类型,否则不能称其为重写而是重载。

3 、重写函数的访问修饰符可以不同。尽管virtual是private的,派生类中重写改写为public,protected也是可以的

1)、必须具有不同的参数列表;

2)、可以有不同的返回类型,只要参数列表不同就可以了;

3)、可以有不同的访问修饰符;

4)、可以抛出不同的异常;

 a 、如果派生类的函数和基类的函数同名,但是参数不同,此时,不管有无virtual,基类的函数被隐藏。

   b 、如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有vitual关键字,此时,基类的函数被隐藏(如果相同有Virtual就是重写覆盖了)。

(1)普通的变量: 一般不考虑啥效率的情况下 可以在构造函数中进行赋值。考虑一下效率的可以再构造函数的初始化列表中进行。 

(2)static 静态变量: 在类内部声明,但是必须 在类的外部进行定义和初始化  static变量属于类所有,而不属于类的对象,因此不管类被实例化了多少个对象,该变量都只有一个。

(3)const常量:常量在类内部声明,但是定义和初始化只能在构造函数的初始化列表进行const常量需要在声明的时候即初始化) 

(4)Reference 引用型变量(也就是&): 引用型变量和const变量类似,需要在创建的时候即进行初始化。也是在初始化列表中进行。但需要注意用Reference类型。但是可以在类内部初始化的。

 总结起来,可以初始化的情况有如下四个地方:

2、在类的构造函数初始化列表中, 包括const对象和Reference对象。

3、在类的定义之外初始化的,包括static变量。因为它是属于类的唯一变量。

4、普通的变量可以在构造函数的内部,通过赋值方式进行。当然这样效率不高。 

28.(1)(2)构造函数为什么不能是虚函数

基类部分在派生类部分之前被构造,当基类构造函数执行时派生类中的数据成员还没被初始化。用一个基类指针指向一个子类对象,子类对象会先调用基类的构造函数,如果此时基类构造函数中的虚函数调用被解析成调用派生类的虚函数,而派生类的虚函数中又访问到未初始化的派生类数据,将导致程序出现一些未定义行为和bug。(同理析构函数中也是不能调用虚函数的,例如基类的析构函数中调用虚函数,派生类的析构函数在函数退出时先被调用,此时派生类已经没有内存资源了,再去调用基类的析构函数,此时如果析构函数中的虚函数被解析成派生类中的函数,也是不存在的。)

(2)虚函数对应一个指向vtable虚函数表的指针,这大家都知道,可是这个指向vtable的指针其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?所以构造函数不能是虚函数。

总结起来就是: 如果构造函数是虚函数,那么:      先要有虚函数表----->才能有虚构造函数;  但是问题在于????   对象必须靠构造函数才能实现实例化------>实例化后才能有内存去存虚函数表; 这样实现的顺序完全相反了!所以不行!!!

另外个原因是:虚函数的作用在于通过父类的指针调用它的时候能够变成调用子类的那个成员函数。而构造函数是在创建对象时自动调用的,不可能通过父类的指针去调用,因此就规定构造函数不能是虚函数。

29. 指针函数和函数指针

指针函数:本质是一个函数。函数返回类型是某一类型的指针

函数指针:是指向函数的指针变量,即本质是一个指针变量。

30.C++函数模板底层实现原理

函数模板:实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。 凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。

使用函数模板能减少不必要的重复

不使用的话会重复定义函数

函数模板和普通函数的区别:函数模板是不允许自动类型转换的,而普通函数允许自动类型转换

当函数模板和普通函数在一起时,调用规则如下:

    (1)函数模板可以像普通函数一样被重载

    (3)如果函数模板可以产生一个更好的匹配,那么选择模板

    (4)可以通过空模板实参列表的语法,限定编译器只通过模板匹配(max<>(a,b); //显示使用函数模板方法,则使用<>空的类型参数化列表

    (5)函数模板不提供隐式的类型转换,必须是严格的匹配。

函数模板底层的实现模式:

              编译器并不是把函数模板处理成能够处理任意类的函数;编译器从函数模板通过具体类型产生不同的函数;编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。

如果 func 函数能顺利执行到 delete 处当然是最理想的。如果由于某种原因导致函数在中途返回了,或者在还没执行到 delete 语句时发生了异常,那么就悲剧了,为指针pt分配的内存得不到释放,产生了一个经典的内存泄漏。如果 func 函数是一个执行频率较高的函数,那么就尴尬了…为了消除传统的内存分配方式上存在的隐患,C++提供了一些强大的智能指针模版类,

智能指针的核心思想是将堆对象的生存期用栈对象(这个栈对象就是智能指针)来管理,当new一个堆对象的时候,立刻用智能指针来接管;具体做法是:在构造函数中对资源初始化(用一个指针指向堆对象),在析构函数中调用delete对资源进行释放。由于智能指针本身就是一个栈对象,它的作用域结束的时候,自动调用析构函数,从而调用了delete释放了堆对象。-------->这样一个堆对象的生存期就由栈对象来管理了。

智能指针:就是智能/自动化的管理指针所指向的动态资源的释放。它是一个类,有类似指针的功能。 

从较浅的层面看,智能指针是利用了一种叫做RAII(资源获取即初始化)的技术对普通的指针进行封装,这使得智能指针实质是一个对象,行为表现的却像一个指针。智能指针的作用是防止忘记调用delete释放内存和程序异常的进入catch块忘记释放内存。另外指针的释放时机也是非常有考究的,多次释放同一个指针会造成程序崩溃,这些都可以通过智能指针来解决

智能指针典型的有着这四种:

1.  auto_ptr类 (独占所有权,转移所有权 ,C++98引入的,带有很大缺陷不建议使用)

  (1)auto_ptr没有使用引用计数,如果多个auto_ptr指向同一个对象,就会造成对象被删除一次以上的错误。因此一个对象只能由一个auto_ptr所拥有,在给其他auto_ptr赋值的时候,会转移这种拥有关系。所以,在赋值、参数传递的时候会转移所有权,因此不要轻易进行此类操作。

2.   (独占所有权,防拷贝)

 scoped_ptr的实现原理是防止对象间的拷贝与赋值。具体实现是将拷贝构造函数和赋值运算符重载函数设置为保护或私有,并且只声明不实现,并将标志设置为保护或私有,防止他人在类外拷贝,简单粗暴,但是也提高了代码的安全性。

unique_ptr “唯一 ” 拥有其所指对象,同一时刻只能有一个unique_ptr实例指向给定对象(通过禁止拷贝、只有移动所有权move()函数来实现)。在出现异常的情况下,动态资源能得到释放。

unique_ptr本身的生命周期从unique_ptr实例对象创建时开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)。unique_ptr指针与其所指对象的关系:在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、通过reset方法重新指定、通过release方法释放所有权、通过移动语义转移所有权。

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

 shared_ptr 的实现原理是通过引用计数来实现,智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象指针指向同一对象,拷贝或赋值时将引用计数加1,析构时只有当引用计数减到0才释放空间,否则只需将引用计数减1即可.

count)。智能指针类将一个计数器与类实例指向的对象相关联,引用计数跟踪该类有多少个对象的指针指向同一对象。每次创建类的新对象时,初始化指针就将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,析构函数减少引用计数(如果引用计数减至0,则删除基础对象)。

存在循环引用的时候会出现内存泄漏。

它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少.

智能指针应用在多线程的时候要注意事项:

在多线程环境下,引用计数的更新存在安全隐患-------我们可以在改变引用计数的时候加上一把互斥锁,防止多线程带来的隐患

32.   保护继承和私有继承,虚继承

虚继承:是指一个指定的基类,在继承体系结构中,将其成员数据实例共享给也从这个基类型直接或间接派生的其它类。

虚拟继承与普通继承的区别:

因此虚继承可以认为不是一种继承关系,而可以认为是一种组合的关系。

33. 可继承的类的实现需要注意什么问题(构造函数、析构函数)

 (1)    当一个类作为基类时,它的析构函数应该为。防止基类指针无法调用子类析构函数,造成内存泄漏

(2)在子类调用父类构造函数的时候,如果父类没有默认的构造函数,则子类的构造函数应当显式地调用父类的自定义构造函数;换句话说:子类在构造时,如果没有显式调用父类的构造函数,会默认先调用父类的默认构造函数,如果此时父类没有默认的构造函数,就会报错了。

子类调用父类带参数的构造方法,需要用初始化父类成员对象的方式;例如图中Base( a )对基类构造函数初始化了

在中,构造函数不会自动继承,只是如果子类没有写构造函数,那么系统会这个类自动添加一个,是一个空函数体,所以什么也没有做,接着就会调用父类的构造函数,再调用这个系统添加的子类默认构造函数。(但是很特殊的一点是,C++11里面  通过提供一条注明了直接基类的using声明语句来让派生类“继承”基类的构造函数的方法,这种方法要和常规的继承而来的方法区别开来。

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。

实现原理:利用stack上的临时对象生命期是程序自动管理的这一特点,将我们的资源释放操作封装在一个临时对象中。

RAII 的一般做法是这样的:在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:

(1)不需要显式地释放资源。 

(2)采用这种方式,对象所需的资源在其生命期内始终保持有效。

资源管理技术的关键在于:要保证资源的释放顺序与获取顺序严格相反。

35. 深拷贝和浅拷贝

深拷贝(值拷贝):拷贝的是内容,主要是看复制过程的时候,如果资源重新分配,这个过程就是深拷贝

浅拷贝(位拷贝):拷贝的是地址,没有重新分配资源,就是浅拷贝

36. 拷贝构造函数什么时候需要重写

(1)当构造函数涉及到动态存储分配空间时,要自己写拷贝构造函数,并且要深拷贝。

(2)如果你需要定义一个非空的析构函数,那么,通常情况下你也需要定义一个拷贝构造函数。

placement new :placement new允许在一个已经分配好的内存中构造一个新的对象。 在使用时需要我们传入一个指针,此时会在该指针指向的内存空间构造该对象,该指针指向的地址可以是堆空间,栈空间,也可以是静态存储区。

使用new操作符分配内存需要在堆中查找足够大的剩余空间,这个操作速度是很慢的,而且有可能出现无法分配内存的异常(空间不够)。placement new就可以解决这个问题,我们构造对象都是在一个预先准备好了的内存缓冲区中进行,不需要查找内存,内存分配的时间是常数;而且不会出现在程序运行中途出现内存不足的异常。所以,placement new非常适合那些对时间要求比较高,长时间运行不希望被打断的应用程序。

可以使用堆的空间,也可以使用栈的空间,所以分配方式有如下两种:

这里的 new才是placement new,其实是没有申请内存的,只是调用了构造函数,返回一个指向已经分配好的内存的一个指针,所以对象销毁的时候不需要调用delete释放空间,但必须调用析构函数销毁对象。

一旦这个对象使用完毕,你必须显式的调用类的析构函数进行销毁对象。但此时内存空间不会被释放,以便其他的对象的构造。

如果缓冲区在堆中,那么调用delete[] buf;进行内存的释放;如果在栈中,那么在其作用域内有效,跳出作用域,内存自动释放。


       对象池------通过对象复用的方式来避免重复创建对象,它会事先创建一定数量的对象放到池中,当用户需要创建对象的时候,直接从对象池中获取即可,用完对象之后再放回到对象池中,以便复用。这种方式避免了重复创建耗时或耗资源的大对象,大幅提高了程序性能

39. 函数模板与类模板区别,函数模板与模板函数,类模板与模板类

(1)函数模板的实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化 必须由程序员在程序中显式地指定。    即函数模板的实例化允许隐式调用和显式调用  ,而类模板只能显示调用;

函数模板的隐式调用和显示调用,MaxValue是个函数

函数模板,实际上是建立一个通用函数,它所用到的数据的类型(包括返回值类型、形参类型、局部变量类型)可以不具体指定,而是用一个虚拟的类型来代替(实际上是用一个标识符来占位),等发生函数调用时再根据传入的实参来逆推出真正的类型。这个通用函数就称为函数模板(Function Template)。

在函数模板中,数据的值和类型都被参数化了,发生函数调用时编译器会根据传入的实参来推演形参的值和类型。换个角度说,函数模板除了支持值的参数化,还支持类型的参数化。

一但定义了函数模板,就可以将类型参数用于函数定义和函数声明了。说得直白一点,原来使用  int、float、char 等内置类型的地方,都可以用类型参数来代替。

类模板在实例化时必须显式地指明数据类型,编译器不能根据给定的数据推演出数据类型

类模板在实例化时必须显式地指明数据类型,编译器不能根据给定的数据推演出数据类型

注意:模板头和类头是一个整体,可以换行,但是中间不能有分号。

一但声明了类模板,就可以将类型参数用于类的成员函数和成员变量了。换句话说,原来使用 int、float、char 等内置类型的地方,都可以用类型参数来代替。

(2)函数模板的重点是模板。表示的是一个模板,专门用来生产函数。(函数模拟板的实例化允许隐式调用和显式调用 )

(3)类模板的重点是模板。表示的是一个模板,专门用于产生类的模子。(类模板的实例化 必须由程序员在程序中显式地指定)

40. 定义全局变量需要注意哪些

(1)在程序中定义了全局变量,但是需要在定义之前使用的话:这时在定义之前用extern关键字对其进行声明

(2)在一个cpp文件中定义了全局变量,需要在其他文件中使用:这时需要在其他文件中需要使用的地方之前用extern声明下

(3)在一个cpp文件中定义了全局变量,但是仅仅需要在本文件中使用该变量:这是需要在定义的时候加上static关键字

41. 静态库和动态库及其区别,  C++部署动态库、静态库,程序如何去使用静态、动态链接库

库(library),库是写好的现有的,成熟的,可以复用的代码。

静态库:链接时会完整地拷贝到可执行文件中去,被多次使用就有多份冗余拷贝在链接的时候,随着目标文件(.obj)一起被链接器打包到最后生成的可执行文件中的库叫静态库。Windows上的静态库是.lib文件(但和dll文件的.lib文件是不同的)。

动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序可以共用,节省内存。在可执行程序被加载到内存中执行的时候,才会去加载的库叫做动态库。Widows上的动态库是dll文件(Dynamic Linked Library)。

静态库与动态库的区别:

1)静态库在程序的链接阶段被复制到了程序中,和程序运行的时候没有关系;动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用。

2)使用动态库系统只需载入一次动态库,不同的程序可以共享内存中相同的动态库的副本,相比于使用静态库节省了很多内存;而使用了静态库的程序会有多个副本在内存中时,它们所使用的库所占的内存也是多份,因此浪费空间。

3)在库需要升级的时候,使用动态库的程序只需要升级动态库就好(假设接口不变),而使用了静态库的程序则需要升级整个程序。

(2) C++部署动态库、静态库,

在Windows操作系统中,Visual Studio使用lib.exe作为库的管理工具,负责创建静态库和动态库。

在静态库情况下,函数和数据被编译进一个二进制文件(通常扩展名为*.LIB), Visual C++的编译器在处理程序代码时将从静态库中恢复这些函数和数据并把他们和应用程序中的其他模块组合在一起生成可执行文件。这个过程称为"静态链接",此时因为应用程序所需的全部内容都是从库中复制了出来,所以静态库本身并不需要与可执行文件一起发行。

  在动态库的情况下,有两个文件,一个是引入库(.LIB)文件,一个是DLL文件,引入库文件包含被DLL导出的函数的名称和位置,DLL包含实际的函数和数据,应用程序使用LIB文件链接到所需要使用的DLL文件,库中的函数和数据并不复制到可执行文件中,因此在应用程序的可执行文件中,存放的不是被调用的函数代码,而是DLL中所要调用的函数的内存地址,这样当一个或多个应用程序运行是再把程序代码和被调用的函数代码链接起来,从而节省了内存资源。从上面的说明可以看出,DLL和.LIB文件必须随应用程序一起发行,否则应用程序将会产生错误。

Window与Linux执行文件格式不同,在创建动态库的时候有一些差异:

在Windows系统下的执行文件格式是PE格式,动态库需要一个DllMain函数作为初始化的入口,通常在导出函数的声明时需要有_declspec(dllexport)关键字。

Linux下gcc编译的执行文件默认是ELF格式,不需要初始化入口,亦不需要函数做特别的声明,编写比较方便。

(3)程序如何去使用静态、动态链接库

动态链接库的使用和静态链接库类似,唯一不同的是需要将myStaticLibTest.dll文件和项目编译生成的执行文件:.exe文件放在一起,一般位于解决方案的debug或release文件夹中。

vs中的程序有debug和release两个版本,Debug通常称为调试版本,通过一系列编译选项的配合,编译的结果通常包含调试信息,而且不做任何优化,它为开发人员提供强大的应用程序调试能力。而Release通常称为发布版本,是为用户使用的,一般客户不允许在发布版本上进行调试。所以不保存调试信息,同时,它往往进行了各种优化,以期达到代码最小和速度最优。为用户的使用提供便利。

       debug程序通常比release程序要慢,尤其是处理视频图像方面release要比debug快很多。在release模式对程序进行调试的时候经常会遇到变量虽然初始化了,但是在查看其值的时候却发现是一个随机的数并不是初始化的值,有时候在对变量进行监视的时候了,会出现找不到变量的情况,原因大致如下:

而release的赋值近似于随机。如果你的程序中的某个变量没被初始化就被引用,就很有可能出现异常:用作控制变量将导致流程导向不一致;用作数组下标将会使程序崩溃;更加可能是造成其他变量的不准确而引起其他的错误。所以在声明变量后马上对其初始化一个默认的值是最简单有效的办法,否则项目大了你找都没地方找。代码存在错误在debug方式下可能会忽略而不被察觉到。debug方式下数组越界也大多不会出错,在release中就暴露出来了,这个找起来就比较难了。

      只有DEBUG版的程序才能设置断点、单步执行、使用 TRACE/ASSERT等调试输出语句。REALEASE不包含任何调试信息,所以体积小、运行速度快。

43. (1) main函数有没有返回值,分别针对什么情况?(2) main函数中的参数argc和argv含义及用法?(3) 如果出现异常,怎么捕获?(4)main函数一定是最先执行的吗?

         VS环境下mian函数的返回值可以是数值类型,如char,float,double或者long,但是绝不能是string这类不能强制转换成int的类型(VS环境下对main函数的返回值没有太严格的要求,只要可以强制转换为int的类型都可以作为返回值

argv 是 argument vector的缩写,表示传入main函数的参数序列或指针,并且第一个参数argv[0]一定是程序的名称,并且包含了程序所在的完整路径,所以确切的说需要我们输入的main函数的参数个数应该是argc-1个;

块抛出的任何类型的异常)除非try里面执行代码发生了异常,否则这里的代码不会执行 } 

第三点: throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。可以使用 throw 语句在代码块中的任何地方抛出异常。throw 语句的操作数可以是任意的表达式,表达式的结果的类型决定了抛出的异常的类型。

(4)实际上,所有的外部对象的构造函数都是先于main()函数执行的。如果要对类中的成员对象进行初始化,那么这些对象的构造函数也是在main()函数之前执行的如果在这些构造函数中还调用了其他函数的话,就可以是更多的函数先于main()函数之前运行。因此main()函数不一定是C++程序的第一个被执行的函数。

44. C++写的动态链接库能不能直接给C用,为什么?

动态链接库(Dynamic Link Library, dll):其实dll文件就是一堆函数的集合,我们只不过是给普通函数的定义加点东西罢了

最前面的 extern "C" 是为了防止不同编译器间的差异的,它的意思是以C语言方式命名,作用是让编译器知道要以C语言的方式编译和连接封装函数。   其中_declspec为什么要加这个条件编译呢?     因为这种技术也可能会用在由C头文件产生出的C++文件中,这样使用是为了建立起公共的C和C++文件,也就是保证当这个文件被用做C文件编译时,可以去掉C++结构,也就是说,extern "C"语法在C编译环境下是不允许的。

(1)用C++去调用C动态链接库:(不能直接去调用)

就可以让C++程序在进行调用的时候进行正确的编译--------在CDLL(用C写的动态链接库)中函数当然是使用C编译器的方式进行编译的,所以在调用程序中,在声明外部函数的时候,必须加上”C”,以使的这个C++程序,在编译的时候使用C编译的方法对这个外部函数声明进行编译,即直接使用函数名而不是一个经过处理的函数名,否则在编译运行的时候就会报链接错误。比如一个函数void fun(double d),C语言会把它编译成类似_fun这样的符号,C链接器只要找到该函数符号就可以链接成功,它假设参数类型信息是正确的。而C++会把这个函数编译成类似_fun_double或_xxx_funDxxx这样的符号,在符号上增加了类型信息,这也是C++可以实现重载的原因。

(2)用C去调用C++动态链接库:(不能直接去调用)

主要的思想就是将C++的动态库再封装一层,做一个中间接口库,对C++库进行二次封装,也就是说在封装的这一层编写C语言的函数接口,而接口中使用C++动态库提供的类;还是用到 extern "C"

45. 设计一个模板函数对常数类型输入返回1(深入------函数和类模板特例化,偏特化)

这里再进一步介绍 函数模板特例化  和   类模板的特例化与偏特化

特例化版本时,函数参数类型必须与先前声明的模板中对应的类型匹配,其中T为const char*。

(1)函数模板特例化:必须为原函数模板的每个模板参数都提供实参,且使用关键字template后跟一个空尖括号对<>,表明将原模板的所有模板参数提供实参。

本质:特例化的本质是实例化一个模板,而非重载它。特例化不影响参数匹配。参数匹配都以最佳匹配为原则。例如,此处如果是compare(3,5),则调用普通的模板,若为compare(“hi”,”haha”)则调用特例化版本(因为这个cosnt char*相对于T,更匹配实参类型),注意,二者函数体的语句不一样了,实现不同功能。

注意:普通作用于规则使用于特例化,即,模板及其特例化版本应该声明在同一个头文件中,且所有同名模板的声明应该放在前面,后面放特例化版本

(2)类模板特例化:原理类似函数模板,不过在类中,我们可以对模板进行特例化,也可以对类进行部分特例化。对类进行特例化时,仍然用template<>表示是一个特例化版本

按照最佳匹配原则,若T != sales_data,就用普通类模板,否则,就使用含有特定功能的特例化版本。

类模板的部分特例化:不必为所有模板参数提供实参,可以指定一部分而非所有模板参数,一个类模板的部分特例化本身仍是一个模板,使用它时还必须为其特例化版本中未指定的模板参数提供实参。此功能就用于STL源码剖析中的traits编程。详见C++primer 628页的例子。(特例化时类名一定要和原来的模板相同,只是参数类型不同,按最佳匹配原则,那个最匹配,就用相应的模板)

特例化类中的部分成员:可以特例化类中的部分成员函数而不是整个类。

左值:指的是既能够出现在等号左边也能出现在等号右边的变量(或表达式),指的是如果一个可以引用到某一个对象,并且这个对象是一块内存空间且可以被检查和存储,那么这个表达式就可以作为一个左值。

右值指的则是只能出现在等号右边的变量(或表达式),指的是引用了一个存储在某个内存地址里的数据。

a,b为左值,3,4为右值

47. cpp大规模对象分配,生命周期控制的难点(并发情况下)?

智能指针控制生命周期,小对象可以参考java的Spring直接搞成只读的,配合 (只有进程空间的各段的内容要发生变化时,才将父进程的内容复制一份给子进程)和原子操作尽量把临界区弄小;大规模内存分配的解决方案,go语言的tcmalloc参考,给每个线程弄自己的内存缓存区。

48. 如何用让cpp程序输出他本身的代码

 fstream提供了三个类,用来实现c++对文件的操作。(文件的创建、读、写)

(1)>>操作符会忽略前面的空白符和换行符,但不会越过后面的换行符和空白符

(2)get()方法不会略过任何符号

文件文件打开模式: 

ios::nocreate 打开一个文件时,如果文件不存在,不创建文件。

ios::noreplace 打开一个文件时,如果文件不存在,创建该文件

ios::trunc 打开一个文件,然后清空内容

常用的错误判断方法: 

(1)good() 如果文件打开成功;(2)bad() 打开文件时发生错误;(3)eof() 到达文件尾 

49.源代码到最后的可执行文件经历的过程   

1)预处理主要包含下面的内容:

a.对所有的“#define”进行宏替换;

c.处理“#include”指令,这个过程是递归的,也就是说被包含的文件可能还包含其他文件

d.删除所有的注释“//”和“/**/”

e.添加行号和文件标识

f.保留所有的“#pragma”编译器指令

    经过预处理后的  .i 文件不包含任何宏定义,因为所有的宏已经被替换,并且包含的文件也已经被插入到 .i 文件中。

 编译的过程就是将预处理完的文件进行一系列词法分析,语法分析,语义分析及优化后生成相应的汇编代码文件(.s文件)

    汇编器是将汇编代码转变成机器可以执行的代码,每一个汇编语句几乎都对应一条机器指令。最终产生   目标  文件    (.o文件或.obj文件)。

静态链接把要调用的库函数直接链接到目标程序成为可执行文件的一部分。换句话说,要调用的库函数在程序的exe文件中,该文件包含了运行时所需的全部代码。静态链接的缺点是当多个程序都调用相同的函数时,内存中会有多个这个函数的拷贝,所以浪费了内存资源。

动态链接所调用的库函数代码并没有拷贝到程序的可执行文件中,它仅仅在exe文件中加入了调用的函数所在文件模块(DLL)和调用函数在文件中的位置等信息链接进目标程序,仅当应用程序被装入内存开始运行时,才从DLL中寻找相应函数代码,因此需要相应DLL文件的支持

51.虚函数表会随着对象生成而复制吗?

不会的,包含虚函数的类才会有虚函数表, 同属于一个类的对象共享虚函数表, 但是有各自的_vptr.

    虚函数表实质是一个指针数组,里面存的是虚函数的函数指针。

我们知道在C语言中是没有class类这个概念的,但是有struct结构体,我们可以考虑使用struct来模拟;但是在C语言的结构体内部是没有成员函数的,如果实现这个父结构体和子结构体共有的函数呢?我们可以考虑使用函数指针来模拟。但是这样处理存在一个缺陷就是:父子各自的函数指针之间指向的不是类似C++中维护的虚函数表而是一块物理内存,如果模拟的函数过多的话就会不容易维护了。

53.memset函数的作用,有哪些参数

总的作用:将已开辟内存空间 s 的首 n 个字节的值设为值 c。

memset:作用是在一段内存块中填充某个给定的值,它是对较大的或进行清零操作的一种最快方法

volatile 和 const 是相对应的,const代表所修饰的变量是不变的,volatile代表所修饰的变量是随时都可能会变化的。

  volatile 指出 i 是随时可能发生变化的,每次使用它的时候必须从 i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在 b 中。而优化做法是,由于编译器发现两次从 i读数据的代码之间的代码没有对 i 进行过操作,它会自动把上次读的数据放在 b 中。而不是重新从 i 里面读。这样以来,如果 i是一个寄存器变量或者表示一个端口数据就容易出错,所以说 volatile 可以保证对特殊地址的稳定访问。注意,在 VC 6 中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。

55.如何让C++类不能被继承

(1)利用static 属性定义静态方法,在方法内部实现一个对象,然后返回它的指针

(2)将构造函数和析构函数设置成私有或者保护属性

我要回帖

更多关于 静态方法可以通过类的实例对象调用 的文章

 

随机推荐