面试题4

本文详细介绍了C/C++程序内存的分配情况,包括栈、堆、全局区、文字常量区和程序代码区。接着讨论了常用的排序算法,如冒泡排序、选择排序和插入排序的实现,并给出了快速排序的例子。同时,文章指出在排序算法中,插入排序在某些特定情况下可能是最快的。此外,文章还涉及了指针、常量、静态成员、内存分配中的注意事项以及进程间通信等知识点。
摘要由CSDN通过智能技术生成
内存分配


一个由C/C++编译的程序占用的内存分为以下几个部分


1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在 WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。


2、堆区(heap)— 由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。      操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。


3、全局区(静态区)(static)— 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。


4、文字常量区 — 常量字符串就是放在这里的,程序结束后由系统释放 。


5、程序代码区— 存放函数体的二进制代码。




常用数据结构:
1,数组 (Array)
2,栈 (Stack)    是只能在某一端插入和删除的特殊线性表。它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来)。
3,队列 (Queue) 一种特殊的线性表,它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。
4,链表 (Linked  List) 是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
5,树 (Tree)
6,图 (Graph)
7,堆 (Heap)在计算机科学中,堆是一种特殊的树形数据结构,每个结点都有一个值。通常我们所说的堆的数据结构,是指二叉堆。堆的特点是根结点的值最小(或最大),且根结点的两个子树也是一个堆。


冒泡排序:
main() 

int i,j,temp; 
int a[10]; 
for(i=0;i<10;i++) 
scanf ("%d,",&a[i]); 


for(j=0;j<9;j++) 
{ for (i=0;i<9-j;i++) 
{
if (a[i]>a[i+1]) 
{ temp=a[i]; 
a[i]=a[i+1]; 
a[i+1]=temp;} 
}

for(i=0;i<10;i++) 
printf("%5d,",a[i] ); 
printf("\n"); 
}


选择法排序:
main() 
{
int a[10],i,j,k,t,n=10;    
   
for(i=0;i<10;i++)     scanf("%d",&a[i]);


for(i=0;i<n-1;i++)    /*外循环控制趟数,n个数选n-1趟*/   
{      
k=i;             /*假设当前趟的第一个数为最值,记在k中 */     
for(j=i+1;j<n;j++)  /*从下一个数到最后一个数之间找最值*/       
if(a[k]<a[j])     /*若其后有比最值更大的*/        
k=j;         /*则将其下标记在k中*/    
if(k!=i)         /*若k不为最初的i值,说明在其后找到比其更大的数*/     
{  
t=a[k];  a[k]=a[i];  a[i]=t;   /*则交换最值和当前序列的第一个数*/   
}    
 }  
 for(i=0;i<10;i++)     
 printf("%d   ",a[i]);  
}    


