C/C++笔试面试常见题目2

13 篇文章 0 订阅

第一、转int到str

//使用函数sprintf将整形数字格式化
 char* int2str(int nSrc,char*sDest)
 {
	 if(sDest == NULL)
		 return NULL;
	 char nSrc_arry[64] = {0};
	 sprintf(nSrc_arry,"%d",nSrc);

   memcpy(sDest,nSrc_arry,strlen(nSrc_arry));
	 return sDest;
 }
 //先逆序保存起来,在从尾部取出数据保存在str中
int int2str(int num, char * str)
{
	int sign, count;
	char buf[12] = {0};

	sign = num<0 ? -1:1;	//标志位
	num *= sign;
  //将这个整数逆序存放在数组中
	for(count=0; num; num/=10, count++)
	{
		buf[count] = num%10 + 48;
  }
	if(sign == -1) 
	   buf[count++] = '-';
  //逆序去除数据存入到str中
	while(count)
	{
		*(str++) = buf[count-1];
		count--;
	}
	return 0;
}
 //不适用任何的库函数实现
  char* int2str(int nSrc,char*sDest)
 {
	 if(sDest == NULL)
		 return NULL;
	 //处理负数
	 if(nSrc < 0)
	 {
		 *sDest = '-';
		 sDest++;
		 nSrc *= -1;
	 }
	 //计算这个整数有多少个整数位
	 int n_bit = 0;
	 int m = nSrc;
	 char *p_sDest = sDest;
	 do
	 {
		 n_bit++;
		 m = m/10;
	 }
	 while(m!=0);
	 //填充这个整数位到sDest中
	 int x_src = nSrc;
	 *(p_sDest+n_bit) = '\0';
	 do
	 {
		 *(p_sDest+n_bit-1) = x_src%10 + '0';
		 n_bit--;
		 x_src = x_src/10;
	 }
	 while(x_src != 0);
	 return p_sDest;
 }

以上三种方法中,第三种比较繁琐。另外出题者的意图不是很清楚,后续再做研究。先做出来再说。

第二、C标准库函数的安全性问题

 C里操作字符串很高效,但也很麻烦。
1. char * strcpy ( char * destination, const char * source );
最常用的函数,但是却不安全,原因在于,一是要destination有足够的空间,二是要保证source和destination指向的空间没有overlap。
2. int sprintf ( char * str, const char * format, ... );
也许要问,这个怎么用于字符串拷贝呢?可以这么用 sprintf(dest, "%s", src); 但是要调用者保证dest有足够的内存存放src。
3. char * strncpy ( char * destination, const char * source, size_t num );
比起strcpy,多了个长度的控制。从source拷贝num个字符到destination。如果source里不够num字符怎么办呢?会补充0。
一个典型的用法是:
char buf[MAX];
strncpy(buf, src, MAX-1);
这段代码的本意是,一个长为MAX的buf,最多也就放MAX-1个字符,最后一个位置放‘\0'。因此最多能从src里拷贝MAX-1个字符,如果src里没这么多,剩余的填充0就是了。
但是这样做就安全了么?不是,如果src刚好MAX-1个字符。注意到strncpy只复制了MAX-1个字符,最后一个位置未知,有潜在的隐患。下段代码可以诠释:
#define MAX 4
char buf[MAX];
char* src="123";
// solution 1. memset(buf, 0, MAX);
strncpy(buf, src, MAX-1);
// solution 2. buf[MAX-1] = '\0';
printf("%s\n", buf);
有两个办法可以解决:1. 调用strncpy之前memset为0,有点浪费。2. 在strncpy之后对最后一个字符赋值为0。
都可以,但不够优雅。
4. int snprintf( char *buffer, int buff_size, const char *format, ... );
用作字符串拷贝的用法:
char buf[MAX];
snprintf(buf, sizeof(buf), "%s", src);
即安全,又简洁。
你可能会关心:如果src的长度大于dest(buf)呢?这个是另外一个问题,这里需要的是安全的字符串拷贝,在C语言里,如果一个字符串指针指向的内存没有结尾字符'\0',是非常危险的。
snprintf会把buf的最后一个位置保留为'\0'。
关于返回值:如果当前buf够用,返回实际写入的字符数;如果不够用,返回将要写入的字符数。换句话说,返回值就是传入的字符数目。
假设当前的buf[4].
待写入    实际写入    返回值
12           12     2  够用
123        123   3  够用
1234     123   4  不够用
12345  123   5  不够用
sprintf/snprintf的另外一个用法:
itoa不是ANSI C或C++的一部分,可以变相的用sprintf来代替:
sprintf(str,"%d",value)  转换为十进制数值。
sprintf(str,"%x",value)  转换为十六进制数值。
sprintf(str,"%o",value)  转换为八进制数值。
总结:
一方面是不够容纳的问题 ,另一个是内存重叠的问题;
复制的时候检查目标内存的大小;对于C字符串警惕字符串结束标识‘\0’是否存在;
C标准库不安全的API可能引入漏洞,所以微软strsafe为之做了改进;

