C++笔试面试答题(一)

1.头文件中的编译宏

    #ifndef __INCvxWorksh
    #define
 __INCvxWorksh
    #endif
   
作用是防止头文件被重复引用。

2. extern "C"

为了实现CC++的混合编程,C++提供了C连接交换指定符号extern "C"来解决名字匹配问题,函数声明前加上extern "C"后,则编译器就会按照C语言的方式将该函数编译为_foo,这样C语言中就可以调用C++的函数了。

3float x 零值比较的 if 语句。(4分)

const float EPSINON = 0.00001;

if ((x >= - EPSINON) && (x <= EPSINON)

不可将浮点变量用“==”=”与数字比较,应该设法转化成“>=”“<=”此类形式。

4.

void GetMemory(char *p)

{

p = (char *)malloc(100);

}

void Test(void)

{

char *str = NULL;

GetMemory(str);

strcpy(str, "hello world");

printf(str);

}

请问运行Test函数会有什么样的结果?答:程序崩溃。

因为

Test函数中的 str一直都是 NULL

strcpy(str, "hello world");将使程序崩溃。

GetMemory并不能传递动态内存。

有效程序:

void GetMemory2(char **p, int num)

{

*p = (char *)malloc(num);

}

void Test(void)

{

char *str = NULL;

GetMemory(&str, 100);

strcpy(str, "hello");

printf(str);

}

 

5.

char *GetMemory(void)

{

char p[] = "hello world";

return p;

}

void Test(void)

{

char *str = NULL;

str = GetMemory();

printf(str);

}

请问运行Test函数会有什么样的结果?答:可能是乱码。

因为GetMemory返回的是指向栈内存的指针,该指针的地址不是 NULL,但其原现的内容已经被清除,新内容不可知。

6.

已知strcpy函数的原型是
   char *strcpy(char *strDest, const char *strSrc);
   其中strDest是目的字符串,strSrc是源字符串。

  (1)不调用C++/C的字符串库函数,请编写函数 strcpy
  
char *strcpy(char *strDest, const char *strSrc);
  
{
   assert((strDest!=NULL) && (strSrc !=NULL)); // 2

   char *address = strDest; // 2
   while( (*strDest++ = * strSrc++) != ‘/0’ ) // 2
   NULL ;
   return address ; // 2

  }  
  (2strcpy能把strSrc的内容复制到strDest,为什么还要char * 类型的返回值?
  答:为了实现链式表达式。 // 2
  例如 int length = strlen( strcpy( strDest, “hello world”) );

7.

int strlen( const char *str ) //输入参数const

{
   
 assert( str != NULL ); //断言字符串地址非
0
   
 
int len;
   
 
while( (*str++)!= '/0' )
   
 {

  len++;
   
 
}
   
 
return len;
    }

8.

void GetMemory( char **p, int num )
    {
   
 *p = (char *) malloc( num );

assert(*p!=NULL);
    }

void Test( void )
    {
   
 
char *str = NULL;
   
 
GetMemory( &str, 100 );
   
 
strcpy( str, "hello" );
   
 printf( str );

//free(*str);

//*str=NULL;
    }

对内存操作的考查主要集中在:

      (1)指针的理解;

      (2)变量的生存期及作用范围;

      (3)良好的动态内存申请和释放习惯。

9.

    swap( int* p1,int* p2 )
    {
   
 int p;//NOT int *p;
   
 p = *p1;
   
 *p1 = *p2;
   
 *p2 = p;
    }

10.数组名的本质如下:

1)数组名指代一种数据结构,这种数据结构就是数组;

      例如:

char str[10];
      cout<<sizeof(str)<<endl;
   
  输出结果为10str指代数据结构char[10]

2)数组名可以转换为指向其指代实体的指针,而且是一个指针常量,不能作自增、自减等操作,不能被修改;

    char str[10];
    str++; //
编译出错,提示str不是左值
3)数组名作为函数形参时,沦为普通指针。

      Windows NT 32位平台下,指针的长度(占用内存的大小)为4字节,故sizeof( str ) sizeof ( p ) 都为4

11. 防止宏的副作用。

    宏定义#define MIN(A,B) ((A) <= (B) ? (A) : (B))MIN(*p++, b)的作用结果是:

((*p++) <= (b) ? (*p++) : (*p++))

这个表达式会产生副作用,由于运算符优先级,指针p会先作三次++自增操作。

12.编写一个函数,作用是把一个char组成的字符串循环右移n个。比如原来是“abcdefghi”如果n=2,移位后应该是“hiabcdefgh”

    //pStr是指向以'/0'结尾的字符串的指针
    //steps
是要求移动的n

