C++集锦

@wanqiuz 2018-04-10 02:47:48发表于 wanqiuz/blog-articles C++集锦

1 strcpy函数

声明: char *strcpy(char *dest, const char *src);
引入: #include <string.h>
功能:将src所指由'\0'结束的字符串复制到dest所指的数组中(包括'\0')
注意:src和dest所指内存区域不可重叠,函数返回指向dest的指针,且dest必须有足够空间容纳src的字符串,src字符串尾结束标识符'\0'也会被复制过去。
实现

char *strcpy(char *dest, const char *src) // 保证src指针不被改变,返回char *可以支持链式表达式
{
    assert(dest != nullptr && src != nullptr ); // 指针有效性检查,debug/release环境启用/关闭断言
    char *result = dest; // 保存原始目标指针
    while((*dest++ = *src++) != '\0'); // 注意此处分号;, 也可以转行写,会更清晰易懂
    return result;
}

2 memcpy函数

声明: void *memcpy(void *dest, const void *src, size_t n);
引入: #include <string.h>
功能:从源src所指的内存地址的起始位置开始,拷贝n个字节的数据到目标dest所指的内存区域,memcpy遇到’\0’不结束,而且一定会复制完n个字节。src和des都不一定是数组,任意的可读写的空间均可。
注意:(内存重叠有两种情况,一是dest在src之前,且dest拷贝后的长度覆盖了src的内存,这种可以,二是dest在src和src后n个字节之间,这种内存拷贝数值不正确,是我们要避免的,下面说的内存重叠就是指第二种情况。)src和dest所指内存区域不可重叠,如果重叠,结果未定义。函数返回指向dest的指针,且dest必须有足够空间容纳src的字符串。
实现

void *memcpy(void *dest, const void *src, size_t n) // 保证src指针不被改变,返回void *可以支持链式表达式
{
    assert(pSrc != nullptr && pDest != nullptr); // 指针有效性检查,debug/release环境启用/关闭断言

    const char *pSrc = static_cast<const char*>(src);
    char *pDest = static_cast<char*>(dest);
    for (size_t i = 0; i < n; ++i)
        pDest[i] = pSrc[i];

    return dest;
}

3 memmove函数

声明: void *memmove(void *dest, const void *src, size_t n);
引入: #include <string.h>
功能:从源src所指的内存地址的起始位置开始,拷贝n个字节的数据到目标dest所指的内存区域,memmove遇到’\0’不结束,而且一定会复制完n个字节。src和des都不一定是数组,任意的可读写的空间均可。
注意:src和dest所指内存区域可以重叠。函数返回指向dest的指针,且dest必须有足够空间容纳src的字符串。
实现

void *memmove(void *dest, const void *src, size_t n) // 保证src指针不被改变,返回char *可以支持链式表达式
{
    assert(pSrc != nullptr && pDest != nullptr); // 指针有效性检查,debug/release环境启用/关闭断言

    const char *pSrc = static_cast<const char*>(src);
    char *pDest = static_cast<char*>(dest);
    if (pDest > pSrc && pDest < pSrc + n) // 内存重叠
    {
        for (int i = n - 1; i >= 0; --i)
            pDest[i] = pSrc[i];
    }
    else // 内存不重叠
    {
        for (size_t i = 0; i < n; ++i)
            pDest[i] = pSrc[i];
    }

    return dest;
}

c++ stack接口

函数名 功能 复杂度
size() 返回栈的元素数 O(1)
top() 返回栈顶的元素 O(1)
pop() 删除栈顶元素,但并不返回被删除的元素 O(1)
push(x) 向栈中添加元素x O(1)
empty() 在栈为空时返回true O(1)

4 二叉树非递归遍历

(1) 前序遍历(Preorder Traversal )(亦称先序遍历)
——访问根结点的操作发生在遍历其左右子树之前。根结点——左结点——右结点
(2) 中序遍历(Inorder Traversal)
——访问根结点的操作发生在遍历其左右子树之中(间)。左结点——根结点——右结点
(3) 后序遍历(Postorder Traversal)
——访问根结点的操作发生在遍历其左右子树之后。左结点——右结点——根结点

