C++关键字

一、static(静态)变量

  在C语言中,static的意思是静态,他有3个明显的做用:ios

  (1)在函数体内,静态变量具备“记忆”功能,即一个被声明为静态的变量在这一函数被调用的过程当中值维持不变。程序员

  (2)在模块内(但在函数体外),他的做用范围是有限制的,但若是一个变量被声明为静态的,那么该变量能够被模块内全部函数访问,但不能被模块外其余函数访问。它是一个本地的全局变量。若是一个函数被声明为静态的,那么其做用与仅在本文件内,它只能够被本文件内的其余函数调用,不能被模块外的其余函数调用,也就是说这个函数被限制在本地范围内使用。编程

  (3)内部函数应该在当前源文件中说明和定义,对于可在当前源文件之外使用的函数,应该在一个头文件中说明,使用这些函数的源文件要包含这个头文件。数组

  具体而言,static全局变量和普通的全局变量的区别在于static全局变量只初始化一次,这样作的目的是为了防止在其余文件单元中被引用。static局部变量和普通变量的区别在于它只被初始化一次。安全

 

  在C++中,在类内数据成员的声明前加上关键字static,该数据成员就是类内的静态数据成员。静态数据成员有如下特色:多线程

  (1)对于非静态成员,每一个类对象都有本身的复制品。而静态数据成员被看成是类的成员。不管这个类的对象被定义了多少个,静态数据成员在程序中也只有一份复制品,由该类型的全部对象又该类型的全部对象共享访问。函数

  (2)静态数据成员存储在全局数据去,定义时要分配控件,因此不能在类声明中定义。因为静态数据成员属于本类的全部对象共享,因此它不属于特定的类对象,在没有产生类对象时其做用域就可见,即在没有产生类的实例时,程序员也可使用它。性能

  (3)静态数据成员和普通数据成员同样听从Public、protected、private访问规则。测试

  (4)static成员变量的初始化是在类外,此时不能再带上static的关键字。private、protected的static成员虽然能够在类外初始化,可是不能在类外被访问。优化

   与全局变量相比,使用静态变量有如下两个优点:

  (1)静态数据成员没有进入程序的全局名字空间,所以不存在与程序中其余全局名字冲突的可能性。

  (2)能够实现信息隐藏。静态数据成员能够是private,而全局变量不能。

引伸问题:

一、为何static变量只初始化一次?

  由于静态变量具备记忆功能,初始化后,一直没被销毁,而是保存在内存区域中,因此不会再次初始化。

二、在头文件中定义静态变量,是否可行?为何?

  不可行,若是在头文件中定义静态变量,会形成资源浪费的问题,同时也会引发程序错误。

 

二、const有哪些做用

  常类型也被成为const类型,是指使用类型修饰符const说明的类型。const是C和C++中常见的关键字,在C语言中,它主要用于定义变量为常类型以及修饰函数参数和返回值,而在C++中还能够修饰函数的定义,定义类的成员函数。常类型的变量或对象的值是不能更新的。

  通常而言,const有如下几个方面的做用:

  (1)定义const变量,具备不可变性。例如:

const int MAX=100;
int Array[MAX];

   (2)进行类型检查,是编译器对处理内容有更多的连接,消除了一些隐患。例如:

void f(const int i){....}

   (3)避免意义模糊的数字出现,一样能够很方便的的进行参数的调整和修改。通红定义同样,能够作到不变则已,一变都变。如1中,若是想修改MAX的内容,子须要定义const int MAX=指望值便可。

  (4)保护被修饰的东西,防止被意外的修改,加强程序的健壮性。上例中,若是在函数体内修改了变量i的值,那么编译其就会报错。例如

void f(const int i)
{
i=10;
}

  上述代码对i赋值会致使编译出错。

  (5)为函数从在提供参考

class A
{
    void f(int i){...}    //定义一个函数
    void f(int i )const {...}//上一个函数的重载  
}    

  (6)节省空间,避免没必要要的内存分配。例如

#define Pi 3.1415926//该宏用来定义常量
const double Pi=3.1415926//此时并未将Pi放入只读存储器中
double i=Pi;     //此时为Pi分配内存,之后再也不分配
double I=Pi;      //编译期间进行宏替换,分配内存
double j=Pi;      //没有进行内存分配
double J=Pi;    //再次进行宏替换

  Const从汇编的角度来看,只是给出了对应的内存地址,而不是想#define同样给出的是当即数,因此const定义的常量在程序运行过程当中只有一份复制品,而#define定义的常量在内存中有若干个复制品。

  (7)提升程序的效率。编译器一般不为普通const常量分配存储空间,而是将他们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操做,使得它的效率也很高。

  