void LoopMove ( char *pStr, int steps )

{

 int n = strlen( pStr ) - steps;

 char tmp[MAX_LEN];

 strcpy ( tmp, pStr + n );

 strcpy ( tmp + steps, pStr);

 *( tmp + strlen ( pStr ) ) = '/0';

 strcpy( pStr, tmp );

}

 

13. 编写类String的构造函数、析构函数和赋值函数,已知类String的原型为:

    class String
    {
   
 public:
   
  String(const char *str = NULL); // 普通构造函数
   
  String(const String &other); // 拷贝构造函数
   
  ~ String(void); // 析构函数
   
  String & operator =(const String &other); // 赋值函数
   
 private:
   
  char *m_data; // 用于保存字符串
    };
   
解答:

    //普通构造函数

String::String(const char *str)
    {
   
 if(str==NULL)
   
 {
   
  m_data = new char[1]; // 得分点:对空字符串自动申请存放结束标志'/0'的空
   
  //加分点:对m_dataNULL 判断

assert(m_data!=NULL);
   
  *m_data = '/0';
   
 }
   
 else
   
 {
   
  int length = strlen(str);
   
  m_data = new char[length+1]; // 若能加 NULL 判断则更好
   
  strcpy(m_data, str);
   
 }
    }

    // String的析构函数

    String::~String(void)
    {
   
 delete [] m_data; // delete m_data;
    }

    //拷贝构造函数

    String::String(const String &other)    // 得分点:输入参数为const
    {
   
 int length = strlen(other.m_data);
   
 m_data = new char[length+1];     //加分点:对m_dataNULL 判断
   
 strcpy(m_data, other.m_data);
    }

    //赋值函数

String & String::operator =(const String &other) // 得分点:输入参数为const
    {
   
 if(this == &other)   //得分点:检查自赋值
   
  return *this;
   
 delete [] m_data;     //得分点:释放原有的内存资源
   
 int length = strlen( other.m_data );
   
 m_data = new char[length+1];  //加分点:对m_dataNULL 判断
   
 strcpy( m_data, other.m_data );
   
 return *this;         //得分点:返回本对象的引用
    }

在这个类中包括了指针类成员变量m_data,当类中包括指针类成员变量时,一定要重载其拷贝构造函数、赋值函数和析构函数。

14. 请说出staticconst关键字尽可能多的作用

static关键字的作用:

1函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,其值在下次调用时仍维持上次的值;

     2)在模块内static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;在OS中处理同步时可以用于记录函数调用的次数。

     3)在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;对于函数来讲,static的作用仅限于隐藏

     4在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;

     5在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。

存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。

在静态数据区,内存中所有的字节默认值都是0x00。首先static的最主要功能是隐藏,其次因为static变量存放在静态存储区,所以它具备持久性和默认值0

const关键字至少有下列n个作用:

      (1)欲阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;

      (2)对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const

      (3)在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;

      (4)对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量;

   5)对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为左值

15. 请写一个C函数,若处理器是Big_endian的,则返回0;若是Little_endian的,则返回1

      解答:

    int checkCPU()
    {
   
 {
   
  union w
   
  {
   
   int a;
   
   char b;
   
  } c;
   
  c.a = 1;
   
  return (c.b == 1);
   
 }
    }
   
  剖析:

      嵌入式系统开发者应该对Little-endianBig-endian模式非常了解。采用Little-endian模式的CPU对操作数的存放方式是从低字节到高字节,而Big-endian模式对操作数的存放方式是从高字节到低字节。例如,16bit宽的数0x1234Little-endian模式CPU内存中的存放方式(假设从地址0x4000开始存放)为:

内存地址存放内容 0x4000 0x34 0x4001 0x12

而在Big-endian模式CPU内存中的存放方式则为:

内存地址存放内容 0x4000 0x12 0x4001 0x34
32bit
宽的数0x12345678Little-endian模式CPU内存中的存放方式(假设从地址0x4000开始存放)为:

内存地址存放内容 0x4000 0x78 0x4001 0x56 0x4002 0x34 0x4003 0x12

而在Big-endian模式CPU内存中的存放方式则为:

内存地址存放内容 0x4000 0x12 0x4001 0x34 0x4002 0x56 0x4003 0x78

联合体union的存放顺序是所有成员都从低地址开始存放,面试者的解答利用该特性,轻松地获得了CPU对内存采用Little-endian还是Big-endian模式读写。如果谁能当场给出这个解答,那简直就是一个天才的程序员。