void preOrder(BinaryTreeNode *pRoot)
{
	if (pRoot == nullptr)
		return;
	
	stack<BinaryTreeNode*> s;
	BinaryTreeNode *p = pRoot;
	while (p != nullptr || !s.empty())
	{
		while (p != nullptr)
		{
			doSomething(p->value);
			s.push(p);
			p = p->left;
		}
		if (!s.empty())
		{
			p = s.top();
			s.pop();
			p = p->right;
		}
	}
}
void preOrderIterative(BinaryTreeNode* pRoot)  
{  
    if(pRoot==NULL)  
       return;  
  
    cout<<pRoot->value;  
    if(pRoot->left!=NULL)  
       preOrder1(pRoot->left);  
    if(pRoot->right!=NULL)  
       preOrder1(pRoot->right);  
}  
void inOrder(BinaryTreeNode *pRoot)
{
	if (pRoot == nullptr)
		return;
	
	stack<BinaryTreeNode*> s;
	BinaryTreeNode *p = pRoot;
	while (p != nullptr || !s.empty())
	{
		while (p != nullptr)
		{
			s.push(p);
			p = p->left;
		}
		if (!s.empty())
		{
			p = s.top();
			doSomething(p->value);
			s.pop();
			p = p->right;
		}
	}
}
void inOrderIterative(BinaryTreeNode* pRoot)  
{  
    if(pRoot==NULL)  
        return;  
      
    if(pRoot->left!=NULL)  
        inOrder1(pRoot->left);  
    cout<<pRoot->value;  
    if(pRoot->right!=NULL)  
        inOrder1(pRoot->right);  
}  
void postOrder(BinaryTreeNode *pRoot)
{
	if (pRoot == nullptr)
		return;
	
    stack<BinaryTreeNode*> s;  
    BinaryTreeNode *cur;  
    BinaryTreeNode *pre=NULL;  
    s.push(pRoot);//根结点入栈  
    while(!s.empty())  
    {  
        cur=s.top();  
        if((cur->left==NULL&&cur->right==NULL)||(pre!=NULL&&(pre==cur->left||pre==cur->right)))  
        {  
            //左孩子和右孩子同时为空,或者当前结点的左孩子或右孩子已经遍历过了  
            cout<<cur->value<<" ";  
            s.pop();  
            pre=cur;  
        }  
        else  
        {  
            if(cur->right!=NULL)  
                s.push(cur->right);  
            if(cur->left!=NULL)  
                s.push(cur->left);  
        }  
    }  
}	
void postOrderIterative(BinaryTreeNode* pRoot)  
{  
    if(pRoot==NULL)  
        return;  
    postOrder1(pRoot->left);  
    postOrder1(pRoot->right);  
    cout<<pRoot->value<<" ";  
}  

5 四种强制类型转换

static_cast

类似于C风格的强制转换,无条件转换。
静态类型转换。用于:
(1) 基类和子类之间转换:其中子类指针转换成父类指针是安全的;但父类指针转换成子类指针是不安全的。(基类和子类之间的动态类型转换建议用dynamic_cast)
(2) 基本数据类型转换。enum, struct, int, char, float等。static_cast不能进行无关类型(如非基类和子类)指针之间的转换。
(3) 把空指针转换成目标类型的空指针
(4) 把任何类型的表达式转换成void类型
(5) static_cast不能去掉类型的const、volitale属性(用const_cast)。

const_cast

该运算符用来修改类型的const或volatile属性。
常量指针被转化成非常量指针,并且仍然指向原来的对象;
常量引用被转换成非常量引用,并且仍然指向原来的对象;
常量对象被转换成非常量对象。

reinterpreter_cast

仅仅重新解释类型,但没有进行二进制的转换:
(1) 转换的类型必须是一个指针、引用、算术类型、函数指针或者成员指针。
(2) 在比特位级别上进行转换。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。但不能将非32bit的实例转成指针。
(3) 最普通的用途就是在函数指针类型之间进行转换。
(4) 很难保证移植性。

dynamic_cast

运行时类型识别(以区别以上三个均在编译时识别),用于将基类的指针或引用安全地转换成派生类的指针或引用。基类必须有虚函数,否则,编译时报错。
对指针进行dynamic_cast,失败返回null,成功返回正常cast后的对象指针;
对引用进行dynamic_cast,失败抛出一个异常bad_cast,成功返回正常cast后的对象引用。

总 结:

(1) 去const属性用const_cast。
(2) 基本类型转换用static_cast。
(3) 多态类之间的类型转换用daynamic_cast。
(4) 不同类型的指针类型转换用reinterpreter_cast。

