如果一个世界都能在弹指一挥间灰飞烟灭,一个人的终结也就应该如露珠滚下草叶般平静淡然。
const int *a
和指针常量 int * const a
区别const int a; int const a; const int *a; int *const a;
1.int const a和const int a均表示定义常量类型a。
2.常量指针是一个指针,读成常量的指针,指向一个只读变量。如int const *p
或const int *p
。
3.指针常量是一个不能给改变指向的指针。如int *const p
int main() { int b = 3; int c = 4; const int *p = &b; //等价于 int const *p = &b; p = &c; //修饰值,指针可变 //*p = 5;//error 修饰值,值不可变 cout << *p << endl; int a = 5; int * const q = &a; //修饰指针 //p = &c;//error修饰指针,指针不可变 *p = 5; //修饰指针,值可变 }
1.sizeof 是一个操作符,strlen 是库函数。
2.sizeof 的参数可以是数据的类型,也可以是变量,而 strlen 只能以结尾为‘\ 0‘的字符串作参数。
3.编译器在编译时就计算出了sizeof 的结果。而strlen 函数必须在运行时才能计算出来。并且 sizeof计算的是分配时数据类型占内存的大小,而 strlen 计算的是字符串实际的长度。
4.数组做 sizeof 的参数不退化,传递给 strlen 就退化为指针了。
5.sizeof 操作符的结果类型是 size_t,它在头文件中 typedef 为 unsigned int 类型。该类型保证能容纳实现所建立的最大对象的字节大小。
6.当适用一个结构类型或变量时, sizeof 返回实际的大小;当适用一静态地空间数组, sizeof 归还全部数组的尺寸;sizeof 操作符不能返回动态地被分派了的数组或外部的数组的尺寸。
1.new、delete 是操作符,可以重载,只能在 C++中使用。
2.malloc、free 是函数,可以覆盖,C、C++中都可以使用。
3.new 可以调用对象的构造函数,对应的 delete 调用相应的析构函数。
4.malloc 仅仅分配内存,free 仅仅回收内存,并不执行构造和析构函数
5.分配成功时:new、delete 返回的是某种数据类型指针,malloc、free 返回的是 void 指针。
6.分配失败时:malloc返回NULL,new默认抛出异常。
注意:malloc 申请的内存空间要用 free 释放,而 new 申请的内存空间要用 delete 释放,不要混用。因为两者实现的机理不同。
要理解static,就必须要先理解另一个与之相对的关键字auto,其实我们通常声明的不用static修饰的变量,都是auto的,因为它是默认的。auto的含义是由程序自动控制变量的生存周期,通常指的就是变量在进入其作用域的时候被分配,离开其作用域的时候被释放;而static就是不auto,变量在程序初始化时被分配,直到程序退出前才被释放;也就是static是按照程序的生命周期来分配释放变量的,而不是变量自己的生命周期;所以,像这样的例子:
void func() { int a; static int b; }
每一次调用该函数,变量a都是新的,因为它是在进入函数体的时候被分配,退出函数体的时候被释放,所以多个线程调用该函数,都会拥有各自独立的变量a,因为它总是要被重新分配的;
而变量b不管你是否使用该函数,在程序初始化时就被分配的了,或者在第一次执行到它的声明的时候分配(不同的编译器可能不同),所以多个线程调用该函数的时候,总是访问同一个变量b,这也是在多线程编程中必须注意的!
1.static可以修饰局部变量(静态局部变量)、全局变量(静态全局变量)和函数,被修饰的变量存储位置在静态区。
生命周期延长
,直到程序运行结束而非函数调用结束,且只在第一次被调用时定义;本文件中
可见;2.C++的static除了上述两种用途,还可以修饰类成员(静态成员变量和静态成员函数),静态成员变量和静态成员函数不属于任何一个对象,是所有类实例所共有。
3.static的数据记忆性可以满足函数在不同调用期的通信,也可以满足同一个类的多个实例间的通信。
4.未初始化时,static变量默认值为0。其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0×00,某些时候这一特点可以减少程序员的工作量。
1.引用只是别名,不占用具体存储空间,只有声明没有定义;指针是具体变量,需要占用存储空间。
2.引用在声明时必须初始化为另一变量,一旦出现必须为typename refname = &varname
形式;
3.指针声明和定义可以分开,可以先只声明指针变量而不初始化,等用到时再指向具体变量。
4.引用一旦初始化之后就不可以再改变(变量可以被引用为多次,但引用只能作为一个变量引用)而指针变量可以重新指向别的变量。
5.不存在指向空值的引用,必须有具体实体;但是存在指向空值的指针。
区别以下指针类型
int *p[10] int (*p)[10] int *p(int) int (*p)(int)
1.int *p[10]
表示指针数组,强调数组概念,是一个数组变量,数组大小为10,数组内每个元素都是指向int类型的指针变量。
2.int (*p)[10]
表示数组指针,强调是指针,只有一个变量,是指针类型,不过指向的是一个int类型的数组,这个数组大小是10。
3.int p(int)
是函数声明,函数名是p,参数是int类型的,返回值是int 类型的。
4.int (*p)(int)
是函数指针,强调是指针,该指针指向的函数具有int类型参数,并且返回值是int类型的。
#include <iostream> using namespace std; int main() { int a[5] = {1, 2, 3, 4, 5}; int *ptr = (int*)(&a+1); cout << *(ptr-1) << "\t" << *(ptr-2) << endl; // 5 4 cout << "----------------" << endl; int *p = (int *)(a+1); //2 cout << *p << endl; }
1.a是数组名,是数组首元素地址,+1表示地址值加上一个int类型的大小,如果a的值是0x00000001,加1操作后变为0x00000005。*(a + 1) = a[1]
。
2.&a是数组的指针,其类型为int(*)[5]
(就是前面提到的数组指针),其加1时,系统会认为是数组首地址加上整个数组的偏移(5个int型变量),值为数组a尾元素后一个
元素的地址。
3.若(int )p ,此时输出 *p时,其值为a[0]
的值,因为被转为int 类型,解引用时按照int类型大小来读取。
1.二者均可通过增减偏移量来访问数组中的元素。
2.数组名不是真正意义上的指针,可以理解为常指针,所以数组名没有自增、自减等操作。
3.当数组名当做形参传递给调用函数后,就失去了原有特性,退化成一般指针,多了自增、自减操作,但sizeof运算符不能再得到原数组的大小了。
1.也叫空悬指针,不是指向null的指针,是指向垃圾内存的指针。
2.产生原因及解决办法:
1.delete只会调用一次析构函数。
2.delete[]会调用数组中每个元素的析构函数。
3.char *p = new char[32];
如果用delete p,会发生什么?
1.申请方式不同。
2.申请大小限制不同。
ulimit -a
查看,由ulimit -s
修改。3.申请效率不同。
4.生长方向不同
C、C++中内存分配方式可以分为三种:
1.从静态存储区域分配:内存在程序编译时就已经分配好,这块内存在程序的整个运行期间都存在。速度快、不容易出错,因为有系统会善后。例如全局变量,static变量等。
2.在栈上分配:在执行函数时,函数内局部变量的存储单元都在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
3.从堆上分配:即动态内存分配。程序在运行的时候用 malloc 或 new 申请任意大小的内存,程序员自己负责在何时用 free 或 delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活。如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏,另外频繁地分配和释放不同大小的堆空间将会产生堆内碎块。
一个 C、C++程序编译时内存分为 5 大存储区:堆区、栈区、全局区、文字常量区、程序代码区。
1.声明仅仅是把变量的声明的位置及类型提供给编译器,并不分配内存空间;定义要在定义的地方为其分配存储空间。
2.相同变量可以再多处声明(外部变量extern),但只能在一处定义。
1.extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。
2.当它与”C”一起连用时,如:extern “C” void fun(int a, int b);
主要是解决在C++代码中调用C代码。告诉编译器,被extern “C”修饰的变量和函数,按照C语言方式编译和链接。
1.传值:形参是实参的拷贝,改变形参的值并不会影响外部实参的值。从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入,不能传出。
2.传指针:形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作
3.传引用:形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作,在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。
4.传指针和传引用区别:主要是指针和引用的区别。首先语法就不一样,指针要取值需要能*ptr,引用可以直接取; 另外传进来的指针可以指向其他位置,但是引用只能绑定在传进来的固定的值上。
1.bool类型:if(flag)
2.int类型:if(flag == 0)
3.指针类型:if(flag == nullptr)
4.float类型:if((flag >= -0.000001) && (flag <= 0. 000001))
1.可以,但含有指针成员时需要注意。
2.对比类的对象赋值时深拷贝和浅拷贝。
1.修饰普通变量: 该变量值不可更改。顶层/底层const
2.修饰函数参数: 函数形参声明加const保护某些值在操作过程中不会改变
3.修饰返回值:表明返回的数据是不可修改的
4.修饰成员函数: 类的成员函数加上const限定可以声明此函数不会更改类对象的内容
1.宏在编译时完成替换,之后被替换的文本参与编译,相当于直接插入了代码,运行时不存在函数调用,执行起来更快;函数调用在运行时需要跳转到具体调用函数。
2.宏定义没有返回值;函数调用具有返回值。
3.宏定义参数没有类型,不进行类型检查;函数参数具有类型,需要检查类型。
1.在使用时,宏只做简单字符串替换(编译前)。而内联函数可以进行参数类型检查(编译时),且具有返回值。
2.内联函数本身是函数,强调函数特性,具有重载等功能。
3.内联函数可以作为某个类的成员函数,这样可以使用类的保护成员和私有成员。而当一个表达式涉及到类保护成员或私有成员时,宏就不能实现了。
1.宏替换发生在预编译阶段,属于文本插入替换;const作用发生于编译过程中。
宏不检查类型;const会检查数据类型。
2.宏定义的数据没有分配内存空间,只是插入替换掉;const变量分配内存空间。
3.宏不是语句,不在最后加分号;
1.宏主要用于定义常量及书写复杂的内容;typedef主要用于定义类型别名。
2.宏替换发生在编译阶段之前,属于文本插入替换;typedef是编译的一部分。
3.宏不检查类型;typedef会检查数据类型。
4.宏不是语句,不在最后加分号;typedef是语句,要加分号标识结束。
5.注意对指针的操作,typedef (char*) p_char
和#define p_char char*
在使用起来区别巨大。
1.可以通过加#define,并通过#ifdef来判断,将某些具体模块包括进要编译的内容。
2.用于子程序前加#define DEBUG用于程序调试。
3.应对硬件的设置(机器类型等)。
4.条件编译功能if也可实现,但条件编译可以减少被编译语句,从而减少目标程序大小。
1.volatile定义变量的值是易变的,每次用到这个变量的值的时候都要去重新读取这个变量的值,而不是读寄存器内的备份。
2.多线程中被几个任务共享的变量需要定义为volatile类型。
1.常引用可以理解为常量指针,形式为const typename & refname = varname
。
2.常引用下,原变量值不会被别名所修改。
3.原变量的值可以通过原名修改。
4.常引用通常用作只读变量别名或是形参传递。
优点:
1.代码复用。
2.模板类更加的安全,因其参数类型在编译时都是已知的。
缺点:
1.模板必须在头文件中,这样一旦有所变更需要重编译所有相关工程;同时也没有信息隐藏。
2.一些编译器对template支持不好。
3.模板对每种类型生成额外的代码,可能导致代码膨胀。
全特化就是限定死模板实现的具体类型,偏特化就是如果这个模板有多个类型,那么只限定其中的一部分。