2023届C/C++软件开发工程师校招面试常问知识点复盘Part 4

31、memcpy()、strcpy()、strcmp()
31.1 strcpy()
#include <cstring>
char * strcpy ( char * destination, const char * source );

1、函数功能:拷贝C风格的字符串(以' '作为字符串的终止字符);将source位置的字符串拷贝到destination缓存位置;

2、返回值:destination

3、为了保证不会溢出,需要保证 destination 指向的数组大小要大于 source 所指向的字符串(包含 ' ';
  • 注意:该函数如果使用不当,也即dest位置的缓存太少,会发生溢出,也即函数并不安全

strcpy()的实现

#include <assert.h>
//* 经典源码
char *strcpy(char *dest, const char *src)
{
    
    
    assert(dest != NULL && src != NULL);
    char *cp = dest;
    while (*cp++ = *src++)
        ;
    return dest;
}
31.2 strncpy()
#include <cstring>
char * strncpy ( char * destination, const char * source, size_t num );

1、函数功能:拷贝字符串source或其前num个字符到destination;
           如果num大于source的长度,先将source拷贝到dest位置(包括空终止符),然后用空字符填充剩余的dest空间;
    	   如果num小于source的长度,那就只拷贝source的前num个字符,因为再拷贝就要溢出了,此时dest的字符串通常没有null空终止字符,未来读dest时有可能溢出。

2、返回值:destination
               
3、通常num的大小等于dest的大小;用一个sizeof(数组名)
  • 通过添加参数num,保证最多从source中拷贝num个字节数据,只要num不超过dest的长度,该函数就是安全的

strncpy()的实现

#include <assert.h>

// 经典源码
char* strncpy(char* dst, const char* src, unsigned int n){
    
    
    assert(dst != NULL && src != NULL);
    char* cp = dst;
    
    while(n-- && (*dst++ = *src++))
        ;
    while(n--)
        *dst++ = '\0';

    return cp;
}
31.3 strcmp()
#include <cstring>
int strcmp ( const char * str1, const char * str2 );

1、函数功能:
    比较两个C风格的字符串,比较的结束条件是 ①遇到两个字符不同 ②遇到字符串的终止空字符

2、返回值: 
    0表示相同  
    1表示首个不等的字符中,str1的字符的字典序大于str2的 
   -1表示首个不等的字符中,str1的字符的字典序小于str2的

3、注意:
    C风格字符串的大小都是包含空字符的哦
    如:sizeof("hello"); // 结果是6

strcmp()的实现

int strcmp(const char *str1, const char *str2)
{
    
    
    assert(str1 != NULL && str2 != NULL);

    int ret = 0;
    while (!(ret = (unsigned int)*str1 - (unsigned int)*str2) && *str1)
    {
    
    
        str1++;
        str2++;
    };

    if (ret < 0)
        return -1;
    else if (ret > 0)
        return 1;
    else
        return ret;
}
31.4 strncmp()
#include <cstring>
int strncmp ( const char * str1, const char * str2, size_t num );

1、函数功能:
    比较两个C风格的字符串str1和str2,且最多比较num个字符;因此比较结束的条件是:
    ①遇到不同的字符
    ②遇到终止字符
    ③已经比较了num个字符

2、返回值:
    0表示相同  
    1表示首个不等的字符中,str1的字符的字典序大于str2的 
   -1表示首个不等的字符中,str1的字符的字典序小于str2的
    

strncmp()的实现

int strncmp(const char *str1, const char *str2, unsigned int n)
{
    
    
    assert(str1 != NULL && str2 != NULL);
    int ret = 0;
    while (n-- && !(ret = (unsigned int)*str1 - (unsigned int)*str2) && *str1)
    {
    
    
        str1++;
        str2++;
    }

    if (ret > 0)
        return 1;
    else if (ret < 0)
        return -1;
    else
        return 0;
}
31.5 memcpy()
#include <cstring>
void * memcpy ( void * destination, const void * source, size_t num );

1、函数功能:
    从source拷贝num字节的数据放到destination,source与dest位置的对象类型与该函数无关
    
2、注意:
    为了避免溢出,source和dest位置数据长度都应该至少为num

memcpy()的实现

/* Public domain.  */
#include <stddef.h>

void * memcpy (void *dest, const void *src, size_t len)
{
    
    
	char *d = dest;
  	const char *s = src;
  	while (len--)
    	*d++ = *s++;
  	return dest;
}
31.6 memcmp()
#include <cstring>
int memcmp ( const void * ptr1, const void * ptr2, size_t num );

1、函数功能:
    比较两块内存数据(一个字节一个字节的比较),一共比较num个字节的数据
    
2、返回值:
    0表示相等
    1表示首个不同的字节,将其视为 unsigned char, ptr1这边大于ptr2
   -1表示首个不同的字节,将其视为 unsigned char, ptr1这边小于ptr2

3、注意:
    该函数所示的比较和NULL空终止符号没有关系,也不会因为遇到NULL就终止比较
    该函数会坚定地比较完num个字符,然后给出比较结果
    

memcmp()的实现

int memcmp (const void *str1, const void *str2, size_t count)
{
    
    
  	register const unsigned char *s1 = (const unsigned char*)str1;
  	register const unsigned char *s2 = (const unsigned char*)str2;

  	while (count-- > 0)
    {
    
    
      	if (*s1++ != *s2++) return *(s1-1) < *(s2-1) ? -1 : 1;
    }
  	return 0;
}
32、C++内联函数
1、内联函数解决了什么问题?
  • CPU调用函数的过程是保存当前主调函数的执行位置与相关上下文,转而执行被调函数,被调函数执行完再返回到主调函数,这个过程其实就是函数调用

  • 函数调用本身有时间和空间的开销,函数体比较大时,函数调用的开销可以忽略;

  • 但是对于比较短小的函数,函数调用所产生的开销占比就比较大了,因此内联函数应运而生。


内联函数用于给编译器提一个建议:

  • 将该函数展开到函数调用处,由此对于较为短小的函数,就不存在函数调用的过程,这个开销也就忽略了

  • 当内联函数较小时,可以使得目标代码更加高效,对于存取函数以及函数体较短、性能关键的函数,推荐内联

  • 内联函数较大或者滥用内联时,目标代码可能会变得很臃肿,也就得不偿失

  • 关于类中函数的内联

    • 如果一个成员函数的实现写在类定义的内部,那么默认内联
2、注意
  1. 通常要求内联的函数在几行之内,非常短小
  2. C++标准规定:内联的函数不能有循环和switch语句,因为循环或switch语句含有动态控制流,这在编译时编译器无法直接确定展开内容,强行展开可能会产生代码膨胀和效率下降,因此得不偿失。
  3. 内联函数不能有递归,因为编译器看到有递归,通常会忽略内联定义
  4. 析构的内联要谨慎,因为析构函数中有可能会调用基类的析构,也就是说析构函数可能并没有看起来那么简短
3、语法
  1. 内联函数的定义必须出现在第一次被调用之前

  2. 内联函数的语法

    inline 返回类型 函数名(参数列表)
    {
          
          
        // 函数体
    }
    
    
  3. 内联inline关键字应该出现在函数定义位置,并且内联函数的定义一般放置在头文件中,因为编译器需要在调用处直接展开函数的定义。

33、给一个结构体计算大小

结构体大小结果为成员中最大元素字节大小的整数倍

 struct myStruct {
     
     
  char b;
  int a;
  short c;
  double d;
 };
|char|    |    |    |int                 | // 8bytes
|short    |    |    |                    | // 8bytes
|double                                  | // 8bytes
// sizeof(myStruct) = 24
34、static的作用

两个方面:类外和类内

一、首先说类外部:

static主要用于隐藏(本文件的)函数或全局变量,怎么理解呢?加了static的函数或全局变量将仅仅在本文件内可见可访问(仅本文件内可见)

②加了static的变量会被存放在静态存储区,因此只能被初始化一次,程序开始运行时就会被初始化,初始化为0(静态存储区

二、然后在类的内部:

①类内修饰类成员时:

  • static修饰的变量属于类变量—静态类变量,可以通过类名::变量名直接访问,不需要创建类对象

  • static修饰的方法属于类方法—静态类方法,可以通过类名::方法名直接访问,不需要创建类对象

  • 类名可以直接访问静态成员,但是不可以直接访问非静态成员

    • 因为非静态成员只有在类实例化之后才真正存在
    • 静态成员则是属于整个类,即使类对象不存在,静态成员也是可以访问的
  • 类对象既可以访问静态成员函数也可以访问非静态成员函数

  • 静态成员函数不可以访问非静态成员

    • 因为静态成员函数属于类,不需要创建类对象,而没有类对象自然无法访问非静态成员
  • 非静态成员函数可以访问静态成员

    • 因为静态成员一直都是存在的
  • 类内的静态成员变量必须先初始化再使用

  • 非静态成员函数有 this 指针,而静态成员函数没有 this 指针

  • 因为static成员函数没有this指针,且虚函数就是借助this指针调用实现的,因此static不能为virtual虚函数

    this(函数的隐含参数)->虚函数表指针vptr(存放于对象空间)->虚函数表(存放于常量区)->虚函数(存放于代码段)

    • 虚函数必须由对象实例调用,这样才可以实现运行时多态,机制如下所示:
    1、通过父类指针指向子类对象,调用某个虚函数;
    
    2、虚函数中的this指针会找到实例对象内存开始位置的虚函数表指针vptr;
    
    3、虚函表中存放有虚函数地址,包括子类重写的虚函数地址;
    
    4、根据这些地址去执行属于子类的虚函数,进而实现了多态。
    
  • static函数属于类,不属于对象,而this指向的是对象实例,故static没有this指针,这让上述的调用虚函数的过程根本无法执行,因此虚函数肯定不能是static。简言之:Static函数属于类,没有this指针;而虚函数的调用执行起始于this指针,因此虚函数不可以是Static函数。

②静态成员是属于整个类的而不是某个对象静态成员变量只存储一份供所有对象共用。所以在所有对象中都可以共享它。

35、内存分区

C++的内存分区从高到低可以分为:栈区、堆区、全局静态区、常量区、代码区

栈区:是操作系统维护的,向下生长,主要存放一些函数调用时的参数、函数返回值、函数内部的局部变量等,效率很高但是容量较小

堆区:是由程序员申请和释放的,向上生长,如果忘记释放已申请的堆内存空间,就会导致内存泄漏

全局静态区:主要用于存放全局变量静态变量;C语言中会分为初始化的和未初始化的;C++中全部都会被初始化,如果没有被初始化,就会被默认初始化

常量区:主要存放一些字符常量、数值常量、字符串常量、虚函数表等

代码区:存放二进制代码、包括函数、虚函数、静态函数

猜你喜欢

转载自blog.csdn.net/qq_40459977/article/details/127518749