endian翻译成字节序,将big endianlittle endian称作大尾小尾PowerPC系列采用big endian方式存储数据,而x86系列则采用little endian方式存储数据。那么究竟什么是big endian,其实big endian是指低地址存放最高有效字节(MSB),而little endian则是低地址存放最低有效字节(LSB)。

bool isBigendian()

{

       unsigned short int data=0x1122;

       unsigned char *pdata=(unsigned char*)&data;

       return (*pdata==0x22);

}

15-1嵌入式位操作

#define BIT_MASK(bit_pos) (0x01<<(bit_pos))

int Bit_Reset(unsigned int* var,unsigned int pos)

{

    if(pos>=sizeof(unsigned int)*8)

           return 0;

    *var=(*var & ~BIT_MASK(pos));

    return 1;

}

int Bit_Clear(unsigned int* var,unsigned int pos)

{

    if(pos>=sizeof(unsigned int)*8)

           return 0;

    *var=(*var | BIT_MASK(pos));

    return 1;

}

 

16 找错误

void test2()
    {
   
 char string[10], str1[10];
   
 int i;
   
 for(i=0; i<10; i++)
   
 {
   
  str1[i] = 'a';
   
 }
   
 strcpy( string, str1 );
    }

字符数组str1不能在数组内结束;strcpy(string, str1)调用使得从str1内存起复制到string内存起所复制的字节数具有不确定性;库函数strcpy工作方式。

考查对基本功的掌握:

      (1)字符串以’/0’结尾;

      (2)对数组越界把握的敏感度;

    (3)库函数strcpy的工作方式

17.将属于一个整体的数据成员组织为一个结构体,利用指针类型转换,可以将memcpymemset等函数直接用于结构体地址,进行结构体的整体操作。

18. C++异常处理机制核心观点:

0).如果使用普通的处理方式:ASSERT,return等已经 足够简洁明了,请不要使用异常处理机制. 1).Csetjump,longjump优秀.

2).可以处理任意类型的异常. 你可以人为地抛出任何类型的对象作为异常. throw 100; throw "hello"; ...

3).需要一定的开销,频繁执行的关键代码段避免使用 C++异常处理机制.

4).其强大的能力表现在:

A.把可能出现异常的代码和异常处理代码隔离开,结构更清晰.

B.把内层错误的处理直接转移到适当的外层来处理,化简了处理流程.传统的手段是通过一层层返回错误码把错误处理转移到上层,上层再转移到上上层,当层数过多时将需要非常多的判断, 以采取适当的策略.

C.局部出现异常时,在执行处理代码之前,会执行堆栈回退,即为所有局部对象调用析构函数,保证局部对象行为良好.

D.可以在出现异常时保证不产生内存泄漏.通过适当的try,catch 布局,可以保证delete pobj;一定被执行.

E.在出现异常时,能够获取异常的信息,指出异常原因. 并可以给用户优雅的提示.

F.可以在处理块中尝试错误恢复.保证程序几乎不会崩溃. 通过适当处理,即使出现除0异常,内存访问违例,也能让程序不崩溃,继续运行,这种能力在某些情况下及其重要.

以上ABCDEF可以使你的程序更稳固,健壮,不过有时让程序崩溃似乎更容易找到原因,程序老是不崩溃,如果处理结果有问题,有时很难查找.

5).并不是只适合于处理'灾难性的'事件.普通的错误处理也可以用异常机制来处理,不过如果将此滥用的话,可能造成程序结构混乱, 因为异常处理机制本质上是程序处理流程的转移,不恰当的,过度的转移显然将造成混乱.许多人认为应该只在'灾难性的'事件上使用异常处理,以避免异常 处理机制本身带来的开销,你可以认为这句话通常是对的.

6).先让程序更脆弱,再让程序更坚强.首先,它使程序非常脆弱,稍有差错,马上执行流程跳转掉,去寻找相应的处理代码,以求适当的解决方式. 很像一个人身上带着许多药品,防护工具出行,稍有头晕,马上拿出清凉油; 遇到蚊子立刻拿出电蚊拍灭之.

WINDOWS:

7).将结构化异常处理结合/转换到C++异常对象,可以更好地处理WINDOWS程序出现的异常. 8).尽一切可能使用try,catch,而不是win32本身的结构化异常处理或者MFC中的TRY,CATCH

 

WINDOWS 结构化异常处理SEH实际上包含两方面的功能:终止处理(termination handling)和异常处理(exception handling)。

1.终止处理

    终止处理程序确保不管一个代码块(被保护代码(the guarded body))是如何退出的,另一个代码块(终止处理程序)总能被调用和执行。终止处理的语法(当使用Microsoft Visual C++编译器时)如下所示:
         __try {
        // Guarded body
        
被保护代码
        ...
    }
    __finally {
        // Termination handler
        
终止处理程序
        ...
    }
    
