C/C++遇到的笔试题

收集了在面试中遇到的C/C++笔试题,作为笔记复习

一、计算 sizeof 的值

void Func(char str[100]){}
char str[]=”Hello”;
char *p=str;
int n=10;
void *p1=malloc(100);

请计算:

sizeof(str)= 4
sizeof(str)= 6
sizeof( p )=4
sizeof(n)=4
sizeof(p1)=4

二、计算下面几个类的大小

class A {};: sizeof(A) = 1;
class A { virtual Fun(){} };: sizeof(A) = 4(32位机器)/8(64位机器);
class A { static int a; };: sizeof(A) = 1;
class A { int a; };: sizeof(A) = 4;
class A { static int a; int b; };: sizeof(A) = 4;
int B[10];
sizeof(B) = 40; sizeof(B[10]) = 4;

三、【剑指offer】字符串转换为整数数字

1. 功能测试

  • 正数/复数/0

2. 边界值测试

  • 最大的正整数/最小的负整数(数据上下溢出)

3. 特殊输入测试

  • 空字符串“”的处理,返回0,设置非法输入

  • 字符串只有符号位的处理,返回0,设置非法输入

  • 输入的字符串中有非数字字符,返回0,设置非法输入

4. 代码:

#include <iostream>
#include <string>
using namespace std;
// 67:把字符串转换成整数
class Solution {
public:
    /*全局变量*/
    enum {kValid,kInvalid}; // 枚举元素(kValid=0,kInvalid=1)
    int g_nStatus = kValid; // 标记是否是非法输入

    /*功能函数*/
    int StrToInt(string str)
    {
        g_nStatus = kInvalid;          // 初始标记为非法输入
        long long num = 0;             // 存储结果
        const char* cstr = str.c_str();// 指向字符数组的指针

        // 判断是否是空指针和空字符串""
        if( (cstr != NULL) && (*cstr != '\0') ) {
            // 处理符号位
            int minus = 1;
            if(*cstr == '-') {
                minus = -1;
                cstr++;
            } else if(*cstr == '+') {
                minus = 1;
                cstr++;
            }

            // 处理其余位
            while(*cstr != '\0') {
                if(*cstr > '0' && *cstr < '9') {
                    // string转int类型
                    g_nStatus = kValid;           // 标记为合法输入
                    num = num*10 + (*cstr -'0');  // string转换为int类型
                    cstr++;

                    // 数据上下溢出
                    if( ((minus>0) && (num > 0x7FFFFFFF)) ||((minus<0) && (num > 0x80000000)) ) {
                        g_nStatus = kInvalid; // 如果溢出,则标记为非法输入
                        num = 0;
                        break;
                    }
                
                } else {
                    g_nStatus = kInvalid;
                    num = 0;
                    break;
                }
            }

            if(g_nStatus == kValid)
                num = num * minus;

        }
        cout<<(int)num<<endl;
        return (int)num;
    }
};
int main()
{
    string str = "123";
    Solution solution;
    solution.StrToInt(str);
    return 0;
}

四、C++中String类的实现

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

请编写String的上述函数:

String::String() {
	m_data = new char[1];
	*m_data = '\0';
}
  
String::~String()
{
	if(m_data) {
		delete []m_data;
		m_data = NULL;
    }
}
  
//普通构造函数  
String::String(const char *str)
{
	if (str == NULL) {
		m_data = new char[1];// 得分点:对空字符串自动申请存放结束标志'\0'的,加分点:对m_data加NULL判断  
		*m_data = '\0';
	} else {
		int length = strlen(str);
		m_data = new char[length + 1];// 若能加 NULL 判断则更好
		strcpy(m_data, str);
	}
}

