1. 说说什么是函数指针,如何定义函数指针,有什么使用场景?
函数指针就是指向函数的指针变量。每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。
int func(int a);
int (*f)(int a);
f = &func;
函数指针的应用场景:回调(callback)。我们调用别人提供的API函数(Application Programming Interface,应用程序编程接口),称为call;如果别人的库里调用我们的函数,就叫回调。
//以库函数qsort排序函数为例,它的原型如下:
void qsort(void *base, // void*类型代表原始数组
size_t number, // size_t类型,代表数据数量
size_t size, // size_t类型,代表单个数据占用空间大小
int (*compare)(const void *, const void *) //第四个参数是函数指针
);
//第四个参数告诉qsort,应该使用哪个函数来比较元素,即只要告诉qsort比较大小的规则,它就可以帮我们对任意数据类型的数组进行排序。在库函数qsort调用哦我们自己自定义的比较函数,这就是回调的应用。
int cmp_int(const void *_a, const void *_b)//参数格式固定
{
int *a = (int *)_a; //里面进行强制类型转换
int *b = (int *)_b;
return *a - *b; //表示升序
}
//调用时
int num[100];
qsort(num, 100, sizeof(num[0]), cmp_int);//回调
2. 说说运算符i++和++i的区别?
a. 赋值顺序不同:++i是先加后赋值;i++是先赋值后加;++i和i++都是分两步完成的
b. 效率不同:i++要比++i要慢
c. i++不能作为左值,而++i可以;
int i = 0;
int *p1 = &(++i);//正确
int *p2 = &(i++);//错误,&后面要用一元 *** 作,i++因为后执行,所以是二元 *** 作
++i = 1; //正确
i++ = 1; //错误
d. 两者都不是原子 *** 作;
3. 说说const和define的区别?
const用于定义常量;而define用来定义宏(也可以看作是定义常量);它们的区别有:
a. const生效于编译的阶段;define生效于预处理的阶段;
b. const定义的常量,需要额外的内存空间;define定义的常量,运行时是直接的 *** 作数,并不会放在内存中;
c. const定义的常量是带类型的;define定义的常量不带类型。因此define定义的常量不利于类型检查。
4. 说说下面表达式代表的含义?
const int a;//指a是一个常量,不允许修改
const int *a;//a指针所指向的内存里的值不变,即(*a)不变
int const *a;//同样表示a指针所指向内存里的值不变
int *const a;//a指针所指向的内存地址不变,即a不变
const int *const a;//a指针所指向的内存地址与内存里的值均不能改变
5. 简述C++的内存管理?
a. 内存分配方式:
在C++中,内存分为5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。
栈:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。
堆:就是那些由new分配的内存块,一般一个new就要对应一个delete。
自由存储区:就是那些由malloc等分配的内存块,和堆十分类似,但是是用free来结束自己的生命周期。
全局/静态存储区:全局变量和静态变量被分配到同一块内存中。
常量存储区:这是一块比较特殊的存储区,里面存放的是常量,不允许修改。
b. 常见的内存错误及其对策:
错误:
(1)内存分配未成功,却使用了它
(2)内存分配虽然成功,但是尚未初始化就引用它
(3)内存分配成功并且已经初始化,但 *** 作越过了内存的边界
(4)忘记了释放内存,造成了内存泄漏
(5)释放了内存却继续使用它
对策:
(1)使用指针时,先初始化为NULL;
(2)用malloc和new申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。
(3)不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。
(4)避免数字或指针的下标越界,特别当心发生“多1“和“少1” *** 作
(5)动态内存的申请与释放必须配对,防止内存泄漏
(6)使用free或delete释放内存后,立即将指针设置为NULL,防止”野指针“
(7)使用智能指针
c. 内存泄漏及解决办法
内存泄漏表示申请了一块内存空间,使用完毕后没有释放掉。
内存泄漏情形:
(1)new和malloc申请资源使用后,没有用delete和free释放
(2)子类继承父类时,父类析构函数不是虚函数
(3)Windows句柄资源使用后没有释放
解决办法
(1)良好的编码习惯,使用了内存分配的函数,使用完毕后,要记得使用其相应的函数释放掉
(2)可以将分配内存的指针以链表形式管理,使用完毕之后可以从链表中删除,程序结束时可检查该链表
(3)使用智能指针
(4)一些常见的工具插件,如ccmalloc,Dmallloc,Leaky,Valgrind等等
6. 内存模型,堆栈,常量区介绍
内存布局:
一个程序由代码段、数据段、BSS段组成
a. 代码段:存放程序执行代码的一块内存区域。只读,代码段的头部还会包含一些只读的常数变量。
b. 数据段:存放程序中已初始化的全局变量和静态变量的一块内存区域。
c. BSS段:存放程序中未初始化的全局变量和静态变量的一块内存区域。
d. 可执行程序在运行时又会多出两个区域:堆区和栈区
堆区:动态申请内存用。堆从低地址向高地址增长
栈区:存储局部变量、函数参数值。栈从高地址向低地址增长。是一块连续的空间
e. 最后还有一块文件映射区,位于堆和栈之间。
堆:由new分配的内存块,其释放由程序远控制(一个new对应一个delete)
栈:编译器在需要时分配,在不需要时自动清除的存储区。存放局部变量、函数参数。
常量存储区:存放常量,不允许修改
7. 简述C++中内存对齐的使用场景
内存对齐应用于三种数据类型中:struct/class/union
struct/class/union内存对齐原则有4个:
(1)数据成员对齐原则:struct或union的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小的整数倍开始
(2)结构体作为成员:如果一个结构体里有某些结构体成员,则结构体成员要从其内部”最宽基本类型成员“的整数倍地址开始存储(struct a里存有struct b,b里有char(1),int(4),double(8)等元素,那b应该从8的整数倍开始存储)
(3)收尾工作:结构体总大小,也就是sizeof的结果,必须是其内部最大成员的”最宽基本类型成员“的整数倍。不足的要补齐。(基本类型不包括struct/class/union)
(4)sizeof(union),以结构里面size最大元素为union的size,因为在某一时刻,union只有一个成员真正存储于该地址。
(5)注意一点,在计算类或者结构体大小时,只有变量占用大小(存在内存中),函数是不占用的(存储在磁盘中),但是虚函数不存储在磁盘中,所以要额外考虑(成员对齐后大小+4*(虚函数指针的个数))。
补充:
什么是内存对齐?
为了使CPU能够对变量进行快速的访问,需要在结构体、类或者联合体中的变量按照一定的规则在空间上排列,而不是顺序的一个接一个的排列,这就是内存对齐。
//字节对齐实例
union example
{
int a[5];
char b;
double c;
};
//union的最长字节为20,但不是double类型的整数倍,所以最小应该是24即满足double类型内存大小的整数倍又可以容纳20字节
cout<<sizeof(example)<<endl;//24
struct example1
{
int a[5];
char b;
double c;
};
//int a[5]和char b对齐是20+1+(3)=24,再加上double 8最后结果是32,同时满足8(duoble(占用最大内存空间类型)类型内存大小)的整数倍
cout<<sizeof(example1)<<endl;//32
struct example2
{
char a;
double b;
int c;
};
//char先和double对齐是1+(7)+8,然后和int对齐同样是+8,最后结果是24,同时满足8的整数倍
cout<<sizeof(example2)<<endl;//24
struct example3
{
char a;
int b;
double c;
};
//char和int对齐是1+(3)+4,然后和double对齐是+8,最后结果是16,同时满足8的整数倍
cout<<sizeof(example3)<<endl;//16
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)