在这段代码中,操作系统和编译器的协同工作保证了不管被保护代码部分是如何退出的——无论我们在被保护代码中使用了return,还是goto,又或者longjump语句(除非调用ExitProcess , ExitThread , TerminateProcess , TerminateThread 来终止进程或线程)——终止处理程序都会被调用,即 __finally 代码块都能执行。

2.异常处理程序与软件异常
   
CPU抛出的异常都是硬件异常 ,操作系统和应用程序也可以抛出异常,这些异常通常被称为软件异常

   
当一个硬件或者软件异常被抛出时,操作系统会给我们的应用程序一个查看异常类型的机会,并允许应用程序逐级处理这个异常。

 

C++异常与结构化异常的比较
    SEH
是操作系统所提供的便利,它在任何语言中都是可以使用的。而C++异常处理只有在编写C++代码时才可以使用。如果读者在开发C++应用程序,那就应该使用C++异常,而不是结构化异常(SEH)。理由是C++异常是语言的一部分,编译器知道什么是一个C++对象。这也就意味着编译器会自动生成代码来调用 C++对象的析构函数,保证对象的释放。

    
我们应该了解MicrosoftVisual C++编译器使用操作系统的结构化异常机制来实现C++异常处理机制。所以,在创建一个C++ try块时,编译器会为我们生成一个SEH __try块。C++catch语句对应SEH异常过滤程序,catch块中的代码则对应SEH __except块中的代码。而编译器也会为C++ throw语句生成对Windows RaiseException函数的调用。throw语句所使用的变量则成为RaiseException的附加参数。

 

18.进程与线程的差别

1)进程是指在系统中正在运行的一个应用程序, 它是系统资源分配(内存、CPU时间片)和调度的基本单位;线程是系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元。对于操作系统而言其调度单元是线程。进程的生存期状态包括创建、就绪、运行、阻塞和死亡等类型。处于用户态下的进程执行的是应用程序指令,处于核心态下的应用程序进程执行的是操作系统指令。

2)线程是进程的一个实体,CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

3)进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

4)每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口,但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

5一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行。
6)代表应用程序的进程中多个线程共享数据内存空间,但保持每个线程拥有独立的执行堆栈和程序执行上下文(Context)。  

7)进程与应用程序的区别在于应用程序作为一个静态文件存储在计算机系统的硬盘等存储空间中,而进程则是处于动态条件下由操作系统维护的系统资源管理实体。

 

 

19 进程间通信的方式有?

进程是一个正在运行的程序的实例,主要由两部分组成:(1)一个操作系统用来管理进程的内核对象。(2)创建时系统所分配的资源,主要是内存地址空间。

进程间通信是指在多进程环境下,使用的数据交互、事件通知等方法使各进程协同工作。常用的有4种:消息传递、共享内存、管道通信、剪贴板:

消息传递:不以进程为界限,处理消息的是窗体(包括子窗体、控件等),而与是否在同一进程无关。与管道不同的是在某个进程写入消息之前,并不需要另外某个进程在该队列上等待消息的到达。这是因为消息队列的生命周期是跟随内核的。

共享内存:在一个进程内创建内存映射,却能够在其他多个进程中使用。这些进程共享的是物理存储器的同一个页面,在把这些物理内存映射到虚拟内存时各个进程的虚拟地址并不一定相同。当一个进程将数据写入共享内存时,其他进程可以立即获取数据变更情况。所有的数据交换都是在内存中完成的。

管道通信:管道的生命周期是跟随进程的。而且在往管道中写入数据时,必须存在读取数据的进程。管道可以传递任何数据,对于管道来说,传的是一个字节串。无论是什么类型的数据,要传递,最后总是需要格式化成字节串。

剪贴板:Windows系统支持剪贴板IPC基本机制是由系统预留的一块全局共享内存,可用于被各进程暂时存储数据。

 

 

20. 线程同步的方式(Mutex, Event, Critical SectionSemaphore)

(1) 互斥对象和事件对象属于内核对象,利用内核对象进行线程同步,速度较慢,但利用互斥对象和事件对象这样的内核对象,可以在多个进程中的各个线程间进行同步。

(2) 关键代码段是工作在用户方式下,同步速度较快,但在使用关键代码段时,很容易进入死锁状态,因为在等待进入关键代码段时无法设定超时值。

1临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。

  保证在某一时刻只有一个线程能访问数据的简便办法。在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。

临界区包含两个操作原语:EnterCriticalSection() 进入临界区,LeaveCriticalSection() 离开临界区。