//拷贝构造函数  
String::String(const String &other)// 得分点:输入参数为const型  
{		 
	int length = strlen(other.m_data);
	m_data = new char[length + 1];// 若能加 NULL 判断则更好  
	strcpy(m_data, other.m_data);
}

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

	return *this;//得分点:返回本对象的引用    
}
  • 考察重点一,拷贝构造函数的参数
    如果对拷贝构造函数理解不到位,将函数做如下声明:String( String other)
    那么,考虑如下情况:myString str1("aaa");myString str2 = str1;
    由于是传值参数,形参复制到实参会调用拷贝构造函数,就会在拷贝构造函数内部调用拷贝构造函数,会形成永无休止的递归调用,导致栈溢出。
  • 考察重点二,赋值运算函数
    (1) 是否把返回值的类型声明为该类型的引用
    (2) 是否在函数结束前返回自身的引用
    (3) 传入参数是否声明为常量引
    (4) 是否释放实例自身的内存
    (5) 是否判断输入参数是否和当前实例是同一个实例
  • 考察重点三,考虑异常及安全
    如果在赋值函数内部使用delete释放实例自身内存,再用new申请新的内存,如果此时内存不足导致new操作失败,抛出异常导致类实例状态无效。可以采用临时对象和自身实例交换, 通过局部零时对象离开作用域时调用析构函数,进而释放内存:
String & operator =(const myString & other)
{
	if( this != &other ) {  // 检查自赋值         
		myString strtemp(other);
        char * pTemp = strtemp.m_data;
        strtemp.m_data = m_data;
        m_data = pTemp;
    }            
        
    return *this;	// 返回本对象的引用
}

总结:
能够准确无误地编写出String类的构造函数、拷贝构造函数、赋值函数和析构函数的面试者至少已经具备了C++基本功的60%以上!
在这个类中包括了指针类成员变量m_data,当类中包括指针类成员变量时,一定要重载其拷贝构造函数、(拷贝)赋值函数和析构函数,这既是对C++程序员的基本要求,也是《Effective C++》中特别强调的条款。
仔细学习这个类,特别注意加注释的得分点和加分点的意义,这样就具备了60%以上的C++基本功!

五、strcpy函数实现

strcpy函数的原型为:char* strcpy(char* _Dest, const char* _Source);

#include <stdio.h>
char* strcpy(char *strDest, const char* strSrc)
{
    /*
    //检查传入参数的有效性
    assert(NULL != _Dest);
    assert(NULL != _Source);
    */
    
    char *p=NULL;
    if(strDest == NULL || strSrc == NULL) { return NULL; }
    
    p = strDest;
    
    while((*strDest++ = *strSrc ++) != '\0');
    return p;
}

int getStrLen(const char* str)
{
    int len = 0;
    while( *str ++ != '\0') { len ++; }
    return len;
}

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

六、memcpy函数实现

memcpy函数的作用

将由src指向地址为起始地址的连续n个字节的数据复制到以dest指向地址为起始地址的空间内,函数返回一个指向dest的指针。

特别说明

  1. strcpy相比,memcpy遇到'\0'不会结束,而是一定要拷贝完n个字节,所以要指定拷贝的数据长度
  2. memcpy可以拷贝任何数据类型的对象,如果destsrc的指针类型不一样,也需要处理,不能直接++使地址自增(例如:int* p
    char*qp++指针的值是4个4个加(0,4,8),q++是1个1个加(0,1,2,3,4))
  3. 如果dest本身就有数据,执行memcpy()之后会覆盖原有的数据,所以srcdest所指向的内存区域不能有重叠
  4. 不能改变形参的值,定义新的临时变量来操作
  5. 参数提供的地址可能为空

destsrc所指向的地址有重叠的情况

内存地址重叠的情况分为两种,第一种是dest的地址在src地址的后面,另一种则是dest地址在src地址的前面。
为了处理上面这两种情况,后来又提供了另一个函数memmove,在不需要保留原来内存区域的数据的时候可以使用memmove

代码

void *memcpy(void *dest, const void *src, size_t count)
{
    if (dest == NULL || src == NULL || dest <= src + count) return NULL;

    char *tmp_dest = dest;
    const char *tmp_src = src;
    
    while (count--) *tmp_dest++ = *tmp_src++ ;
    return dest;
}

另外,这段代码还可以优化,比如根据CPU的字节长度,把原来一个一个字节拷贝转换为按CPU的长度拷贝,等等。

优化版:

#include<iostream>
using namespace std;
void* mcpy(void *dest, const void *src, size_t count)
{
	if (dest == nullptr || src == nullptr) { return nullptr; }
		
	char *pd = (char*)dest;
	const char *ps = (char*)src;
 
	//判断是否会发生内存重叠现象
	bool flag1 = (pd < ps && pd + count > ps);
	bool flag2 = (pd > ps && ps + count > pd);
	
	if (flag1 || flag2) { return nullptr; }
 
	while (count--) { *pd++ = *ps++; }
	return dest;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值