引伸问题:

 一、引伸1:什么状况下须要使用const关键字?

  (1)修饰通常常量。通常常量是指简单类型的常量。这种常量在定义时,修饰符const能够用在类型说明符,也能够用在类型说明符后。例如:

int const x=2;
或
const  int x=2;

  (2)修饰常数组。定义或说明一个常数组能够采用以下形式:

int const a[0]={1,2,3,4,5,6,7,8};
const int a[0]={1,2,3,4,5,6,7,8};

  (3)修饰常对象。常对象时指对象常量,定义格式以下:

class A;
const A a;
A const a;

  定义常对象时,一样要进行初始化,而且该对象不能再被更新,修饰符const 能够放在类名后面,也能够放在类名前面。

  (4)修饰常指针。

const int *A;//const 修饰指向的对象,A可变,A指向的对象不可变
int const *A;//const 修饰指向的对象,A可变,A指向的对象不可变
int *const A;//const 修饰指针A,A不可变,A指向的对象可变
const int *const A;//指针A和A指向的指针都不可变

  (5)修饰常引用。使用const修饰符也能够说明引用,被说明的引用,该引用所引用的对象不能被更新。其定义格式以下:

const double & v;

  (6)修饰函数的常参数。const修饰符也能够修饰函数的传递参数,格式以下:

void Fun(const int Var)

  告诉编译器Var在函数体中的没法改变,从而防止了使用者一些无心的或错误的修改。

  (7)修饰函数的返回值。const修饰符也能够修饰函数的返回值,返回值不可被改变,格式以下:

const int Fun1();
const MyClass Fun2();

  (8)修饰类的成员函数。const修饰符也能够修饰类的成员函数。格式以下:

class ClassName
{
  public:
  int Fun() const;  
};

  这y样,在调用函数Fun()时就不能修改类或对象的属性。

  (9)在另以链接文件中引用const常量。使用方式有:

extern const int i;
extern const int j=10;

  第一种用法是正确的。而第二种用法是错误的,常量不能够被再次赋值。另外,还须要注意,常量必须初始化,如 const int i=5.

 

 二、引伸2:什么是常引用?

  常引用也成为const引用。之因此引入常引用,是为了不在使用的引用时,在绝不知情的状况下改变了值,从而引发程序错误。常引用主要用于定义一个普通变量的只读属性的别名,做为函数的传入参数,避免实参在调用函数中被意外的改变。

  const引用的意思是指向const对象的引用,非const引用表示指向非const类型的引用。若是即要利用引用提升程序的效率,又要保护传递给函数的数据再也不函数中被改变,就应使用常引用。常引用申明方式:

const 类型标识符 & 引用名 = 目标变量名

  常引用的主要用途以下:

  1. 用做普通变量只读属性的别名。一般这个别名智能得到这个变量的值,而不能改变这个变量的值。
  2. 用做函数的形参。这样能够保证在函数内不会改变实参的值,因此参数传递时要尽可能使用常引用类型。

  若是是对一个变量进行引用,则编译器首先创建一个临时变量,而后将该变量的值置入临时变量中,对该引用的操做就是对该临时变量的操做,对变量的引用能够用其余任何引用来初始化,但不能改变。

  关于引用的初始化,通常须要注意如下问题:当初始化值是一个左值(能够取得地址)时,没有任何问题;而当初始化值不是一个左值时,则只能对一个常引用赋值,并且这个赋值有一个过程,首先将值隐式转换到类型T,而后将这个转换结果存放到一个临时对象里,最后用这个临时对象来初始化这个引用变量。例以下面两种使用方式:

一、double & dr=1;
二、const double &cdr=1;

  第一种方式错误,初始化值不是左值。而第二种方式正确,执行过程以下:

double temp = double(1);
const double & cdr = temp;

  若是对第1种使用方法进行相应的改造,也能够变为合法,例如:

const int ival = 1024;
一、const int & refVal = ival;
2. int & ref2 = ival;

  这里第一种方法是正确的,第二种是非法的。上例中,能够读取refVal的值,可是不能修改它。由于不能修改它,由于refVar的类型是const,任何对refVal的赋值都是不合法的(const引用是只读的,常量既不能做为左值的量,定义式中赋初值除外)。同时,const引用能够初始化为不一样类型的对象或者初始化为右值,如字面值常量,而非const引用只能绑定到该引用同类型的对象。例以下面的const引用都是合法的。