EnterCriticalSection()语句执行后代码将进入临界区以后无论发生什么,必须确保与之匹配的LeaveCriticalSection()都能够被执行到。否则临界区保护的共享资源将永远不会被释放。虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。

2互斥量:为协调共同对一个共享资源的单独访问而设计的。

互斥量跟临界区很相似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源。互斥量比临界区复杂。因为使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。

互斥量包含的几个操作原语:
  CreateMutex() 创建一个互斥量
  OpenMutex() 打开一个互斥量
  ReleaseMutex() 释放互斥量
  WaitForMultipleObjects() 等待互斥量对象

3信号量:为控制一个具有有限数量用户资源而设计。

信号量对象对线程的同步方式与前面几种方法不同,信号允许多个线程同时使用共享资源,这与操作系统中的PV操作相同。它指出了同时访问共享资源的线程最大数目。它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。

4事件:用来通知线程有一些事件已发生,从而启动后继任务的开始。

事件对象也可以通过通知操作的方式来保持线程的同步。并且可以实现不同进程中的线程同步操作。

1.Critical Section

A.速度快

B.不能用于不同进程

C.不能进行资源统计(每次只可以有一个线程对共享资源进行存取)

 

2.Mutex

A.速度慢

B.可用于不同进程

C.不能进行资源统计

 

3.Semaphore

A.速度慢

B.可用于不同进程

C.可进行资源统计(可以让一个或超过一个线程对共享资源进行存取)

 

4.Event

A.速度慢

B.可用于不同进程

C.可进行资源统计

 

 

21. 对序列11235813。。。。  Fab..数列。23513...Fab..质数数列,因为他们与自己前面的Fab...数列都互质给出k,返回第k小的Fab..质数。

 

 

22. 完成字符串拷贝可以使用 sprintfstrcpy memcpy 函数,请问这些函数有什么区别,你喜欢使用哪个,为什么?

这些函数的区别在于 实现功能 以及 操作对象 不同。


strcpy
函数操作的对象是 字符串,完成 源字符串 目的字符串 拷贝 功能。


sprintf
函数操作的对象 不限于字符串:虽然目的对象是字符串,但是源对象可以是字符串、也可以是任意基本类型的数据。这个函数主要用来实现 (字符串或基本数据类型)向 字符串 的转换 功能。如果源对象是字符串,并且指定 %s 格式符,也可实现字符串拷贝功能。


memcpy
函数顾名思义就是 内存拷贝,实现 将一个 内存块 的内容复制到另一个 内存块 这一功能。内存块由其首地址以及长度确定。程序中出现的实体对象,不论是什么类型,其最终表现就是在内存中占据一席之地(一个内存区间或块)。因此,memcpy 的操作对象不局限于某一类数据类型,或者说可 适用于任意数据类型,只要能给出对象的起始地址和内存长度信息、并且对象具有可操作性即可。鉴于 memcpy 函数等长拷贝的特点以及数据类型代表的物理意义,memcpy 函数通常限于同种类型数据或对象之间的拷贝,其中当然也包括字符串拷贝以及基本数据类型的拷贝。

对于字符串拷贝来说,用上述三个函数都可以实现,但是其实现的效率和使用的方便程度不同:

  • strcpy 无疑是最合适的选择:效率高且调用方便。
  • snprintf 要额外指定格式符并且进行格式转化,麻烦且效率不高。
  • memcpy 虽然高效,但是需要额外提供拷贝的内存长度这一参数,易错且使用不便;并且如果长度指定过大的话(最优长度是源字符串长度 + 1),还会带来性能的下降。其实 strcpy 函数一般是在内部调用 memcpy 函数或者用汇编直接实现的,以达到高效的目的。因此,使用 memcpy strcpy 拷贝字符串在性能上应该没有什么大的差别。

对于非字符串类型的数据的复制来说,strcpy sprintf 一般就无能为力了,可是对 memcpy 却没有什么影响。但是,对于基本数据类型来说,尽管可以用 memcpy 进行拷贝,由于有赋值运算符可以方便且高效地进行同种或兼容类型的数据之间的拷贝,所以这种情况下 memcpy 几乎不被使用。memcpy 的长处是用来实现(通常是内部实现居多)对结构或者数组的拷贝,其目的是或者高效,或者使用方便,甚或两者兼有。

22-2写一个函数,完成内存之间的拷贝。[考虑问题是否全面]

void* mymemcpy( void *dest, const void *src, size_t count )

{

char* pdest = static_cast<char*>( dest );

const char* psrc = static_cast<const char*>( src );

if( pdest>psrc && pdest<psrc+cout ) 能考虑到这种情况就行了

{

for( size_t i=count-1; i!=-1; --i )

pdest[i] = psrc[i];

}

else

{

for( size_t i=0; i<count; ++i )

pdest[i] = psrc[i];

}

return dest;

}

 

 