插入法排序:
main() 
{
int a[10],i,j,t;    
   
for(i=0;i<10;i++)     scanf("%d",&a[i]);


for(i=1;i<10;i++)  /*外循环控制趟数,n个数从第2个数开始到最后共进行n-1次插入*/ 
{     
t=a[i];      /*将待插入数暂存于变量t中*/      
for( j=i-1;j>=0 && t>a[j];j-- )  /*在有序序列(下标0 ~ i-1)中寻找插入位置*/       
a[j+1]=a[j];   /*若未找到插入位置,则当前元素后移一个位置*/     
a[j+1]=t;
}
for(i=0;i<10;i++)     
printf("%d   ",a[i]);  



快速排序:
#include <stdio.h>


int quichsort(int *arr, int left, int right);
void swap(int *a, int *b);


int
main()
{
  int arr[] = {9, 2, 1, 4, 3, 15, 11, 6, 7};
  int arr_len = sizeof(arr) / sizeof(arr[0]);
  int i;
  for (i = 0; i < arr_len; i++) {
    printf("%d ", arr[i]);
  }
  printf("\n");
  quicksort(arr, 0, arr_len - 1);
  for (i = 0; i < arr_len; i++)
    printf("%d ", arr[i]);
  printf("\n");


  return 0;
}
int quicksort(int *arr, int left, int right)
{
  int i = left;
  int j = right;
  int one_key = arr[left];
  if (i >= j) //avoid infinity recursion
    return 0;
  while (i != j) {
  //accomplish one quicksort
    while (arr[j] > one_key) {
      j--;
    }
    swap(&arr[j], &one_key);
    while (arr[i] < one_key) {
      i++;
    }
    swap(&arr[i], &one_key);
  }
  quicksort(arr, left, i - 1); //accomplish the left remaining quicksort
  quicksort(arr, j + 1, right); //accomplish the right remaining quicksort
}


void swap(int *a, int *b)
{
  int tmp;
  tmp = *a;
  *a = *b;
  *b = tmp;
}




下面哪种排序法对12354最快--插入排序应该最快,其次是快速排序,归并排序。
a quick sort
b.buble sort
c.merge sort
快速排序:有一个划分元素,该元素左边的所有元素都小于它,右边的所有元素都大于它。
冒泡排序:依次比较相邻的两个数,将大数放在前面,小数放在后面。即首先比较第1个和第2个数,将大数放前,小数放后。这样一趟之后,最小的数就放在了最后面。
归并排序:将两个有序的数列合并成一个
希尔排序:基本思想:将整个无序序列分割成若干小的子序列分别进行插入排序。序列分割方法:将相隔某个增量h的元素构成一个子序列。在排序过程中,逐次减小这个增量,最后当h减到1时,进行一次插入排序,排序就完成。增量序列一般采用:ht=2t-1,1≤t≤[log2n],其中n为待排序序列的长度。


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#include<iostream>
using namespace std;
//函数内的s[0]实际只是一个指向字符串的指针,
//没有任何额外的与数组相关的信息,因此sizeof作用于上只将其当指针看,一个指针为4个字节,因此返回4。
void aa(char s[])
{
cout<<sizeof(s)/sizeof(s[0])<<endl;//4
}
//函数内的s[0]实际只是一个指向字符串的指针,
//没有任何额外的与数组相关的信息,因此sizeof作用于上只将其当指针看,一个指针为4个字节,因此返回4。
void bb(char *s)
{
cout<<sizeof(s)/sizeof(s[0])<<endl;//4
}
void main()
{
char s[]="abc123";
aa(s);
bb(s);
cout<<sizeof(s)/sizeof(s[0])<<endl;//7
}
/*--
以上代码中的两个sizeof用法有问题吗?
  答:函数内的sizeof有问题。
  根据语法,sizeof如用于数组,只能测出静态数组的大小,无法检测动态分配的或外部数组大小。
  函数外的s是一个静态定义的数组,因此其大小为7,函数内的s实际只是一个指向字符串的指针,
  没有任何额外的与数组相关的信息,因此sizeof作用于上只将其当指针看,一个指针为4个字节,因此返回4。
  
  
Class Test{int a;static double c};//sizeof(Test)=4. 结构或者类中的静态成员不对结构或者类的大小产生影响,因为静态变量的存储位置与结构或者类的实例地址无关。
Class test1{ };//sizeof(test1)=1;  没有成员变量的结构或类的大小为1,因为必须保证结构或类的每一个实例在内存中都有唯一的地址。
int func(char s[5]); //sizeof(s)=4 函数的参数在传递的时候系统处理为一个指针
izeof(func("1234"))=4//因为func的返回类型为int,所以相当于求sizeof(int). 


 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%




const int i=0;            //i是常量,i的值不会被修改
const int *p1=&i;        //指针p1所指内容是常量,可以不初始化,指针可以变化
int  * const p2;     //指针p2是常量,所指内容可修改
const int * const p3=&i; //指针p3是常量,所指内容也是常量  
const位于*左侧,指针指向为常量;
const位于*右侧,指针本身为常量;  




 ####################################################### 
以下代码能够编译通过吗,为什么?
unsigned int const size1 = 2;
char str1[ size1 ];
unsigned int temp = 0;
cin >> temp;
unsigned int const size2 = temp;
char str2[ size2 ];
答:str2定义出错,size2非编译器期间常量,而数组定义要求长度必须为编译期常量。  
对于编译期常量,编译器常常在编译时就可以折叠开。而对于运行期常量,编译期无法折叠,编译器能做的,只是对所以可能修改它的动做报错。
编译期常量最常见的例子是编译时的常数定义,比如:
 #define MAX 128     这个 MAX 就是编译期常量, 没有对应的内存空间,在编译时候, 所有的 MAX 都被128这个值代替
 const double PI = 3.1415926;


 运行期常量的最常见的例子是函数的常量参数(包括常引用,常指针参数)比如:
void f(const string& s) {...}
次常见的例子是类的非静态常量成员。
——这些都是一经初始化,不允许再发生变化的,但其初始值必须到运行时才能知道。  
在运行时常量,它的值虽然在运行时初始化后不再发生变化,但问题就在于它的初始值要到编译时才能确定。比如:
srand(clock());
const int i = rand();
虽然i的值在定义并初始化成不会再发生变化(除非你使用一些不符合标准的小技巧),但再聪明的编译器也无法在编译时确定它的值呀。 
########################################################################  




?????????????????????????????????????????????????????????????????
int a = *p++; //等价于a = *(p++);即a = *p; p = p + 1;     ++的优先级和*相同,右结合
int b = *++p; //等价于b = *(++p); 即p = p + 1; b = *p;
*p++是先取出*p的值,然后让p++。。*p++ :先对指针p解引用,就是求*p,然后对指针自增,即p++;
(*p)++是先取出*p的值,让这个值++ 。。(*p)++ :先求*p,然后将括号里的当成一个整体,即*p,对*p自增,求*p++;
*(P++)是先取出*p的值,让p++
所以,*p++等价于*(P++)


#include <iostream.h>
int main(){
 char s[] = "012345678", *p = s;
 cout << *p++ << *(p++) << (*p)++ << *++p << *(++p) <<++*p << ++(*p) << endl;
 
 /*p = s;
 cout << *p++ << endl;
 cout << *(p++) << endl;
 cout << (*p)++ << endl;
 cout << * ++p << endl;
 cout << *(++p) <<endl;
 cout << ++*p << endl;
 cout << ++( *p) <<endl;*/
 return 0;
}
VC++6 DEBUG下分析:cout的运算是从右向左进行的,但最后输出还是从左到右。所以cout << *p++ << *(p++) << (*p)++ << *++p << *(++p) <<++*p << ++(*p) << endl;依次++(*p),++*p,*(++p),*++p,(*p)++,*(p++), *p++ ,最后再反着输出。
1.++(*p):P指向S[0],并把S[0]加1做为表达式的值,所以输出为1,此时S[0]=='1'
2.++*p:P还指向S[0](S[0]现在的值为1),并把S[0]加1做为表达式的值,所以输出为2,此时S[0]=='2'
3.*(++p):p指向S[1],然后取S[1]的值作为表达式的值,输出'1'
4.*++p :P指向S[2],然后取S[2]的值作为表达式的值,输出'2'
5.(*p)++:P还是指向S[2],取S[2]的值作为表达式的值,所以输出'2',然后S[2]的值加1,S[2]==3
6.*(p++):P还是指向S[2](现值为3),取S[2]的值作为表达式的值,所以输出'3',然后P指向S[3]
7.*p++ :P指向S[3],取S[3]的值作为表达式的值,所以输出'3',然后P指向S[4];
最后反着输出为3322121
???????????????????????????????????????????????????????????????????????




