数组和链表的区别
数组:
①数组的空间是一块连续的空间。
②对数组的读取快速方便,直接通过下表访问,时间复杂度为O(1)。
③使用数组时必须提前开辟好空间,但是如果使用不完,就会存在浪费空间。但是如果不够用,就要重新开辟空间,原来数组中的内容也必须拷贝过去。
④在数组中进行随意位置的插入和删除不方便,必须移动数据。
链表:
①链表的空间不是连续的,每个节点都保存着下一个节点的位置。
②对链表中的节点进行读取的时候,必须遍历整个链表,时间复杂度为O(n)。
③链表便于插入和删除。
栈和队列
栈:
①规则:先进后出。
②只能在栈顶进行元素的删除和插入。
队:
①规则:先进先出。
②一端进行插入,另一端进行删除。
static关键字
①修饰变量:如果在代码块内部,用static修饰时,其生命周期就会变为整个程序的生命周期,作用域不变。如果是一个全局变量用static修饰,生命周期和作用域都是整个程序,但是会改变链接属性(就是不可以在另外的文件中使用该变量)。
②修饰函数:只能在本文件中使用该函数,不可以在其他文件中使用。
volatile关键字
作用:保证内存的可见性。当用const修饰变量时,就会优化代码,将代码往寄存器中拷贝一份,当调用的时候就直接从寄存器中读取,但是如果想改变其内容的话,就只能改变内存中值,寄存器中的值却无法改变。所以此时就可以用volatile去修饰这个变量,当再次读取的时候就会从内存中读取。
记录一下杂碎的知识点。
宏的优缺点
- 优点
① 作为常量:增强代码的可维护性。
②作为函数:提高了效率(因为宏会在预处理阶段进行替换,没有栈帧开销)。
但是,作为函数时,不能出现递归。否则会增加代码的冗余度。 - 缺点
①不方便调试。
②没有类型安全的检查(宏常量)。
③当宏作为函数时,代码的可读性差,可维护性差,容易错(优先级 )。
如何用宏写一个swap函数:
#define Swap(a,b) {int tmp = a;a = b;b = tmp;}
在C++中提供了inline(内联)函数,在宏的基础上可以进行类型的检查和便于调试(在dbug模式下有栈帧的开销)。在Release模式下会优化 不会展开没有栈帧的开销。 同样inline函数在使用内联函数的地方展开,使用时如果代码量过长,也会造成程序的膨胀。内联函数必须跟定义放在一起,如果跟声明放在一起并没有作用。
6.assert断言
程序一般分为Debug版和Release 版,Debug:用于内部调试;Release:发行给用户使用。
assert:仅在Debug版本中起作用的宏,用于检查“不应该”发生的情况。当assert条件为假,那么程序就会终止,这样就可以提示程序员程序在哪儿出错了。在函数的入口处使用断言可以检查参数的有效性(合法性)。下面我们写一个memcpy库函数来使用一下断言:
assert的头文件是:assert.h
void *my_memcpy(void* dest,const void* src,size_t count)
{
char* to = (char*)dest;
char* from = (char*)src;
assert((dest != NULL) && (src != NULL));
while(count-- > 0)
{
*to++ = *from++;
}
if(*to != '\0')
{
*to = '\0'; //如果没有拷贝整个src的字符串,就需要手动加上‘\0’
}
return dest;
}
当程序中有不能容忍的错误时,就用assert断言来给程序员们发出警报。
引用和指针
引用:别名(绰号)
指针:地址空间
引用和指针的区别:
我们来解释一下第三条:
int i = 3;
int j = 5;
int &k = i;
k = j; //错误
EOF与feof
EOF(end of file):其实就是一个宏,文件结束的标志.在文件的内部就有一个EOF,就是-1,当文件读到-1时,就表示文件读完了.类比:字符串中的’\0’.只可以用来判断文本文件是否结束.
feof:同样也可以用来判断文件是否结束,但是feof()是一个函数.若文件已经结束,就返回1,否则返回0.既可以来判断文本文件的结束,也可以判断二进制文件的结束.
相同点:都可以用来判断文本文件是否结束.
解引用空指针
我们编写的代码,都是在虚拟地址上,虚拟地址通过页表可以转换到对应的物理地址上.所以,在解引用空指针时,MMU在通过页表的转换为物理地址时,就会发现这是一个不合法的地址访问.所以硬件设备此时就会发送一个异常到内核.内核将其解释为SIGSEGV(11号信号)发送到对应的进程.从而就会引发程序崩溃.
下面这个例子:
1 #include <stdio.h>
2 #include<signal.h>
3
4 void Handler(int sig)
5 {
6 printf("sig = %d\n",sig);
7 }
8 int main()
9 {
10 //signal(SIGSEGV,Handler); //如果使用这个自定义的信号捕捉函数,就会死循环
11 signal(SIGSEGV,SIG_IGN); //如果忽视,程序依然后崩溃.因为Linux操作系统对于每种的信号忽略的处理方式不同.
12 int *p = NULL;
13 *p = 10;
14 return 0;
15 }
在使用自定义的信号捕捉函数时,由于每次在执行解引用空指针进行赋值时,这条语句并没有执行完就去调用信号处理函数.然后又回到函数的上下文处进行执行,又发现了错误,又去调用信号处理函数.以此就会造成死循环.
指针数组&数组指针
指针数组:是一个数组,只是数组的每个元素都是指针.
例如:int *ptr[10].
数组指针:是一个指针.而这个指针指向了一个数组.例如:int (*ptr)[10];