23. 请完成以下题目。注意,请勿直接调用 ANSI C 函数库中的函数实现。

ASCII编码表中,{123…a,b,c,d…}均为字符,如字符‘0’对应的ASCII码是48,字符‘1’对应的ASCII码是49

   a)请编写一个 C 函数,该函数给出一个字节中被置 1 的位的个数,并请

     给出该题的至少一个不同解法。

int   check1_in_n(int   n)  
  {  
  int   i,count=0;  
  for   (i=0;   i<8;   i++)   {  
  if   (n   &   1)   count++;  
  n>>=1;  
  }  
  return   count;  
  }  

int check2_in_n(int x)

{

    int count1inx = 0;

    while(x)

    {

          count1inx ++;

          x = x&(x-1);

     }

    return count1inx;

}

 

   b)请编写一个 C 函数,该函数将给定的一个字符串转换成整数。

int trans_string_to_int(const char *s)

{

    assert(s!=NULL);

    int rev=0;

    int n=1;

    if(*s=='-')

    {

           n=-1;

           s++;

    }

    while(*s!='/0')

    {

           if(!isdigit(*s))

                  return -1;

           rev=rev*10+(*s-'0');  //ASCII of ‘0’ is 48

           s++;

    }

    return (n*rev);

}  

 

c)请编写一个 C 函数,该函数将给定的一个整数转换成字符串。

void trans_int_to_string(int integer)

{

     int intcopy=integer;

     int len=0,count=0,i=0;

     char intstr[20];

     while(intcopy)

     {

            intcopy/=10;

            ++len;

     }

     count=len;

     --len;

 

     while(integer!=0)

     {

            intstr[len]=char((integer%10)+'0');

            integer/=10;

            --len;

     }

     intstr[count]='/0';

     while(intstr[i]!='/0')

     {

            cout<<intstr[i];

            ++i;

     }

     cout<<endl;

}

 

   d)请编写一个 C 函数,该函数将一个字符串逆序。

exchange(char *str)
{
   char tmp;
   int len, i, j;

   len = strlen(str);
   for(i = 0, j = len-1; i != len/2; i++, j--) {
      tmp = str[i];
      str[i] = str[j];
      str[j] = tmp;
   }

 

   e)请编写一个 C 函数,该函数在给定的内存区域搜索给定的字符,并返回

     该字符所在位置索引值。

int search(char *cpSource,char ch)

{

    int count=0;

    while((*cpSource++)!=ch)

    {

           if(*cpSource=='/0')

                  return -1;

           ++count;

    }

    return count;

}

   f)请编写一个 C 函数,该函数在一个字符串中找到可能的最长的子字符串,

     该字符串是由同一字符组成的。

char   *   get_max_long_same_str(const   char   *   in_str)  

  {  

    char   *   out_str   =   new   char[MAX_LEN];  

    char   *   out_str_save   =   out_str;  

   

    int   max_str_len   =   0;  

    char   check_char   =   *in_str;  

    char   the_char   =   check_char;  

    int   jsq_len   =   1;  

   

    while(*(++in_str))  

    {  

      if(check_char   ==   *in_str)  

      {  

        jsq_len++;   

        if(max_str_len   <   jsq_len)  

        {  

          max_str_len   =   jsq_len;  

          the_char   =   check_char;  

        }  

      }  

      else  

      {  

        if(max_str_len   <   jsq_len)  

        {  

          max_str_len   =   jsq_len;  

          the_char   =   check_char;  

        }  

                check_char   =   *in_str;  

                jsq_len   =   1;  

      }  

    }

      for(int   i   =   0;   i   <   max_str_len;   i++)  

      {  

        *out_str++   =   the_char;  

      }  

      *out_str   =   '/0';      

      return   out_str_save;  

   

  }

g). char *strcat( char *strDestination, const char *strSource )

{

    ASSERT(strDestination);

    ASSERT(strSource);

    char* tmp = strDestination;

    while (*strDestination)

    {

        strDestination++;

    }

   while (*strSource)

    {

        *strDestination = *strSource;

        strDestination++;

        strSource++;

    }

    *strDestination = '/x00';

    return tmp;

}

h).  int strcmp( const char *string1, const char *string2 )

{

    ASSERT(string1);

    ASSERT(string2);

    while (*string1 || *string2)

    {

        if (*string1 - *string2)

        {

            return *string1 - *string2;                

        }

        string1++;    

        string2++;

    }

   return 0;

}

 