6 构造函数的初始化列表

对象的构造顺序是先执行初始化列表,再执行构造函数函数体。
必须用初始化列表:1. 成员变量是对象,且其无无参或缺省构造函数。2. 成员变量是const或者引用类型。
优先用初始化列表:1. 效率高,不显式用初始化列表的话,会有隐式初始化,再在构造函数体内显式赋值,操作两次,效率低。
初始化列表的初始化顺序:按照声明的顺序初始化,而不是按照出现在初始化列表中的顺序。

7 C/C++中struct和class的区别

C中struct和class的区别:

struct不能定义成员函数,只能定义变量,没有继承、没有封装。

C++中struct和class的区别:

(1) 默认成员权限区别
struct的成员默认权限是public,而class的成员默认权限是private。
(2) 默认继承方式
struct的默认继承方式为public,而class的默认继承为private。
(3) 用于定义模板参数
模板为C++语言新增特性,C语言没有,只有class可用于定义参数,而struct不可以。
总:建议用法,在字面上struct与class的含义不一样,struct更适合看成是一个数据结构的实现体,class更适合看成是一个对象的实现体。

8 C++中union介绍

union内定义多种不同的数据类型,数据共享同一段内存,以达到节省空间的目的。union变量所占用的内存长度等于最长的成员的内存长度。
Union可以用来测试CPU大小端
在小端模式中,低位字节放在低地址,高位字节放在高地址;
(1) 在c中,联合体(共用体)的数据成员都是从低地址开始存放。
(2) 若是小端模式,由低地址到高地址c.a存放为0x01 00 00 00,c.b被赋值为0x01;
(3) 若是大端模式,由低地址到高地址c.a存放为0x00 00 00 01,c.b被赋值为0x0;
//若处理器是Big_endian的,则返回0;若是Little_endian的,则返回1。

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

9 堆溢出和栈溢出原因分析

(1) 堆溢出: 不断的new 一个对象,一直创建新的对象,
(2) 栈溢出:死循环或者是递归太深 。

10 堆为什么TCP需要三次握手和四次挥手

TCP协议是一种面向连接的、可靠的、基于字节流的传输层通信协议,采用全双工通信。
那为什么需要三次握手呢?请看如下的过程:
(1) A向B发起建立连接请求:A——>B;
(2) B收到A的发送信号,并且向A发送确认信息:B——>A;
(3) A收到B的确认信号,并向B发送确认信号:A——>B。
三次握手大概就是这么个过程。
通过第一次握手,B知道A能够发送数据。通过第二次握手,A知道B能发送数据。结合第一次握手和第二次握手,A知道B能接收数据。结合第三次握手,B知道A能够接收数据。
至此,完成了握手过程,A知道B能收能发,B知道A能收能发,通信连接至此建立。三次连接是保证可靠的最小握手次数,再多次握手也不能提高通信成功的概率,反而浪费资源。
那为什么需要四次挥手呢?请看如下过程:
(1) A向B发起请求,表示A没有数据要发送了:A——>B;
(2) B向A发送信号,确认A的断开请求请求:B——>A;
(3) B向A发送信号,请求断开连接,表示B没有数据要发送了:B——>A;
(4) A向B发送确认信号,同意断开:A——>B。
B收到确认信号,断开连接,而A在一段时间内没收到B的信号,表明B已经断开了,于是A也断开了连接。至此,完成挥手过程。
可能有捧油会问,为什么2、3次挥手不能合在一次挥手中?那是因为此时A虽然不再发送数据了,但是还可以接收数据,B可能还有数据要发送给A,所以两次挥手不能合并为一次。
挥手次数比握手多一次,是因为握手过程,通信只需要处理连接。而挥手过程,通信需要处理数据+连接。

11 进程间通信

管道、信号、信号量、共享内存、消息队列、套接字

12 引用和指针的区别

★相同点:
(1) 都是地址的概念;指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名。
★不同点:
(1) 指针是一个实体,而引用仅是个别名;
(2) 引用只能在定义时被初始化一次,之后不可变;指针可变;
(3) 引用没有const,指针有const,const的指针不可变;
(4) 引用不能为空,指针可以为空;
(5) “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小;
(6) 指针和引用的自增(++)运算意义不一样;
(7) 引用是类型安全的,而指针不是 (引用比指针多了类型检查)。

参考并感谢: