这篇文章主要是我复习一些C和C++时记录的一些知识点,以备不时之需,常常浏览,温故而知新~
union和struct
union为联合体、共用体,而struct为结构体;他们的相同点是:都可以包含多种数据类型。具体的使用规则见:联合体;其实从名字上已经可以看出来,联合体就是在内存开辟一个空间,它的大小跟联合体里面最大的数据类型一样,举个例子,我定义了一个书包,书包可以放电脑和书和其他东西,书包的大小由电脑决定,电脑多大,书包就多大,因为一般来说,电脑是书包能够容纳的最大物品。而对于结构体,它的大小则是按照一定的对齐方式,所有数据类型大小的“总和”;这里的对齐是指每个数据类型都要对齐,比如你定义了如下数据类型:
struct stu{
char sex;
char name[10];
}
如果是按四字节对齐方式,请问上面的结构体的大小是多少?答案是16,char类型占一个字节,但是要对齐所以后面三个字节为空。同理name数组后面需要空两个字节,因此大小为4+12=16。
命名空间
命名空间是解决命名冲突的。详见:using namespace std 到底是什么意思;
类型转换
当我们赋值给无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示数值总数取模后的余数,比如:
unsigned char c = -1;//假设char占一个字节,则c的值为255
当我们赋值给带符号类型,如果这个数大于它能够表示的范围,结果是未定义的。此时程序可能继续工作,可能崩溃,可能生成垃圾数据。
实际上,char类型究竟是有符号还是无符号有编译器决定,因此,考虑到可移植性,不要在算术表达式使用char或者bool(会转化成整形,非0即1)。
变量
初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新值来代替。
变量的初始化
列表初始化:用一个花括号来初始化变量,如果出现丢失信息的风险,编译器将会报错:
double pi = 3.1415926; int a{pi};//错误,转换不会执行
默认初始化:如果定义变量时没有指定初值,则变量被默认初始化。定义在函数体外的内置类型对象如果没有初始化,其值未定义。定义在函数体外部的内置类型变量转化为0.类的对象如果没有显示初始化,则其值由类本身确定(由类的构造函数定义)。变量的声明和定义
变量声明规定了变量的类型和名字,在这一点上定义与之相同。但是除此之外,定义还申请存储空间,也可能会为变量赋一个初始值。任何包含显式初始化的声明即成为定义:
extern int i = 2;//定义
;
引用和指针
- 引用
引用是一个已经存在的对象(不可以是字面值)的别名;引用本身不是对象,不能对引用进行引用;定义引用必须初始化,程序将会把引用和它的初始值一直绑定在一起;而且一般来说,引用类型必须跟与之绑定的对象严格匹配。但是,也有例外:其一是在初始化常量引用时允许用任意表达式作为初始值(
const int &r = 42*2;
);其二是可以将基类的引用绑定到派生类对象上。
int val = 1;
int &refVal = val; //refVal 是val的小名
int &ref; //报错,引用必须初始化
double &ref1 = val; //错误,类型必须严格匹配
虽然引用和指针都提供间接访问对象的功能,但是指针不同于引用,指针是一个变量,可以更改指针所指的对象,使用指针最好是有效指针(指向实际的对象);还有一个怪胎就是void指针,它可以存放任意的数据类型的地址,但是不能操作void*指针所指的对象,因为无法知道这个对象的类型。
在C语言中没有引用的概念,引用是C++提出来的;既然指针和引用都能够对一个对象进行间接访问,那为什么还要使用引用呢?直接使用指针不就好了吗?引用是怎么实现的?
下面本人做出解答:
引用实质上就是被引用对象的地址。在编译的时候,指针变量的地址是和指针所指向对象的地址不同,指针变量的值才跟指向对象的地址相同。而引用的地址就是被引用对象的地址,引用的值就是被引用对象的值。因此一般说来,引用是不占用内存空间的,也不存在引用的引用,引用声明的时候必须初始化。而指针是一个对象实体,占用内存空间,存在指针的指针,指针在声明的时候可以不初始化,也可以为NULL。因此,可以把引用看成是只允许内容存取的指针。
引用存在的理由:
- 当你看别人的代码的时候,有时觉得变量名的含义不清晰,可以声明一个含义更清晰地引用,然后再对引用进行操作。(这个好像用一个全局替换就可以了==)
- 虽然引用和指针都能够用来传递实参和返回结果,并且能够避免拷贝,但是引用使用起来跟优雅一些。因为指针需要解引用操作来更改实参的值,但是引用是自动间接寻址来更改实参的值。实质上,按指针传参也是通过按值传参的方式实现的,它传的是一个地址值,在栈上实际保存的是指针变量的副本,实际上可以改变的是指针指向对象的值,但是指针变量本身的值是不会变的。按引用传参则不同,虽然在栈上同样开辟空间,但是存放的是实参变量的地址,被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。
数组和链表的优缺点
数组的优缺点:元素值的存取操作方便,节省空间。增删操作费时,大小固定。
链表的优缺点:元素的增删操作方便,可以组织成更为复杂的数据结构:树,图。扩展性比较好,大小可以随意增加。但是元素值的存取和查找操作费时,耗费空间。
结合数组和链表的优点的数据结构:解决哈希表冲突使用链地址法使用的结构:总体是一个数组,数组的元素是一个指针,指向一个链表。
delete 和 delete [] 的区别
具体要看delete的对象类型:
(1)delete的是内置数据类型的数组,没有区别。
(2)delete的是自定义的数据类型的数组,则delete将会只调用一次析构函数,对数组首元素进行析构,而其他的元素则没有,因此会造成内存泄露。而delete []则会把所有对象析构,然后释放空间,比较安全。
参考链接:具体的工作原理。
c++程序运行时的三种内存分配策略
(1)静态存储分配:在编译的时候就可以确定对象在运行时刻的空间大小,因此在编译时就可以给他们分配固定的内存空间。
(2)栈式存储分配:在编译的时候未知,只有在程序运行的时候才能够知道大小。栈的分配和释放有编译器自动管理,栈空间比较小,栈不存在碎片问题,一般来说能够用栈最好用栈。(函数的局部变量存放在栈区)
(3)堆式存储分配:在编译时或运行时都无法确定存储要求的数据结构的内存分配。堆由大片的可利用的块组成,堆中的内存都可以按任意书序分配和释放。堆需要程序员自己控制,空间比较大,不停的分配和释放会带来碎片的问题,效率比较低,但是堆非常灵活,需要谨慎使用。(使用new分配的内存块都在堆里。)
C++内存管理详解