给出演示上述函数功能的一个简单程序,并请编写对应的 Makefile 文件。

 

 

24. 对如下电文:"CASTCASTSATATATASA"给出Huffman编码。

哈夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树。所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。树的带权路径长度记为WPL=(W1*L1+W2*L2+W3*L3+...+Wn*Ln)N个权值Wi(i=1,2,...n)构成一棵有N个叶结点的二叉树,相应的叶结点的路径长度为Li(i=1,2,...n)。可以证明哈夫曼树的WPL是最小的。

哈夫曼在上世纪五十年代初就提出这种编码时,根据字符出现的概率来构造平均长度最短的编码。它是一种变长的编码。在编码中,若各码字长度严格按照码字所对应符号出现概率的大小的逆序排列,则编码的平均长度是最小的。

步骤进行:
l
)将信号源的符号按照出现概率递减的顺序排列。
2
)将两个最小出现概率进行合并相加,得到的结果作为新符号的出现概率。
3
)重复进行步骤12直到概率相加的结果等于1为止。

4
)在合并运算时,概率大的符号用编码0表示,概率小的符号用编码1表示。
5
)记录下概率为1处到当前信号源符号之间的0l序列,从而得到每个符号的编码。

静态的哈夫曼编码,它对需要编码的数据进行两遍扫描:第一遍统计原数据中各字符出现的频率,利用得到的频率值创建哈夫曼树,并必须把树的信息保存起来;第二遍则根据第一遍扫描得到的哈夫曼树进行编码,并把编码后得到的码字存储起来。静态哈夫曼编码方法有一些缺点:一、对于过短的文件进行编码的意义不大,因为光以4BYTES的长度存储哈夫曼树的信息就需1024Bytes的存储空间;二、进行哈夫曼编码,存储编码信息时,若用与通讯网络,就会引起较大的延时;三、对较大的文件进行编码时,频繁的磁盘读写访问会降低数据编码的速度。

 

 

25. 中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展让标准C支持中断。代表事实是,产生了一个新的关键字__interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。

__interrupt double compute_area (double radius)

{

double area = PI * radius * radius;

printf(" Area = %f", area);

return area;

}

这个函数有太多的错误了,以至让人不知从何说起了:

1). ISR 不能返回一个值。如果你不懂这个,那么你不会被雇用的。

2). ISR 不能传递参数。如果你没有看到这一点,你被雇用的机会等同第一项。

3). 在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的。

4) .printf()经常有重入和性能上的问题。

怎么会有可重入和不可重入的概念呢?

在多任务系统下,中断可能在任务执行的任何时间发生;如果一个函数的执行期间被中断后,到重新恢复到断点进行执行的过程中,函数所依赖的环境没有发生改变,那么这个函数就是可重入的,否则就不可重入。

在中断前后不都要保存和恢复上下文吗,怎么会出现函数所依赖的环境发生改变了呢?

我们知道中断时确实保存一些上下文,但是仅限于返回地址,cpu寄存器等之类的少量上下文,而函数内部使用的诸如全局或静态变量,buffer等并不在保护之列,所以如果这些值在函数被中断期间发生了改变,那么当函数回到断点继续执行时,其结果就不可预料了。

满足下面条件之一的多数是不可重入函数:

(1)使用了静态数据结构;

(2)调用了mallocfree;

(3)调用了标准I/O函数;

(4)进行了浮点运算.

 

malloc/free是不可重入的,它们使用了全局变量来指向空闲区;标准I/O库的很多实现都使用了全局数据结构; 许多的处理器/编译器中,浮点一般都是不可重入的 (浮点运算大多使用协处理器或者软件模拟来实现)

 

在信号处理程序及多线程编程时,要特别注意。

考虑这种情况:

1) 信号处理程序A内外都调用了同一个不可重入函数BB在执行期间被信号打断,进入A (A中调用了B),完事之后返回B被中断点继续执行,这时B函数的环境可能改变,其结果就不可预料了。

2) 多线程共享进程内部的资源,如果两个线程AB调用同一个不可重入函数FA线程进入F后,线程调度,切换到BB也执行了F,那么当再次切换到线程A时,其调用F的结果也是不可预料的。

在信号处理程序中即使调用可重入函数也有问题要注意。作为一个通用的规则,当在信号处理程序中调用可重入函数时,应当在其前保存errno,并在其后恢复errno(要了解经常被捕捉到的信号是SIGCHLD,其信号处理程序通常要调用一种wait函数,而各种wait函数都能改变errno)

不可重入函数指的是该函数在被调用还没有结束以前,再次被调用可能会产生错误。

 

 

26.