int i=42;
const int &r = 42;
const int &r2 = =r+i;

  在使用cosnt引用进行函数调用的时候,须要注意一个问题,例以下面函数声明

void bar(string &s);

  那么下面的表达式将是非法的:

bar("hello world")

  程序实例以下:

#include <iostream>
#include <string>

using namespace std;

void bar(string &s)
{
  cout<<s<<endl;
}

int main()
{
  var("hello world");
  return 0;
}

  程序输出为

    hello world

  缘由在于“hello world”会产生一个临时对象,而在C++中,临时对象是const类型的。所以上面的表达式就试图将一个const类型的对象转换为非const类型,这是非法的。引用型类型参数应该在能被定义为const的状况下,尽可能定义为const。

 

三、volatile在程序设计中的做用

   编译器优化的时候可能会出现问题,如当遇到多线程编程时,变量的值可能由于别的线程而改变了,而该寄存器的值不会相应改变,从而形成应用程序读取的值和实际的变量值不一致。例如:在本次线程内,当读取一个变量时,为提升存取熟读,编译其优化过程当中有时会先把变量量读取到一个寄存器内;当之后再取变量值时,就直接从寄存器中取值;当变量值再本线程里改变时,会同时把变量的新值复制到该寄存器中,以便保持一致。

  volatile是一个类型修饰符,它用来修饰被不一样线程访问和修改的变量。被volatile类型定义的变量,系统每次用到它时都是直接从对应的内存中提取,而不会利用cache中的原有值,以适应它的未知什么时候会发生的变化,系统对这种变量的处理不会作优化。因此,volatile通常用于修饰多线程间被多个任务共享的变量和并行设备硬件寄存器等。

  对于volatile关键字的做用,能够经过再代码中插入汇编代码,测试有无volatile关键字对程序最终代码的影响。

  首先创建一个voltest.cpp文件,输入下面的代码:

