const关键字的用法:
一、const修饰变量
1. int const a = 5;
2. const int a = 5; 这两种定义方法都是一样的,都是防止a的值发生改变。
二、const修饰指针
1. 指针是常量不可变 char * const p;
2. 指针指向的内容不可变 (1) char const * p; (2) const char * p;
3. 指针与指针指向的内容都不可变 const char * const p;
tips:
const在*左边表示const修饰的是指针p指向的内容;
const在*右边表示const修饰的是指针p;
三、函数中使用const
1. 用const修饰函数参数
(1) 修饰指针,可以防止指针被修改;
(2) 修饰普通类型,说明这个参数不应该被修改;
(3) 修饰引用类型,参数的值不能被修改, 也就失去了引用类型的效果,但传递对象时,可以起到不copy对象的目的。
2. 用const修饰函数返回值,说明函数的返回类型是const的,功能类似于函数参数
3. 用const修饰函数,说明函数不会修改成员变量的值
四、const修饰变量与普通变量的区别
用 const 修饰的变量,无论是全局变量还是局部变量,生存周期都是程序运行的整个过程。全局变量的生存周期为程序运行的整个过程这个是理所当然的。而使用 const 修饰过的局部变量就有了静态特性,它的生存周期也是程序运行的整个过程。我们知道全局变量是静态的,静态的生存周期就是程序运行的整个过程。但是用const修饰过的局部变量只是有了静态特性,并没有说它变成了静态变量。
五、const与define
const与define从功能上很像,但是又有明显的区别。
1、define是预编译指令,而const是普通变量的定义。define定义的宏是在预处理阶段展开的,而const定义的只读变量是在编译运行阶段使用的。
2、const定义的是变量,而define定义的是常量
define定义的宏在编译后就不存在了,它不占用内存,因为它不是变量,系统只会给变量分配内存。但const定义的常变量本质上仍然是一个变量,具有变量的基本属性,有类型、占用存储单元。
可以说,常变量是有名字的不变量,而常量是没有名字的。有名字就便于在程序中被引用,所以从使用的角度看,除了不能作为数组的长度,用const定义的常变量具有宏的优点,而且使用更方便。所以编程时在使用const和define都可以的情况下尽量使用常变量来取代宏。
3、const定义的是变量,而宏定义的是常量,所以const定义的对象有数据类型,而宏定义的对象没有数据类型。所以编译器可以对前者进行类型安全检查,而对后者只是机械地进行字符替换,没有类型安全检查。这样就很容易出问题,即“边际问题”或者说是“括号问题”。
C++11的新特性:
C++中的智能指针
智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。简要的说,智能指针利用了 C++ 的 RAII 机制,在智能指针对象作用域结束后,会自动做内存释放的相关操作,不需要我们再手动去操作内存。但是智能指针也不只是说的简简单单的自动释放内存这么简单,在使用上有一些坑需要注意。
C++ 中有四种智能指针:auto_pt、unique_ptr、shared_ptr、weak_ptr 其中后三个是 C++11 支持,第一个已经被 C++11 弃用且被 unique_prt 代替,不推荐使用。
vector,array和数组的区别
相同点:
1、三者均可以使用下表运算符对元素进行操作,即vector和array都针对下标运算符[]进行了重载
2、三者在内存的方面都使用连续内存,即在vector和array的底层存储结构均使用数组
不同点:
1、vector属于变长容器,即可以根据数据的插入删除重新构建容器容量(1.5倍或者2倍);但array和数组属于定长容量。
2、vector和array提供了更好的数据访问机制,即可以使用front和back以及at访问方式,使得访问更加安全。而数组只能通过下标访问,在程序的设计过程中,更容易引发访问错误。
3、vector和array提供了更好的遍历机制,即有正向迭代器和反向迭代器两种。
4、vector和array提供了size和判空的获取机制,而数组只能通过遍历或者通过额外的变量记录数组的size。
5、vector和array提供了两个容器对象的内容交换,即swap的机制,而数组对于交换只能通过遍历的方式,逐个元素交换的方式使用。
6、array提供了初始化所有成员的方法fill。
7、vector提供了可以动态插入和删除元素的机制,而array和数组则无法做到,或者说array和数组需要完成该功能则需要自己实现完成。
8、由于vector的动态内存变化的机制,在插入和删除时,需要考虑迭代的是否失效的问题。
static关键字的用法
1. static全局变量与普通的全局变量有什么区别 ?
全局变量(外部变量)的说明之前再冠以static 就构成了静态的全局变量。
全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。 这两者在存储方式上并无不同。
这两者的区别在于非静态全局变量的作用域是整个源程序, 当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。
static全局变量只初使化一次,防止在其他文件单元中被引用;
2. static局部变量和普通局部变量有什么区别 ?
把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。
static局部变量只被初始化一次,下一次依据上一次结果值;
3. static函数与普通函数有什么区别?
static函数与普通函数作用域不同,仅在本文件。只在当前源文件中使用的函数应该说明为内部函数(static修饰的函数),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件。
面向对象的三大特性
封装、继承、多态
封装(Encapsulation)
封装的目的是为了保证变量的安全性,使用者不必在意具体实现细节,而只是通过外部接口即可访问类的成员
如果不进行封装,类中的实例变量可以直接查看和修改,可能给整个代码带来不好的影响
因此在编写类时一般将成员变量私有化,外部类需要同getter和setter方法来查看和设置变量
继承(Inherit)
继承实际上也是为了提高代码的复用性和可扩展性,在定义不同类的时候存在一些相同属性,为了方便使用可以将这些共同属性抽象成一个父类,在定义其他子类时可以继承自该父类,减少代码的重复定义,子类可以使用父类中非私有的成员。
1、继承得到的新类称为“子类”或“派生类”。被继承的父类称为“基类”、“父类”或“超类”。
2、继承的过程是一个从一般到特殊的的过程。
3、继承概念的实现方式有二类:实现继承与接口继承。实现继承是指直接使用基类的属性和方法而无需额外编码的能力;接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力。
继承与接口与抽象类
1、接口可以继承接口,但用extends 而不是implement。
2、接口不能继承抽象类,抽象类可以实现(implement)接口。原因是接口的实现和抽象类的继承都要重写父类的抽象方法。而接口里只能有抽象方法,抽象类里则允许有抽象方法和非抽象方法。
3、抽象类可以继承实体类。
多态(Polymorphism)
多态性是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。这就意味着虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。
实现多态,有二种方式,覆盖,重载。
覆盖,是指子类重新定义父类的虚函数的做法。
重载,是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。
重载与重写的区别
方法的重写(override)两同两小一大原则:
方法名相同,参数类型相同,
子类返回类型小于等于父类方法返回类型,
子类抛出异常小于等于父类方法抛出异常,
子类访问权限大于等于父类方法访问权限。
方法的重载(overload)原则:
方法名相同,参数个数或类型必有一个不同。
返回值类型可相同,可不同。
访问权限可随意改变。
重写:
1、被覆盖的方法不能是final类型的。因为final修饰的方法是无法覆盖的。
2、被覆盖的方法不能为private。否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。
3、被覆盖的方法不能为static。所以如果父类中的方法为静态的,而子类中的方法不是静态的,但是两个方法除了这一点外其他都满足覆盖条件,那么会发生编译错误。反之亦然。即使父类和子类中的方法都是静态的,并且满足覆盖条件,但是仍然不会发生覆盖,因为静态方法是在编译的时候把静态方法和类的引用类型进行匹配。
当父类中的方法被重写了后,除非用super关键字,否则就无法再调用父类中的方法了。
重载:
可以发生在一个类本身亦可以发生在继承类,要求方法名一致,参数列表不同(参数名,参数个数,参数顺序),返回值无要求。
重写和重载的区别:
1、方法重写要求参数列表必须一样,而重载则要求必须不同。
2、方法重写只能用于子类重写父类方法 ,重载不仅可以重载父类的方法还可以重载自身的方法。
3、方法重写对方法的访问权限有特殊的要求,而重载则不作要求。
4、一个方法只能被子类重写一次,而一个方法能在改类本身或子类重载多次。
设计模式
总体来说设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
其实还有两类:并发型模式和线程池模式。
设计模式的六大原则:
总原则:开闭原则(Open Close Principle)
开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类等。
不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,如若不然,就应该把类拆分。
2、里氏替换原则(Liskov Substitution Principle)
里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科
历史替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。
3、依赖倒转原则(Dependence Inversion Principle)
这个是开闭原则的基础,具体内容:面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。
4、接口隔离原则(Interface Segregation Principle)
这个原则的意思是:每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。
5、迪米特法则(最少知道原则)(Demeter Principle)
就是说:一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。
最少知道原则的另一个表达方式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作为局部变量出现在类中。
6、合成复用原则(Composite Reuse Principle)
原则是尽量首先使用合成/聚合的方式,而不是使用继承。