目录
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、注意
- 通常要求内联的函数在几行之内,非常短小
- C++标准规定:内联的函数不能有循环和switch语句,因为循环或switch语句含有动态控制流,这在编译时编译器无法直接确定展开内容,强行展开可能会产生代码膨胀和效率下降,因此得不偿失。
- 内联函数不能有递归,因为编译器看到有递归,通常会忽略内联定义
- 析构的内联要谨慎,因为析构函数中有可能会调用基类的析构,也就是说析构函数可能并没有看起来那么简短
3、语法
-
内联函数的定义必须出现在第一次被调用之前
-
内联函数的语法
inline 返回类型 函数名(参数列表) { // 函数体 }
-
内联
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++中全部都会被初始化,如果没有被初始化,就会被默认初始化
常量区:主要存放一些字符常量、数值常量、字符串常量、虚函数表等
代码区:存放二进制代码、包括函数、虚函数、静态函数等