360-c++开发面经(三)
共 3797字,需浏览 8分钟
·
2021-06-16 10:16
点击蓝字关注我们,获取更多面经
![](https://filescdn.proginn.com/db6189daf6c052b0b89e468e7d7dadea/3dc4a0add1e71a55ff6a3fd4f122e60a.webp)
![](https://filescdn.proginn.com/8b74a80f0f181cc8d39cccb122f59163/c46353fe3eb4473b50da812a06096d01.webp)
一、限制符号的作用域只在本程序文件
若变量或函数(统称符号)使用static修饰,则只能在本程序文件内使用,其他程序文件不能调用(非static的可以通过extern 关键字声明该变量是在其他文件内定义的,此文件可调用)。不加static修饰的,则默认是可以被其他程序文件调用的。
本程序文件内的非static函数也可以随意调用static的变量和函数。只是限制了外部程序文件的函数不能调用。
原理:默认的变量和函数名(统一称为符号)在编译成汇编代码.s文件时,会有.globl func_name,.globl指示告诉汇编器,func_name这个符号要被链接器用到(汇编文件在经过汇编器处理成二进制的.o文件时,符号会被变量或函数实际的地址值代替),所以要在目标文件的符号表中标记它是一个全局符号。如果一个符号没有用.globl声明,就表示这个符号不会被链接器用到。而static关键字修饰的符号在编译成汇编代码.s文件时,就不会被.globl声明,因此不会参与后序链接就不会被其他程序文件调用到。
(不同程序文件的函数变量互相调用是在链接各个.o文件步骤后进行的,前面预编译、编译、汇编步骤都是对单个程序文件进行操作)
二、指定变量的存储位置
对于函数内的变量。auto变量(函数局部变量)都是在栈内存区存放,函数结束后就自动释放。但是全局的和函数内定义的static变量都是存放在数据区的,且只存一份,只在整个程序结束后才自动释放。
由于static变量只存一份即同一地址,所以不管函数调用多少次,函数内定义static变量的语句只会在第一次调用时执行,后面调用都不执行也不再初始化,而是对该地址内的数据进行操作。
三、C++类的静态成员变量
是属于类,而不属于某个实例对象,因此也只有一个地址保存一份数据存放于数据区。在类中只是声明,并不是定义,因此不分配内存,对类用sizeof求大小也不会将static变量得大小加入。
必须在类声明的外部,以及main()函数的外部,也就是全部变量区域对类的static成员变量再次定义(定以后才分配唯一内存,此时该类静态成员变量相当于是全局的静态变量了,只是调用的时候要使用类名加::)。若只定义不赋值初始化,则默认初始化为0。
public的静态数据成员既可以通过类名引用,也可以通过对象名引用(会自动转换成类名引用)。
四、C++类的静态成员函数
只能调用本类的静态成员变量或函数,不能调用本类的非静态成员函数和变量。因为非静态成员函数和变量在类成员函数中调用时,都是由形参中隐含一个指向当前实例对象的this指针来调用。然而静态成员函数没有这个this形参。
![](https://filescdn.proginn.com/db6189daf6c052b0b89e468e7d7dadea/3dc4a0add1e71a55ff6a3fd4f122e60a.webp)
![](https://filescdn.proginn.com/8b74a80f0f181cc8d39cccb122f59163/c46353fe3eb4473b50da812a06096d01.webp)
一个空类含有的函数:
class Empty{
};
// 等价于
class Empty{
public:
Empty(){ //默认构造函数
}
Empty(const Empty& rhs){ //拷贝构造函数
}
~Empty(){ //析构函数
}
Empty& operator=(const Empty rhs){ //重载赋值操作符函数
}
Empty* operator & (); // 取址运算符
const Empty* operator & () const; // 取址运算符const
};
这些函数只有在被调用的时候才会被编译器创建出来。
Empty e1; // 默认构造函数
Empty e2(e1); // 拷贝构造函数
e2 = e1; //重载赋值操作符
拷贝构造函数和重载赋值操作符函数用于将来源对象的每一个非静态成员变量拷贝到目标对象。
如果类中含有指针型的数据成员、需要使用动态内存,程序员最好显式定义自己的复制构造函数,避免各种可能出现的内存错误。
默认的拷贝构造函数是浅拷贝,可能导致两个指针指向了堆里的同一个空间,在销毁对象时,两个对象的析构函数将对同一个内存空间释放两次,从而引发错误。
对于对象中动态成员,就不能简单赋值,而应该重新动态分配空间。
class Rect{
public:
Rect() // 构造函数,p指向堆中分配的一空间
{
p = new int(100);
}
Rect(const Rect& r)
{
width = r.width;
height = r.height;
p = new int; // 为新对象重新动态分配空间
*p = *(r.p);
}
~Rect() // 析构函数,释放动态分配的空间
{
if(p != NULL)
{
delete p;
}
}
private:
int width;
int height;
int *p; // 一指针成员
};
![](https://filescdn.proginn.com/db6189daf6c052b0b89e468e7d7dadea/3dc4a0add1e71a55ff6a3fd4f122e60a.webp)
![](https://filescdn.proginn.com/8b74a80f0f181cc8d39cccb122f59163/c46353fe3eb4473b50da812a06096d01.webp)
数组:
数组是将元素在内存中连续存放,由于每个元素占用内存 相同,可以通过下标迅速访问数组中任何元素。但是如果要在数组中增加一个元素,需要移动大量元素,在内存中空出一个元素的空间,然后将要增加的元素放在其 中。同样的道理,如果想删除一个元素,同样需要移动大量元素去填掉被移动的元素。如果应用需要快速访问数据,很少或不插入和删除元素,就应该用数组。
链表:
链表恰好相反,链表中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起。比如:上一个元素有个指针指到下一个元素,以此类推,直到最后 一个元素。如果要访问链表中一个元素,需要从第一个元素开始,一直找到需要的元素位置。但是增加和删除一个元素对于链表数据结构就非常简单了,只要修改元 素中的指针就可以了。如果应用需要经常插入和删除元素你就需要用链表数据结构了。
C++语言中可以用数组处理一组数据类型相同的数据, 但不允许动态定义数组的大小,即在使用数组之前必须确定数组的大小。而在实际应用中,用户使用数组之前有时无法准确确定数组的大小,只能将数组定义成足够 大小,这样数组中有些空间可能不被使用,从而造成内存空间的浪费。链表是一种常见的数据组织形式,它采用动态分配内存的形式实现。需要时可以用new分配 内存空间,不需要时用delete将已分配的空间释放,不会造成内存空间的浪费。
(1) 从逻辑结构角度来看
a, 数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费。
b,链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。(数组中插入、删除数据项时,需要移动其它数据项)
(2)从内存存储角度来看
a,(静态)数组从栈中分配空间, 对于程序员方便快速,但自由度小。
b, 链表从堆中分配空间, 自由度大但申请管理比较麻烦.
数组和链表的区别整理如下:
数组静态分配内存,链表动态分配内存;
数组在内存中连续,链表不连续;
数组元素在栈区,链表元素在堆区;
数组利用下标定位,时间复杂度为O(1),链表定位元素时间复杂度O(n);
数组插入或删除元素的时间复杂度O(n),链表的时间复杂度O(1)。
更多面经
扫描二维码
获取更多面经
扶摇就业