char *ptr;

if ((ptr = (char *)malloc(0)) == NULL)

puts("Got a null pointer");

else

puts("Got a valid pointer");

 

如果所请求的空间大小为0,其行为由库的实现者定义:可以返回空指针,也可以让效果跟申请某个非0大小的空间一样,所不同的是返回的指针不可以被用来访问一个对象。

为什么 new T[0] malloc(0) 不返回空指针

首先需要说明的是,按照C++标准,成功的 new T[0] 是不能返回空指针的。而 malloc(0)C 语言标准则指出,成功的时候可以返回空指针,也可以返回非空指针,多数库一般也选择了返回非空指针这种行为。
为什么这么做呢?
1.
理念:0大小的对象也是对象。
2.
实践:返回空会和分配失败混淆,尤其是大小是计算出来的时候,这时如果得到的是空指针,用户程序会误以为分配失败了。
3.
实现:反正返回的地址不能读写,此时可以返回同一个固定的地址,并没什么额外开销。而且多数编译器,CC++一起提供,C++库的new也用C库的malloc实现,使二者保持一致也比较省事。
4.
不管返回的是不是空,都可以free的。

 

27. 一个单向链表,不知道头节点,一个指针指向其中的一个节点,问如何删除这个指针指向的节点?

将这个指针指向的next节点值copy到本节点,将next指向next->next,并随后删除原next指向的节点。

       q=p->next;

       p->value=q->value;

       p->next=q->next;

       delete q;

 

28. 1000!的未尾有几个0(用素数相乘的方法来做,如72=2*2*2*3*3;

11000中能够相乘变为10的倍数的数可以分为下面几类数:

1)能被5整除而不能被25整除(共有1000/5-1000/25=160

2)能被25整除而不能被125整除(共有1000/25-1000/125=32

3)能被125整除而不能被625整除(共有1000/125-[1000/625]=7)

4)能被625整除(625一个数)

而从11000共有偶数500个,足够52550125625500乘以若干个2(包括零个)之后成为10(一个零),100(两个零),1000(三个零),10000(四个零)。

因而零的个数可以计算出来

从第一类算起,应该是160*1+32*2+7*3+1*4应该是249个零。

 

求出1->1000,能被5整除的数的个数n1,能被25整除的数的个数n2,能被125整除的数的个数n3,能被625整除的数的个数n4.

1000!末尾的零的个数=n1+n2+n3+n4;

#include

#define NUM 1000

 

int find5(int num){

int ret=0;

while(num%5==0){

num/=5;

ret++;

}

return ret;

}

int main(){

int result=0;

int i;

for(i=5;i<=NUM;i+=5)

{

result+=find5(i);

}

printf(" the total zero number is %d/n",result);

return 0;

}

 

29. y10进制数据,x28(oct)16(hex)进制数据         0x0116进制表示法

void f(int x,int y)  //x=2 or 8

{

 stack<int> s;

 while (y) {

  s.push(y%x);

  y=y/x;

 }

 while(!s.empty()){

  int i;

  i=s.top();

  s.pop();

  cout<<i;

 }

 cout<<endl;

}

 

void dec_to_hex(int n)

{

       stack<int> s;

       int j=0;

       while(n)

       {

              s.push(n%16);

              n/=16;

       }

       while(!s.empty())

       {

                     int i=0;

                     i=s.top();

                     s.pop();

                     if(i>=10)

                     {

                            switch(i)

                            {

                                   case 10: cout<<"A";

                                          break;

                                   case 11: cout<<"B";

                                          break;

                                   case 12: cout<<"C";

                                          break;

                                   case 13: cout<<"D";

                                          break;

                                   case 14: cout<<"E";

                                          break;

                                   case 15: cout<<"F";

                                          break;

default: break;

                            }

                     }

                     else

                            cout<<i;

                     cout<<endl;

       }

}

 

 

30.16进制转换为10进制

int  sixt_to_ten(char   *p)  
  {  
  int   i=0,sum=0;  
  while(*p!='/0')  
  {  
  i=0;  
  if((*p)>='A'&&(*p)<='F')  
  {  
  switch(*p)  
  {  
  case   'A':   i=10;break;  
  case   'B':   i=11;break;  
  case   'C':   i=12;break;  
  case   'D':   i=13;break;  
  case   'E':   i=14;break;  
  case   'F':   i=15;break;  
  default: break ;  
  };  
  }  
  else   if((*p)>='0'&&(*p)<='9')  
  {  
  i=(int)(*p-‘0’);  
  };  
  if(i!=0)  
  sum=sum*16+i;
 
  p++;    };  
  return   sum;  
  };  

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值