第三、可重入和线程安全的关系

1、概念比较
(1)一个函数对于多个线程是可重入的,则这个函数是线程安全的。
(2)一个函数是线程安全的,但并不一定是可重入的。线程安全强调“排队”,或者说“同步”;
(3)可重入性要强于线程安全性。即可重入必线程安全。
(4)可重入函数,是线程安全函数的一种。特点:当它们被多个线程调用时,不会引用任何共享数据,也就是不引用静态或全局变量。
(5)一个线程安全的函数通过“锁”来保护共享的资源不被并发地访问。
2、可重入函数:
(1)不为连续的调用持有静态数据。
(2)不返回指向静态数据的指针;所有数据都由函数的调用者提供。
(3)使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据。
(4)如果必须访问全局变量,记住利用互斥信号量来保护全局变量。
(5)绝不调用任何不可重入函数。
3、不可重入函数:
(1)函数中使用了静态变量,无论是全局静态变量还是局部静态变量。
(2)函数返回静态变量。
(3)函数中调用了不可重入函数。
(4)函数体内使用了静态的数据结构;
(5)函数体内调用了malloc()或者free()函数;
(6)函数体内调用了其他标准I/O函数。printf不可重入
(7)函数是singleton中的成员函数而且使用了不使用线程独立存储的成员变量 。
4、在大部分情况下,不可重入的函数可以修改成可重入的函数,这需要修改函数的对外接口。
很多不可重入的函数返回一个指向静态数据的指针。要把这样的不可重入函数改写为可重入函数时,有两种解决办法:
(1)返回从堆中分配的空间的地址。在这种情况下,调用者必须负责释放堆中的空间。这种办法的优点是不必修改函数的外部接口,但是不能向后兼容。现存的单线程的程序使用修改后的函数会导致内存泄露(因为它们没有释放空间)。
(2)由调用者提供空间。尽管函数的外部接口需要改变,仍然推荐这种方法。
5、使一个函数变成线程安全的
在一个多线程的程序中,所有的被多线程调用的函数多必须是线程安全的(或可重入的)。注意,不可重入的函数一般都是线程“不安全”的,然而,将它们改写成可重入的同时,一般就会将它们变成线程安全的。
“锁”住共享资源
使用静态数据或者其它任何共享资源(如文件、终端等)的函数,必须对这些资源加“锁”以实现对它们的串行访问,这样才能成为线程安全的函数。例如:
在一个使用线程库的多线程程序中,应该使用信号量来串行化共享资源的访问,或者其它“锁”。 
总的来说,如果一个函数在重入条件下使用了未受保护的共享的资源,那么它是不可重入的。

第四、IO多路复用问题
说到底就是一个select的使用问题,select观察文件描述符的读写状态,设定超时时间即可。
第五、网络编程中的状态机问题
就是说这个netclient目前的状态,是已经断开、已经连接、密码错误、还是被服务器踢开等。来决定这个类的使用。

第六、打印完数

求2000以内的所有“完数”。所谓“完数”是指一个数恰好等于它的因子值和。要求:所有的完数放在一个数组中,并输出所有完数。
IsWanshu(int Num)
{
    int i = 2;
    int total = 1;
	for(;i<Num;i++)
	{
		if(Num%i == 0)
			total += i;
		if(total == Num)
		{
			printf("%d\n",Num);
			return 0;
		}

	}
    return 0;
}
int main(void)
{
	int i = 0;
	for(;i < 2000;i++)
	{
		IsWanshu(i);

	}
	return EXIT_SUCCESS;
}

第七、抽象类的使用

class CAbstract

{
    virtual int fun() = 0;
}
CAbstract是一个抽象类,抽象类不能实例化,所以:
(1)作为该对象作为参数是不可以的,int fun(CAbstract A)错误
(2)作为函数的返回值对象是不可以的,CAbstract fun();错误
(3)定义抽象类的对象是错的,CAbstract obj;错误
抽象类的使用都是作为指针和引用方式出现,例如:
int fun1(CAbstract *A)
{
   return 0;
}
或者
int fun2(CAbstract &A)
{
   return 0;
}
或者定义CAbstract *p;