(1)指针数组: 是数组,但数组中的每个元素都是指针,int *p[5]
(2)指向数组的指针: 是个指针,但它指向的是一个数组,int (*p)[5]


int a[10];a是数组首地址,也就是a[0]的地址,a+1是数组下一元素的地址,即a[1],&a是对象的首地址,&a+1是下一个对象的地址,即a[10]








  
------------------apue----------------------------------
{ssize_t pread(int fd, void *buf, size_t count, off_t offset);
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);}
read from or write to a file descriptor at a given off-set。The file offset is not changed.


文件系统:(磁盘文件系统+虚拟文件系统)---这里讲的是磁盘文件系统
ext文件系统:ext2,ext3---细节太多,不讲
UFS(ext前身)
ntfs
FAT---标识文件用文件名或路径。FAT文件系统的本质:静态单链表。(静态即是用数组实现的,数组是静态的),静态单链表的缺陷即单链表的缺陷:1,回不去;2,限制文件大小:惧怕大文件
    优点:小,轻量级(优盘,SID卡依然在用FAT)
一个磁盘可分为一个或多个分区,每个分区包含一个文件系统,每个分区都有一个根,unix是把所有的文件组合到一个数组里,这就需要虚拟文件系统。---虚拟文件系统只存在内存里,并不存在于磁盘里。
创建虚拟文件系统:先去读根分区,然后拿根分区做模板,找到根分区之后就能找到/etc/fstab文件,然后把文件mount
     根下( vim /etc/fstab )UUID=e1863969-f85d-4683-be65-422665397343 /boot ---读根分区
     home
                      swap
真正的虚拟的文件:proc,sys,dev(设备文件,不可能存在于磁盘里)    


分区: sb(superblock超级块)__块位图_____inode区________block(块区)____
最小的文件系统为512字节,一块不可能存放两个文件。不同的文件系统适用于不同的大文件或小文件。
inode区(UNIX标识文件用inode号):文件属性(inode中有stat结构体中所有的成员),文件存储的位置---一个inode里存储了所有的属性和数据以及无关的数据,唯独没有文件名,文件名是专门存放在目录文件内的。每一个目录下都有目录文件
文件是如何找到的:目录也是文件,存在于磁盘中,一个目录其实是一个数组,数组里每个成员至少存在两个东西:文件名name和目录项的inode。一个目录下有多个目录项,目录项也是一个数组:name+inode
例:/aa/bb/cc---根下有一个目录,每一个目录项都存在两个东西:文件名name和inod,先找到aa目录项的inod,再找到aa的块,在aa的目录里找到bb目录项的inode,再找到bb的块,在bb的目录里找到cc目录项的inode,再找到cc的块。

链接:
一个目录的链接数至少是2:.和..

块位图:每一位表示一个开关,对应于block,用0/1表示是否被占用。以字节为单位,每一块为512字节(半k),一定能整除8
inode位图:对应于inode,用0/1表示是否被占用。

link
unlink---系统调用
remove---库函数,是用unlink实现的。
rename


符号链接---只存路径的一个文件 ,符号链接本身是可用的,但是使用成功与否在于指向文件的权限      、
int symlink(const char *oldpath, const char *newpath);
ssize_t readlink(const char *path, char *buf, size_t bufsiz);


4096以下的地址全都不可用
系统调用全都是用二进制方法实现的,没有文本操作
系统调用--------------------标准库
文件描述符                  FILE *:可操作文件




系统调用和标准库混用的时候,一定要每次都得fflush,但是最好别混用,很容易出错,因为系统调用没有缓冲
标准库是否一定要基于系统调用:不一定,要看是否进入内核,比如strcat


可以根据fp获得fd:fileno()


lseek:
fseek:
ftell:获取当前位置




sizeof 是关键字不是函数
void fun(int b[100])
{
printf("%d\n",sizeof(b));
}
int *p=NULL;
sizeof(p)=4;   p指向一个NULL的指针;
sizeof(*p)=4; *p是指向指针的第一个数;
int a[100];
sizeof(a)=400; a是数组名,表示整个数组的大小;
sizeof(a[100])=100;
sizeof(&a)=400; &a是指向数组a的地址;
fun(a)=4; 数组传递时是传递数组的头指针,所以是int型;
p=a;则p+1指向a[1]; 
p=&a;则p+1指向a[101];






临时文件
char *tmpnam(char *s)---is dangerous


localtime
gmtime这两个函数返回的指针很可能指向同一块空间造成覆盖,安全的办法就是使用gmtime_r




当涉及到硬件时一般才说中断,其他时间都叫打断
不可打断的休眠是当条件满足时才开始醒


kill要杀死进程得满足几个条件:不能是init;必须具有权限;进程不能处于不可打断的休眠状态
内核不能随便使用user的空间,要调用函数,就得使用栈


进程:
vim /usr/src/kernels/2.6.32-358.el6.x86_64/include/linux/sched.h
进程(并发)的调度是把他们放在队列里&#
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值