1、进程和线程的区别
1) 在有线程的操作系统中,进程通常作为分配资源的基本单位;线程则作为独立运行和独立调度的基本单位。
2) 进程间是独立的,这表现在内存空间、代码和上下文环境上;线程运行在进程空间内,也就是说一个进程内的线程共享数据空间。
3) 线程间的上下文切换速度远大于进程间上下文的切换速度;线程占用的资源要少于进程占用的资源。
4) 进程和线程都可以有优先级;进程可以进程进程间通信,线程也可以进行线程间通 信,但只有进程间可以通过IPC通信,线程不可以。
个人理解:进程可以比喻成一个农场,里面有许多资源(种子和幼崽等)。而在农场里的工作的人就是线程,进程资源的利用(播种子和喂养幼崽等),进而处理相关的任务。
2、栈和堆的区别(并非数据结构中的堆和栈)
1)栈:由编译器进行管理,自动分配和释放,存放函数调用过程中的各种参数,
局部变量,返回值及函数返回地址。操作方式类似数据结构中的栈。
2)堆:用于程序动态申请分配和释放空间。C语言中的malloc、calloc和free;C++中的new和delete均是在堆中进行的。正常情况下,程序员申请的空间在使用结束后应该释放,若程序员没有释放空间,则程序结束时系统自动回收。
3、strcpy、memcpy和memset的区别
1)strcpy 用于字符串复制,当遇到”\0”。复制结束,并且”\0”也已被拷贝。需要注意的是使 strcpy函数的时候一定要注意前面目的数组的大小必须大于后面字符串的大小,否则便是访问越界。
2)memcpy用于复制源空间的数据到目的空间中。是用来做内存拷贝,可以用来拷贝任何数据类型的对象,可以指定拷贝的数据长度。
3)memset主要应用是初始化某个内存空间。用来对一段内存空间全部设置为某个字符,一般用于在对定义的字符串初始化为”\0”。
4、malloc/free和new/delete的区别
1) 前者是c++/C语言的标准库函数,后者是c++的运算符。它们都可以用于申请动态内存和释放内存。
2) 后者在对象创建的时候会自动执行构造函数,在对象消亡的时候自动执行析构函数,而前者没有该项功能。
5、#define和const的区别
1)#define定义的只是个常数,不带类型;const定义的常数是变量,带类型。
2)#define是在编译的预处理阶段起作用,而const是在编译、运行的时候起作用。
3)#define只是简单的字符串替换,没有类型检查;而const有对应的数据类型,要判断。
6、数据结构基础之双链表(仅仅是基本功能)
1)节点的设计
typedef struct list_node{
int data;
struct list_node *next;
struct list_node *prev;
}Node,*PLNode;
2) 初始化头节点
PLNode init_head(PLNode head)
{
//申请堆空间
head = (PLNode)malloc(sizeof(Node));
if(head == NULL)
{
printf("init_head is NULL!\n");
}
//赋值
head->data = 100;
head->next = NULL;
head->prev = NULL;
return head;
}
3) 尾插
int list_add_tail(PLNode head,int num)
{
//为节点申请空间
PLNode node = NULL;
PLNode p = NULL;
node = (PLNode)malloc(sizeof(Node));
if(node == NULL)
{
printf("list_add_tail node is NULL!\n");
return -1;
}
//赋值
node->data = num;
//寻找最后一个节点
for(p=head;p->next!=NULL;p=p->next);
p->next = node;
node->next = NULL;
node->prev = p;
return 0;
}
4) 遍历节点
PLNode search_list_node(PLNode head,int num)
{
PLNode p = NULL;
for(p=head;p!=NULL;p=p->next)
{
if(p->data == num)
{
printf("search_list_node:%d\n",p->data);
return p;
}
}
printf("Not Found Node!\n");
return NULL;
}
5) 删除节点
int delete_one_node(PLNode head,int num)
{
PLNode p = NULL;
PLNode q = NULL;
//1.找到需要删除的节点
for(q=head,p=head->next;p!=NULL;q=p,p=p->next)
{
if(p->data == num)
{
printf("delete_one_node:%d\n",p->data);
q->next = p->next;
if(p->next!=NULL)
p->next->prev = p->prev;
free(p);
//printf("p->data:%d\n",p->data);//0
return 0;
}
}
printf("Not Found delete Node!\n");
return -1;
}
7、一维数组、二维数组和指针(举几个经典例子)
1)
int array[5] = {1, 2, 3, 4, 5};
int* ptr = (int*)(array + 1);
printf("%d, %d", *(array + 1), *(ptr - 1));
2)
int a[2][5] = {0}; //定义一个二维数组
int* p1 = a[0]; //这里的a[0]是a[0][5]的数组名,也为数组
//的首元素地址,a[0]等价于&a[0][0]
int* p2 = a[1];
//则下面的值依次为a[0][0], a[0][1], a[1][0], a[1][1]的值
printf("%d, %d, %d, %d", *p1, *(p1 + 1), *p2, *(p2 + 1));
3)
int a[2][5] = {0};
int(*p)[5] = a; //数组指针p指向二维数组a[2][5]
//下面的值依次为a[0][0], a[0][1], a[1][0], a[1][1]的值
printf("%d, %d, %d", **p, *(*p + 1), **(p + 1), *(*(p + 1) + 1));
8、关键字(static、volatile和const)
static:
1)设置变量的存储域,函数体内static变量的作用范围为该函数体,该变量的内存只被分配一次(在静态存储区),因此其值在下次调用时仍维持上次的值。
2)限制变量的作用域,static修饰的静态局部变量作用域是代码块作用域(和自动局部变量是一样的),链接属性是无连接;当它修饰全局变量时,其作用范围就被限制在本文中。
3)限制函数的作用域,static修饰的函数,其作用范围锁在了本文件中。
4)在类中的static成员变量意味着它为该类的所有实例所共享,也就是说当某个类的实例修改了该静态成员变量,其修改值为该类的其它所有的实例所见。
5)在类中的static成员函数属于整个类所拥有,这个函数不接受this指针,因而只能访问类的static成员变量。
volatile:
1)含义:让编译器不要优化其修饰过的变量。
2)多线程共享全局变量的时候,该全局变量要加上volatile进行修饰。
3)裸机编程的时候,某函数与中断服务函数共享全局变量的时候,该全局变量要加上volatile进行修饰。
4)ARM定义寄存器的时候,寄存器是指向一个地址,要加上volatile进行修饰。
const:
1) int const *p或者const int *p:表示p所指向的内容是“常量”,即p所指向的内容不能修改。
2) int *const p:表示指针变量p本身不能被修改,即p的指向不能改变,但p所指向的内容可以被修改。
3) int const const p:表示为上面两种情况的结合,即p的指向不能被修改,而p指向的内容也不能被修改。
小技巧:当在const的左边时表示指向不能被修改,*在右边时内容不能被修改,即左指向右内容。
注意:上面的常量用双引号括起来是因为它虽说为常量,但用指针却可以修改它所修饰的值。
int const a = 100;
int *p = (int*)&a;
*p = 200;
printf("%d", a); //运行后a的值为200