第八、关于虚函数的使用

#include <iostream>

#include <stdio.h>
using namespace std;
class A
{
public:
	A()	{doSth();}
	virtual void doSth(){printf("I am A!\n");}
};
class B:public A
{
public:
	virtual void doSth(){printf("I am B!\n");}
};
int main() 
{
	A*pa = new B();
	B*pb = new B();
	pa->doSth();
	pb->doSth();
	delete pa;
	delete pb;
	return 0;
}
//结果:
I am A!
I am A!
I am B!
I am B!
//结论:父类的构造函数调用的是父类的doSth函数,而不是子类的doSth函数。
//      这道题只是关于构造函数的,子类的构造函数是默认的,没有打印信息
//      覆盖,虚函数实现多态。pa指向子类的空间,调用的是子类的doSth函数

第九、string类的实现

这个实现比较简单,这里是从网友哪里获取的代码,修改了一些编译中出现的bug,还有按照自己的书写习惯

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<iostream>

using namespace std;

class CMyString
{
        friend std::ostream& operator<<( std::ostream& os, const CMyString& str);
        private:
                char* m_pData; //  私有变量保存字符串
        public:
                CMyString( const char* str = NULL ); // 构造函数
                CMyString( const CMyString& str ); // 拷贝构造函数
                ~CMyString( void ); // 析构函数
                CMyString& operator=( const CMyString& str ); // 赋值运算符
                CMyString operator+( const CMyString& str ); // 字符串连接
                bool operator==( const CMyString& str ); // 判断相等
                char operator[]( int idx ); // 数组索引
                int getLength(); // 返回长度
};

CMyString::CMyString( const char* str ) // 构造函数
{
        if ( str == NULL )
        {
                this->m_pData = NULL;
        }
        else
        {
                this->m_pData = new char[ strlen( str ) + 1 ];
                strcpy( this->m_pData, str );//'\0'
        }
}

CMyString::CMyString( const CMyString& str )// 拷贝构造函数
{
        if ( str.m_pData == NULL)
        {
                this->m_pData = NULL;
        }
        else
        {
                this->m_pData = new char[ strlen( str.m_pData ) + 1 ];
                strcpy( this->m_pData, str.m_pData );
        }
}

CMyString::~CMyString( void )// 析构函数
{
        if ( this->m_pData)
        {
                delete[] this->m_pData;
                this->m_pData = NULL;
        }
}

CMyString& CMyString::operator=( const CMyString& str)
{
        if ( this != &str )
        {
                delete[] this->m_pData;
                if ( !str.m_pData )
                {
                        this->m_pData = 0;
                }
                else
                {
                        this->m_pData = new char[ strlen( str.m_pData ) + 1 ];
                        strcpy( this->m_pData, str.m_pData );
                }
        }
        return *this;
}

CMyString CMyString::operator+( const CMyString& str )
{
        CMyString newString;
        if ( !str.m_pData )
        {
                newString = *this;
        }
        else if ( !this->m_pData )
        {
                newString = str;
        }
        else
        {
                newString.m_pData = new char[ strlen( this->m_pData ) + strlen( str.m_pData ) + 1 ];
                strcpy( newString.m_pData, this->m_pData );
                strcat( newString.m_pData, str.m_pData );
        }

        return newString;

}

bool CMyString::operator==( const CMyString& str )
{
        if ( strlen(this->m_pData) != strlen( str.m_pData ) )
        {
                return false;
        }
        else
        {
                return strcmp( this->m_pData, str.m_pData ) ? false : true;
        }
}

char CMyString::operator[]( int idx)
{
        if ( idx > 0 && idx < (int)strlen( this->m_pData ) )
        return this->m_pData[idx];
        return 0;
}

int CMyString::getLength()
{
        return strlen(this->m_pData);
}

std::ostream& operator<<( std::ostream& os, const CMyString& str )
{
        os<< str.m_pData;
        return os;
}

int main()
{

        CMyString str1("i love ");
        CMyString str2("u baby");
        std::cout << "str1: " << str1 << std::endl;
        std::cout << "str2: " << str2 << std::endl;
        CMyString str3 = str1 + str2;
        std::cout << "str3: " << str3 << std::endl;

        CMyString str4 = str2;
        str1 = str2;
        std::cout << "str4: " << str4 << std::endl;
        std::cout << "str1: " << str1 << std::endl;

        std::cout << "str3 length: " << str3.getLength() << std::endl;
        std::cout << "str3[3]= " << str3[3] << std::endl;

        std::cout << ( str1 == str3 ) << std::endl;
        return 0;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值