#include <stdio.h>
int main()
{
  int i=10;
  int a=i;
 print("i=%d\n",a);//下面汇编语句的做用是改变内存中i的值,可是又不让编译器知道
  _asm
{
   mov  dword ptr [ebp-4],20h
}
int b=i;
print('i=%d\n",b);
return 0;
}

  再debug调试版本模式运行程序,输出结果以下:

  i=10

  i=32

  在release模式下,输出结果以下:

  i=10

  i=10

  输出结果明显代表,在release模式下,编译器对代码进行了优化,第二次没有输出正确的i值。把i的声明加上volatile关键字,程序示例以下:

#include <stdio.h>
int main()
{
  volatile int i=10;
  int a=i;
 print("i=%d\n",a);//下面汇编语句的做用是改变内存中i的值,可是又不让编译器知道
  _asm
{
   mov  dword ptr [ebp-4],20h
}
int b=i;
print('i=%d\n",b);
return 0;
}

  分别在debug调试版本和release发布版本运行,输出以下所示:

i=10
i=32

  一个定义为volatile的变量是值这个变量可能会被一项不到地改变,这样编译器就不会去假设这个变量的值。准确的说,优化器在用到这个变量时,必须每次都当心的从新读取这个变量的值,而不是使用保存在寄存器里的备份。 

四、断言ASSERT()是什么

   ·ADDERT()通常被称为断言,它是一个调试程序时常用的宏。它定义在<assert.h>头文件中,一般用于判断程序中是否出现了非法的数据,在程序运行时它计算括号内的表达式的值。若是表达式的值为false(0),程序报告错误,总之运行,以避免致使严重后果,同时也便于查找错误;若是表达式的值不为0,则继续执行后后面语句。在此须要强调一点,ASSERT()捕获的是非法状况,而非错误状况,错误状况是必然存在的,而且月底ing须要作出相应的处理,而非法不是,它可能只是漏洞而已。

  其用法以下:

ASSERT(n!=0)
k=10/n

  须要注意的是,ASSERT()只在Debug版本中,编译的Release版本则被忽略。

  还须要注意的一个问题是ASSERT()与assert()的区别,ASSERT()是宏,而assert()是ANSIC标准中规定的函数,它与ASSERT()的功能相似,可是能够应用在Release版本中。

  使用assert()的缺点是,频繁的调用会极大的影响程序的性能,增长额外的开销。在调试结束后,能够在包含#include<assert.h>的语句以前插入#define NDEBUG来禁用assert()调用,示例代码以下:

#include<stdio.h>
#define NDEBUG
#include<assert.h>

  对于assert()的使用,须要注意如下几个方面:

 (1)在函数开始处检验传入参数的合法性。例如:

assert(nNewSize>=0);
assert(nNewSize<=MAX_BUFFER_SIZE);

  (2)每一个asset()通常只检验一个条件,而不对多个条件进行检验,由于同时检验多个条件时,若是断言失败,则没法直观的判断是哪一个条件失败。例如,assert(nOffset>=0&&noffset+nSize<=m_nInformationSize)就不是一种高效的方式。将其分开更好

assert(nOffset>=0);
assert(noffset+nSize<=m_nInformationSize);

  (3)不能使用改变环境的语句,由于assert只在DEBUG时生效,若是这么作,会使程序在真正运行时遇到问题。例如,assert(i++<100)就是错误的。若是执行错误,在执行以前i=100,那么这条语句就不会执行,i++这条命令就没有执行。而正确的写法是:

assert(i<100);
i++;

  (4)并不是全部的assert()都能代替过滤条件,对于有的地方,assert()没法达到条件过滤的目的。

  (5)通常在编程的时候,为了造成逻辑和视觉上的一致性,会将assert()与后面的语句之间空一行来隔开。

五、main()中带的参数argc与argv的含义是什么?

   C语言设计原则是把函数做为程序的构成模块。在C99标准中,容许main()函数没有参数,或者有两个参数(有些实现容许更多的参数,但这只是对标准的扩展)。

  命令有时用来启动一个程序的执行,如int main(int argc,char *argv[])

  其中第一个参数表示明亮行参数的数目,它是int型的;

  第二个参数argv是一个指向字符串的指针数组,因为参数的数目并无内在的限制,因此argv指向这组参数值(从本质上是一个数组)的第一个元素,这些元素中的每一个都是指向一个参数文本的指针。

 

六、前置运算和后置运算有什么区别?

  ++a表示取a的地址,增长它的内容,而后把值放到寄存器中。

  a++表示取a的地址,把它的值装到寄存器中,而后增长内存中a的值。

 

七、new/delete与malloc/free的区别是什么?

   在C++中,申请动态内存和释放内存动态,用new/delete与malloc/free均可以,并且他们的存储方式相同,new和malloc动态申请的内存都位于堆中,没法被操做系统自动收回,须要对应的delete和free来释放空间,同时对于通常的数据类型,如int、char型,他们的效果同样。

  malloc/free是C/C++语言的标准库函数,在C语言中须要头文件<stdlib.h>的支持,new/delete是C++的运算符。对于类的对象而言,malloc/free没法知足动态对象的要求,对象在建立的同时要自动执行构造函数,对象消亡以前要自动执行析构函数,而malloc/free再也不编译器控制权限以内,没法执行构造函数和析构函数。

  具体而言,new/delete与malloc/free的区别主要表如今如下几个方面:

  (1)new可以自动计算须要分配的的内存空间,而malloc须要手工计算字节数。例如

int *p1=new int [2]
int *p2=malloc(2*sizeof(int))

  (2)new与delete直接带具体类型的指针,malloc与free返回void类型的指针。

  (3)new是类型安全的,而malloc不是,例如,int *p=new float[2],编译时就会报错。而int *p=malloc(2*sizeof(float)),编译时就没法指出错误来。

  (4)new通常由两步构成,分别是new操做和构造。new操做对应于malloc,但new操做能够重载,能够自定义内存分配策略,不作内存分配,甚至分配到非内存设备上,而malloc不行。

  (5)new将调用构造函数,而malloc不能,delete将调用析构函数,而free不能。

  (6)malloc/free须要库文件stdlib.h支持,new/delete则不须要库文件支持。

示例程序以下:

  

#include "stdafx.h"
#include<iostream> using namespace std; class A { public: A() { cout<<"A is here!"<<endl; } ~A() { cout <<"A is dead!"<<endl; } private: int i; }; int main() { A *pA= new A; delete pA; return 0; }

  运行结果以下:

   须要注意的是,有资源的申请,就有资源的回收,不然就会出现资源泄露(也称内存泄漏问题)的问题,因此new/delete必须配对使用。并且delete和free被调用后,内存不会当即收回,指针也不会指向空,delete或free仅仅是告诉操做系统,这一块内存被释放了,能够用做其余用途。可是,因为没有从新对这块内存进行写操做,因此内存中的变量数值并无发生改变,出现野指针的状况。所以,释放完内存后,应该将指针指向位置为空。

  程序示例以下:

#include "stdafx.h"
#include<stdlib.h>
#include<stdio.h>
#include<string.h>

void TestFree()
{
	char *str = (char*)malloc(100);
	strcpy(str, "helo");
	free(str);
	if (str != NULL)
	{
		strcpy(str, "world");
		printf("%s", str);
	}
}
	int main()
	{
		TestFree();
		return 0;
	}

  程序运行结果以下:

  经过上例可值,free或delete调用后,内存其实并无释放,也没有为空,而是还存储有内容,因此在将free或delete调用后,还须要将其置为NULL才行。

  此时,便产生了一个问题,既然new/delete的功能彻底覆盖了malloc/free,为何在C++中没有取消掉malloc/free呢?

  由于C++常要调用C,而C只能用malloc/.free管理动态内存,因此保留了。

 

八、在C++中如何实现模板函数的外部调用

  export是C++新增的关键字,他的做用是实现模板函数的外部调用,乐视与extern关键字。为了访问其余关键字。为了访问其余代码文件中的变量或对象(包括基本数据类、结构和类)能够利用extern来使用这些变量或对象,但对于模板类型,则能够在头文件中声明模板类和模板函数,在代码文件中使用关键字export来定义具体的模板对象和模板函数,而后在其余用户代码中,包含声明头文件后,就可使用这些对象和函数了,使用方式以下:

extern int n;
extern struct Point p;
extern class A a;
export template <class T>class Stack<int>s;
export template <class T>void f(T&t){...}

九、在C++中,关键字explicit有什么做用?

  在C++中,以下声明是合法的/

class String
{
     String(const char *p;
     ...
}
String s1="hello";

  上例中,String s1="hello"会执行隐式转换,等价于String s1=String("hello").为了不这种状况的发生,C++引入了关键字explicit,他能够组织不该该容许的通过转换构造函数进行的隐式转换的发生,声明为explicit的构造函数不能在隐式转换中使用。

  在C++中,一个参数的构造函数(或者除了第一个参数外其他参数都有默认值的多参构造函数)通常具备两个功能:构造器和默认且隐含的类型转换操做符。因此AAA=XXX,刚好XXX的类型正好也是AAA单参数构造器的参数类型,这时候编译器就自动调用这个构造器,建立一个AAA的对象。而在某些状况下,却违背了程序员的本意。此时就要在这个构造器前面加上explicit修饰,指定这个构造器只能被明确的调用、使用,不能做为类型转换操做符被隐含的使用。

  程序代码以下:

  

class Test1
{
public:
	Test1(int n) { num = n; }
private:
	int num;
};
class Test2
{
public:
	explicit Test2(int n) { num = n; }//explicit(显示)构造函数
private:
	int num;
};

int main()
{
	Test1 t1 = 12;//隐式调用器构造函数,车功能
	Test2 t2 = 12;//编译错误,不能隐式调用其构造函数
	Test2 t3(12);//显示调用成功
	return 0;
}

  Test1的构造函数带一个int型的参数,Test t1=12会隐式转换成调用Test1的这个构造函数,而Test2的构造函数被声明为explicit(显示),这表示不能经过隐式转换调用这个构造函数,所以Test2 t2=12会出现编译错误。普通构造函数可以被隐式调用,而explicit构造函数之只能被显式调用。

 

 

十、C++中异常的处理方法以及使用哪些关键字?

   C++异常处理使用的关键字有try、catch、throw。C++中的异常处理机制只能处理有throw捕获的异常,没有捕获的将被忽略。使用try{}catch{}来捕获异常,把可能发生异常的代码放到try{}语句中,后面跟若干个catch(){}负责处理具体的异常类型,这样一组有try块和很多于一个catch块构成了一级异常捕获。若是本机没有带适当类型参数的catch块,将不能捕获异常,异常就会向上一级传递,函数调用出若是没有捕获住异常,则直接跳到更高一层的使用者,若是一直捕获该异常,C++就会使用默认的异常处理函数,该函数可能会让程序最终跳出main()函数并致使程序异常终止。

  catch的做用是捕获异常,finally 无论代码是否有异常都执行。try中若是有return,仍然须要执行finally语句。此种状况的执行过程以下:

(1)执行return返回语句(return以后的语句内容),计算返回值,暂存在一个临时变量中。

(2)执行finally语句块

(3)return原来已经计算获得的结果值。

  若是在finally区段中又调用一次return语句,则try区段中的返回值将会被掩盖,是的方法调用者获得的是finally区段中的返回值,这经常与程序编写的初衷相违背。