int a
定义变量须要为变量在内存中分配存储空间c++
extern int a
声明不须要分配存储空间程序员
声明的目的是为了在定义以前使用,若是不须要在定义以前使用,那么就没有单独声明的必要算法
局部变量用static声明,变量由动态存储方式改变为静态存储方式。为该变量分配的存储空间会存在于整个程序运行过程当中。静态局部变量做用域局限于本函数内。数组
外部变量用static声明,使变量局部化(局部于本文件),但仍为静态存储方式。静态外部变量做用域局限于本文件内安全
static静态变量虽然和整个程序共生存期,可是做用域仍是须要看其定义的地方,当你在某个函数中定义一个变量,该变量做用域仅在该函数中。但你在文件开头定义一个全局变量,该变量做用域仅在该文件中。因此当你声明一个变量调用另外一个文件静态变量,编译器会报错的。数据结构
全局变量储存在静态存储区,局部变量存在于堆栈中。动态申请数据存在于(堆)中。函数
能,局部会屏蔽全局。要用全局变量,须要使用"::"post
能够,在不一样的C文件中以static形式来声明同名全局变量。前提是其中只能有一个C文件中对此变量赋初值,此时链接不会出错。this
变量的定义只能出现一次,不然会致使重复定义。但却能够声明屡次。全局变量定义在头文件中。当该头文件被多个c文件包含的话,就会致使重复定义。因此全局变量不能够定义在头文件中。spa
static全局变量:静态全局变量限制了其做用域,只在定义该变量的源文件内有效。在同一源程序的其它源文件中不能使用它。static全局变量只初始化一次,防止在其余文件单元中被引用。
普通全局变量:非静态全局变量的做用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。
static局部变量:变量由动态存储方式改变为静态存储方式。static局部变量只被初始化一次,下一次依据上一次结果值。
普通局部变量:仍是动态存储方式,存储在堆栈中。
static函数:做用域仅在本文件中,只在当前源文件中使用的函数应该说明为内部函数(static),内部函数应该在当前源文件中说明和定义。static函数在内存中只有一份。
普通函数:可在当前源文件之外使用,需应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件。普通函数在每一个被调用中维持一份拷贝。
auto自动变量:代表变量自动具备本地范围,在离开做用域,不管块做用域,文件做用域仍是函数做用域,变量都会被程序隐藏或自动释放。而后等你从新进入该做用域,变量又从新被定义和调用。使用auto变量优点是无需考虑变量是否被释放。
static静态变量:简单说就是在函数等调用结束后,该变量也不会被释放,保存的值还保留。即它的生存期是永久的,直到程序运行结束,系统才会释放,但也无需手动释放。
extern外部变量:它属于变量声明,extern int a和int a的区别就是,前者告诉编译器,有一个int类型的变量a定义在其余地方,若是有调用请去其余文件中查找定义。
关于extern变量声明使用,例如一个工程中:
Test1.cpp文件开头定义了int i =10
; //定义了一个全局变量
Test2.cpp文件中定义:extern int i
; //声明在另外一个编译单元有i变量
x++ > x+=1 > x=x+1
使用const关键字来声明变量,代表,内存被初始化后,程序便不能再对它进行修改。 在默认的状况下,全局变量的连接性为外部的,但const全局变量的连接性为内部的。也就是说,在C++看来,全局const定义就像使用了static说明符同样。
const int Months = 12; 此时,应该注意的是应该在声明中对const进行初始化,咱们应该避免以下的写法: const int Months; Months = 12;
strcpy提供了字符串的复制。即strcpy只用于字符串复制,而且它不只复制字符串内容以外,还会复制字符串的结束符。 strcpy函数的原型是:char* strcpy(char* dest, const char* src)
;
strcpy的风险:(strcpy自己没有什么风险,风险来源于传递进去的两个参数)
一、内存不够:strcpy(x,y),字符串y比x大的话,就越界了
二、没有结束符
三、拷贝自身
memcpy提供了通常内存的复制。即memcpy对于须要复制的内容没有限制,所以用途更广。
char *strcpy(char * dest, const char * src) // 实现src到dest的复制 {
if ((src == NULL) || (dest == NULL)) { //判断参数src和dest的有效性
return NULL;
}
char *strdest = dest; //保存目标字符串的首地址
while ((*strDest++ = *strSrc++)!='\0'); //把src字符串的内容复制到dest下
return strdest;
}
void *memcpy(void *memTo, const void *memFrom, size_t size) {
if ((memTo == NULL) || (memFrom == NULL)) {//memTo和memFrom必须有效
return NULL;
}
char *tempFrom = (char *)memFrom; //保存memFrom首地址
char *tempTo = (char *)memTo; //保存memTo首地址
while (size -- > 0) { //循环size次,复制memFrom的值到memTo中
*tempTo++ = *tempFrom++ ;
}
return memTo;
}
复制代码
strcpy和memcpy主要有如下3方面的区别。
一、复制的内容不一样。strcpy只能复制字符串,而memcpy能够复制任意内容,例如字符数组、整型、结构体、类等。
二、复制的方法不一样。strcpy不须要指定长度,它遇到被复制字符的串结束符"\0"才结束,因此容易溢出。memcpy则是根据其第3个参数决定复制的长度。
三、用途不一样。一般在复制字符串时用strcpy,而须要复制其余类型数据时则通常用memcpy
size_t类型是一个类型定义,一般将一些无符号的整形定义为size_t,好比说unsigned int或者unsigned long,甚至unsigned long long。每个标准C实现应该选择足够大的无符号整形来表明该平台上最大可能出现的对象大小。
size_t的定义在<stddef.h>, <stdio.h>,<stdlib.h>,<string.h>,<time.h>和<wchar.h>这些标准C头文件中,也出如今相应的C++头文件, 等等中,在使用size_t以前应该头文件中至少包含一个这样的头文件。
包含以上任何C头文件(由C或C++编译的程序)代表将size_t做为全局关键字。
根据定义,size_t是sizeof关键字(注:sizeof是关键字,并不是运算符)运算结果的类型。因此,应当经过适当的方式声明n来完成赋值: n = sizeof(thing);
参数中带有size_t的函数一般会含有局部变量用来对数组的大小或者索引进行计算,在这种状况下,size_t是个不错的选择。适当地使用size_t还会使你的代码变得如同自带文档。当你看到一个对象声明为size_t类型,你立刻就知道它表明字节大小或数组索引,而不是错误代码或者是一个普通的算术值。
相同点:均可用于申请动态内存和释放内存
不一样点:
函数malloc 的原型以下: void * malloc(size_t size)
用malloc 申请一块长度为length 的整数类型的内存,程序以下: int *p = (int *) malloc(sizeof(int) * length)
一、malloc 返回值的类型是void *,因此在调用malloc 时要显式地进行类型转换,将void * 转换成所须要的指针类型。
二、 malloc 函数自己并不识别要申请的内存是什么类型,它只关心内存的总字节数。
函数free 的原型以下: void free( void * memblock )
为何free 函数不象malloc函数那样复杂呢?这是由于指针p的类型以及它所指的内存的容量事先都是知道的,语句free(p)能正确地释放内存。若是p 是NULL 指针,那么free对p 不管操做多少次都不会出问题。若是p 不是NULL 指针,那么free 对p连续操做两次就会致使程序运行错误。
运算符new 使用起来要比函数malloc 简单得多,例如:
int *p1 = (int *)malloc(sizeof(int) * length)
int *p2 = new int[length]
这是由于new 内置了sizeof、类型转换和类型安全检查功能。对于非内部数据类型的对象而言,new 在建立动态对象的同时完成了初始化工做。若是对象有多个构造函数,那么new 的语句也能够有多种形式。
若是用new 建立对象数组,那么只能使用对象的无参数构造函数。例如:
Obj *objects = new Obj[100]
// 建立100 个动态对象
不能写成
Obj *objects = new Obj[100](1)
// 建立100 个动态对象的同时赋初值1
在用delete 释放对象数组时,留意不要丢了符号‘[]’。例如:
delete []objects
// 正确的用法
delete objects;
// 错误的用法
后者至关于delete objects[0],漏掉了另外99 个对象。
综上:
一、new自动计算须要分配的空间,而malloc须要手工计算字节数
二、new是类型安全的,而malloc不是,好比:
int* p = new float[2]
// 编译时指出错误
int* p = malloc(2*sizeof(float))
// 编译时没法指出错误
new operator 由两步构成,分别是 operator new 和 construct
三、operator new对应于malloc,但operator new能够重载,能够自定义内存分配策略,甚至不作内存分配,甚至分配到非内存设备上。而malloc无能为力
四、new将调用constructor,而malloc不能;delete将调用destructor,而free不能。
五、malloc/free要库文件支持,new/delete则不要。
new operator/delete operator就是new和delete操做符,而operator new/operator delete是函数。
new operator:
(1)调用operator new分配足够的空间,并调用相关对象的构造函数
(2)不能够被重载
operator new:
(1)只分配所要求的空间,不调用相关对象的构造函数。当没法知足所要求分配的空间时,则
->若是有new_handler,则调用new_handler,不然
->若是没要求不抛出异常(以nothrow参数表达),则执行bad_alloc异常,不然
->返回0
复制代码
(2)能够被重载
(3)重载时,返回类型必须声明为void*
(4)重载时,第一个参数类型必须为表达要求分配空间的大小(字节),类型为size_t
(5)重载时,能够带其它参数
在C++中,内存分红5个区,他们分别是:
堆:就是那些由new分配的内存块,他们的释放编译器不去管,由咱们的应用程序去控制,通常一个new就要对应一个delete。若是程序员没有释放掉,那么在程序结束后,操做系统会自动回收。
栈:就是那些由编译器在须要的时候分配,在不须要的时候自动清除的变量的存储区。里面的变量一般是局部变量、函数参数等。
自由存储区:就是那些由malloc等分配的内存块,他和堆是十分类似的,不过它是用free来结束本身的生命的。
全局/静态存储区:全局变量和静态变量被分配到同一块内存中,在之前的C++堆栈中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。
常量存储区:常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不容许修改(固然,你要经过非正当手段也能够修改,并且方法不少,取地址修改)
管理方式不一样
对于栈来说,是由编译器自动管理,无需咱们手工控制;对于堆来讲,释放工做由程序员控制,容易产生memory leak
空间大小不一样
通常来说在32位系统下,堆内存能够达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。可是对于栈来说,通常都是有必定的空间大小的,例如,在VC6下面,默认的栈空间大小是1M。(这个值能够经过编译器修改)
可否产生碎片不一样
对于堆来说,频繁的new/delete势必会形成内存空间的不连续,从而形成大量的碎片,使程序效率下降。对于栈来说,则不会存在这个问题,由于栈是先进后出的队列,他们是如此的一一对应,以致于永远都不可能有一个内存块从栈中间弹出,在他弹出以前,在他上面的后进的栈内容已经被弹出,详细的能够参考数据结构。
生长方向不一样
对于堆来说,生长方向是向上的,也就是向着内存地址增长的方向;对于栈来说,它的生长方向是向下的,是向着内存地址减少的方向增加。
分配方式不一样
堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,好比局部变量的分配。动态分配由 malloc 函数进行分配,可是栈的动态分配和堆是不一样的,他的动态分配是由编译器进行释放,无需咱们手工实现。
分配效率不一样
栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是 C/C++ 函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照必定的算法(具体的算法能够参考数据结构/操做系统)在堆内存中搜索可用的足够大小的空间,若是没有足够大小的空间(多是因为内存碎片太多),就有可能调用系统功能去增长程序数据段的内存空间,这样就有机会分到足够大小的内存,而后进行返回。显然,堆的效率比栈要低得多。
构造函数不能为虚函数,而析构函数能够且经常是虚函数。
构造函数不能为虚函数
1)从存储空间角度:
虚函数对应一个虚函数表vtable,这个vtable实际上是存储在对象的内存空间的。可是,若是构造函数是虚的,就须要经过vtable来调用,但是对象尚未实例化,也就是内存空间尚未,没法找到vtable,因此构造函数不能是虚函数。
即vtable是在构造函数调用后才创建,于是构造函数不可能成为虚函数。
2)从使用角度:
虚函数主要用于在信息不全的状况下,能使重载的函数获得对应的调用。构造函数自己就是要初始化实例,那使用虚函数也没有实际意义,因此构造函数没有必要是虚函数。
虚函数的做用在于经过父类的指针或者引用来调用它的时候可以变成调用子类的那个成员函数。而构造函数是在建立对象时自动调用的,不可能经过父类的指针或者引用去调用,所以也就规定构造函数不能是虚函数。
析构函数能够是虚函数,且经常如此
这个就好理解了,由于此时vtable已经初始化了;何况咱们一般经过基类的指针来销毁对象,若是析构函数不为虚的话,就不能正确识别对象类型,从而不能正确销毁对象。
在类的继承中,若是有基类指针指向派生类,那么用基类指针delete时,若是不定义成虚函数,派生类中派生的那部分没法析构。在类的继承体系中,基类的析构函数不声明为虚函数容易形成内存泄漏。因此若是你设计必定类多是基类的话,必需要声明其为虚函数。
在C++中,类的对象创建分为两种
一种是静态创建,如A a
另外一种是动态创建,如A* ptr=new A
这两种方式是有区别的:
静态创建类对象: 是由编译器为对象在栈空间中分配内存,是经过直接移动栈顶指针,挪出适当的空间,而后在这片内存空间上调用构造函数造成一个栈对象。使用这种方法,直接调用类的构造函数。
动态创建类对象: 是使用new运算符将对象创建在堆空间中。这个过程分为两步,第一步是执行operator new()函数,在堆空间中搜索合适的内存并进行分配;第二步是调用构造函数构造对象,初始化这片内存空间。这种方法,间接调用类的构造函数。
只能在堆上分配类对象——就是不能静态创建类对象,即不能直接调用类的构造函数。 当对象创建在栈上面时,是由编译器分配内存空间的,调用构造函数来构造栈对象。当对象使用完后,编译器会调用析构函数来释放栈对象所占的空间。编译器管理了对象的整个生命周期。若是编译器没法调用类的析构函数,状况会是怎样的呢?好比,类的析构函数是私有的,编译器没法调用析构函数来释放内存。因此,编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性,其实不光是析构函数,只要是非静态的函数,编译器都会进行检查。若是类的析构函数是私有的,则编译器不会在栈空间上为类对象分配内存。
所以,将析构函数设为私有,类对象就没法创建在栈上了。
代码以下:
class A {
public:
A(){}
void destory(){delete this;}
private:
~A(){}
};
复制代码
试着使用A a;来创建对象,编译报错,提示析构函数没法访问。这样就只能使用new操做符来创建对象,构造函数是公有的,能够直接调用。类中必须提供一个destory函数,来进行内存空间的释放。类对象使用完成后,必须调用destory函数。
没法解决继承问题。
若是A做为其它类的基类,则析构函数一般要设为virtual,而后在子类重写,以实现多态。 所以析构函数不能设为private。
还好C++提供了第三种访问控制,protected。 将析构函数设为protected能够有效解决这个问题,类外没法访问protected成员,子类则能够访问。
类的使用很不方便
使用new创建对象,却使用destory函数释放对象,而不是使用delete。 (使用delete会报错,由于delete对象的指针,会调用对象的析构函数,而析构函数类外不可访问。这种使用方式比较怪异。)
为了统一,能够将构造函数设为protected,而后提供一个public的static函数来完成构造,这样不使用new,而是使用一个函数来构造,使用一个函数来析构。
代码以下,相似于单例模式:
class A {
protected:
A(){}
~A(){}
public:
static A* create() {
return new A();
}
void destory() {
delete this;
}
};
复制代码
这样,调用create()函数在堆上建立类A对象,调用destory()函数释放内存。
只能在栈上分配类对象
只有使用new运算符,对象才会创建在堆上,所以,只要禁用new运算符就能够实现类对象只能创建在栈上。 虽然你不能影响new operator的能力(由于那是C++语言内建的),可是你能够利用一个事实:new operator 老是先调用 operator new,然后者咱们是能够自行声明重写的。
所以,将operator new()设为私有便可禁止对象被new在堆上。 代码以下:
class A {
private:
void* operator new(size_t t){} // 注意函数的第一个参数和返回值都是固定的
void operator delete(void* ptr){} // 重载了new就须要重载delete
public:
A(){}
~A(){}
};
复制代码
参数为引用,不为值传递是为了防止拷贝构造函数的无限递归,最终致使栈溢出。
若是拷贝构造函数中的参数不是一个引用,即形如CClass(const CClass c_class)
,那么就至关于采用了传值的方式(pass-by-value),而传值的方式会调用该类的拷贝构造函数,从而形成无穷递归地调用拷贝构造函数。所以拷贝构造函数的参数必须是一个引用。
须要澄清的是,传指针其实也是传值,若是上面的拷贝构造函数写成CClass(const CClass* c_class)
,也是不行的。事实上,只有传引用不是传值外,其余全部的传递方式都是传值。