C++易错知识点记录

  1. 定义在类中的友元函数的作用域在全局作用域之下
class Point
{
 friend double Distance( const Point & p1, const Point & p2 )    /* ① */
 {
 double dx  = p1.x_ - p2.x_;
 double dy  = p1.y_ - p2.y_;
 return(sqrt( dx * dx + dy * dy ) );
 }
 
 
public:
 Point( int x, int y ) : x_( x ), y_( y )                        /* ② */
 {
 }
 
 
private:
 int    x_;
 int    y_;
};
  1. STL容器:https://blog.csdn.net/shs1992shs/article/details/83113087

    char a[] = "Hello";
    char b[] = {'H', 'e', 'l', 'l', 'o'};
    cout << sizeof(a) << endl; --> 6
    cout << sizeof(b) << endl; --> 5
  1. const位于 * 左侧,指针指向的值不变 eg: int const *p = &x;,const位于 * 右侧,指针指向不可改变,eg: int *const p = &x。
  2. int类型在32和64位系统中都是32位即4个字节,指针在32位系统中占32位即4字节,在64位系统占64位即8字节。(是根据地址线的位数决定)
  3. 类中有虚函数,则占有一个指针大小的字节数。
#include <iostream>
using namespace std;
class animal
{
protected:
 int age;
public:
 virtual void print_age(void) = 0;
};
class dog : public animal
{
public:
 dog(){ this->age = 2; }
 ~dog(){}
 virtual void print_age(void)
 { cout << "wang. my age=" << this->age << endl; }
};
class cat:public animal
{
public :
 cat(){ this->age = 1; }
 ~cat(){}
 virtual void print_age(void){ cout << " Miao,my age= " << this->age << endl; }
};
int main(void)
{
 cat kitty;
 dog jd;
 animal *pa;
 int *p = (int *)(&kitty);
 int *q = (int *)(&jd);
 cout << q[1] << endl;
 cout << q[1] << endl;
 p[1] = q[1];
 pa = &kitty;
 pa->print_age();
 return 0;
}

上述代码 p[0]和q[0]都是各自虚函数表的指针,32位指针占4字节 64位指针占8字节,而不论32位还是64位 int都是4字节。
因此32位时 p[1]和q[1]指向的都是各自的age p[1]被重新赋值,输出是Miao,my age= 2。
64位时 指针占8字节 相当于两个int大小 p[1]和q[1]指向的不是age p[2]和q[2]才是age,输出是Miao,my age= 1 。

    const char str1[]="abc";
    const char str2[]="abc";
    const char *p1 = "abc";
    const char *p2 = "abc";
    cout << str1 << endl << str2 << endl << p1 << endl << p2 << endl;
    cout << &str1 << endl << &str2 << endl << &p1 << endl << &p2 << endl;

输出:
abc
abc
abc
abc
0x7ffe41793850
0x7ffe41793860
0x7ffe41793840
0x7ffe41793848

  1. 指针在free或者delete之后,不会置为nullptr,只是把指针指向的内存释放掉,并没有把指针本身即存储在硬件上的值清除,需要手动置为nullptr。

  2. 对象的隐式转化

  3. struct中长度为0的数组用途与原理

  4. 在这里插入图片描述

  5. 在这里插入图片描述

  6. 1)一般进程
    正常情况下:子进程由父进程创建,子进程再创建新的进程。父子进程是一个异步过程,父进程永远无法预测子进程的结束,所以,当子进程结束后,它的父进程会调用wait()或waitpid()取得子进程的终止状态,回收掉子进程的资源。
    2)孤儿进程
    孤儿进程:父进程结束了,而它的一个或多个子进程还在运行,那么这些子进程就成为孤儿进程(father died)。子进程的资源由init进程(进程号PID = 1)回收。
    3)僵尸进程
    僵尸进程:子进程退出了,但是父进程没有用wait或waitpid去获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中,这种进程称为僵死进程。
    问题危害:
    孤儿进程是没有父进程的进程,它由init进程循环的wait()回收资源,init进程充当父进程。因此孤儿进程并没有什么危害。
    补充:任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程的数据结构,等待父进程去处理。如果父进程在子进程exit()之后,没有及时处理,出现僵尸进程,并可以用ps命令去查看,它的状态是“Z”。
    僵尸进程:当子进程退出时,如果父进程不调用wait或waitpid的话,那么保留的信息就不会被释放,其进程号就会被一直占用,但是系统所能使用的进程号是有限的,如果大量产生僵尸进程,系统将因没有可用的进程号而导致无法产生新的进程!
    解决僵尸进程方法:
    a. kill杀死元凶父进程.
    b. 父进程用wait或waitpid去回收资源(方案不好)
    父进程通过wait或waitpid等函数去等待子进程结束,但是不好,会导致父进程一直等待被挂起,相当于一个进程在干活,没有起到多进程的作用。
    c. 通过信号机制,在处理函数中调用wait,回收资源
    通过信号机制,子进程退出时向父进程发送SIGCHLD信号,父进程调用signal(SIGCHLD,sig_child)去处理SIGCHLD信号,在信号处理函数sig_child()中调用wait进行处理僵尸进程。什么时候得到子进程信号,什么时候进行信号处理,父进程可以继续干其他活,不用去阻塞等待

  7. 有 500 桶酒,其中一桶有毒,而一旦吃了,毒性会在一周内发作,现在我们用小老鼠做实验,要在 1 周后找出那桶毒酒,最少需要多少老鼠。

答案:9只。
解析:(二进制思想)将500桶毒酒进行编号,并用二进制表示。得到000000000~111110100。
每只老鼠编一个号(从1开始),对应二进制的每一位(共9位),然后喝下所有其对应位为1的那个编号的毒酒 ,然后根据死亡的老鼠的编号得出毒酒的二进制编码,进而获得毒酒的编号。如毒酒编号为350,那么二进制对应为101011110,老鼠2、3、4、5、7、9会死亡。

  1. 快慢指针判断有环并返回环的入口节点:
    在这里插入图片描述
//Definition for singly-linked list.
struct ListNode {
     int val;
     ListNode *next;
     ListNode(int x) : val(x), next(NULL) {}
};
 
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
    ListNode * slow=head;
        ListNode * fast=head;
         
        while(fast!=nullptr&&fast->next!=nullptr)
        {
            fast=fast->next->next;
            slow=slow->next;
             
            if(fast==slow)
            {   //利用快慢指针找相遇点
                ListNode* slow2=head;    //设置以相同速度的新指针从起始位置出发
                while(slow2!=slow)
                {      //未相遇循环。
                    slow=slow->next;
                    slow2=slow2->next;
                }
                return slow;
            }
        }
        return nullptr;
     }
};
  1. 在这里插入图片描述
  2. 位运算实现加法和减法
void add(int num1, int num2)
{
    int x = num1 ^ num2;
    int y = num1 & num2;
    while( y != 0 )
    {
        y <<= 1;
        int temp = x;
        x = x ^ y;
        y = temp & y;
    }
    cout << x <<endl;
}
void jian(int num1,int num2)
{
    int x = num1 ^ num2;
    int y = x & num2;
    while(y != 0)
    {
        y <<= 1;
        x ^= y;
        y &= x;
    }
    cout << x << endl;
}

  1. shell中awk的使用
  2. C++的4种cast转化
    1. const_cast只针对指针、引用和this指针,将只读变为可读写
    2. static_cast第一个作用是代替隐式转换; static_cast第二个作用是做基类与派生类的转换,但 static_cast可以将子类转换成父类,但是不提供安全性检查。
    3. dynamic_cast 用于有虚函数的基类与其派生类之间的转换,特点是进行运行时检测转换类型是否安全,如果转换失败返回nullptr,依赖于RTTI技术,但是有额外的函数开销,所以非必要的时候不使用。
    4. reinterpret_cast 代替显示转换,用于转换各种高风险的转换(隐式转换无法转换的)。但是不进行检查,只是进行强制的复制,有安全隐患,一般不用。
  3. C++中拷贝赋值函数的形参能否进行值传递?
    不能。如果是这种情况下,调用拷贝构造函数的时候,首先要将实参传递给形参,这个传递的时候又要调用拷贝构造函数。。如此循环,无法完成拷贝,栈也会满。
  4. vector扩大容量的本质:
    1. 重新申请更大的内存空间;
    2. 将旧内存空间中的数据,按原有顺序移动到新的内存空间中;
    3. 最后将旧的内存空间释放.
  5. 迭代器和指针的区别:
    1. 迭代器不是指针,是类模板,表现的像指针。他只是模拟了指针的一些功能,通过重载了指针的一些操作符,->、*、++、–等;
    2. 迭代器封装了指针,是一个“可遍历STL( Standard Template Library)容器内全部或部分元素”的对象, 本质是封装了原生指针,是指针概念的一种提升(lift),提供了比指针更高级的行为,相当于一种智能指针,他可以根据不同类型的数据结构来实现不同的++,–等操作;
    3. 迭代器返回的是对象引用而不是对象的值,所以cout只能输出迭代器使用*取值后的值而不能直接输出其自身。
  6. 迭代器产生原因:
    Iterator类的访问方式就是把不同集合类的访问逻辑抽象出来,使得不用暴露集合内部的结构而达到循环遍历集合的效果。
  7. C++引用、移动、转发.
    在这里插入图片描述
    25. 自定义的错误输出:
#ifndef UTIL
#define UTIL

#include <iostream>
/**
 * @brief PrintErrorinfo  output the error Info with Filename Funcname and Line
 * @param errorinfo
 * @param filename
 * @param funcname
 * @param line
 */
void PrintErrorinfo(const char* errorinfo, const char* filename, const char* funcname,
                    const uint line )
{
    std::cerr<<"Error: "<<errorinfo<<", at file:"<<filename
                <<", function: "<<funcname <<", line: "<<line<<std::endl;
}
std::string _ErrorStr_;
#define ERROR(ERR)\
    _ErrorStr_ = ERR;\
    PrintErrorinfo(_ErrorStr_.c_str(),__FILE__,__FUNCTION__,__LINE__);\


#endif // UTIL
.....................................................................
#include "util.h"
int main()
{
    ERROR("input the error information!");
    return 0;
}
  1. 用int类型初始化bitset<32>
/**
 * @brief IntchangeBin   Get binary with string of T type
 * @param x
 * @return
 */
template<typename T>
const std::string IntchangeBin(T& x)
{
    std::string str = "";
    for(int i = sizeof(T)*8-1; i>=0;--i)
        str.append(std::to_string((x>>i)&1));
    return str;
}
int main()
{
    int a = 10;
    bitset<32> bits(IntchangeBin<int>(a));
    
    return 0;
}

  1. static 关键字
  2. 单例模式代码:
class SingleTonD
{
public:
    SingleTonD* getInstence()
    {
        static SingleTonD instence;
        return &instence;
    }

private:
    SingleTonD();
    SingleTonD(const SingleTonD&);
    SingleTonD& operator = (const SingleTonD&);
};

  1. C++设计模式总结文章
  2. C++中const修饰引用
  3. 静态成员不能在类内初始化
  4. float精度和取值范围解密
  5. C++中,使用基类引用指针调用一个虚函数时将发生动态绑定
  6. C++中,在类名后加final关键字,表示该类不能作为基类。
  7. C++中,只有虚函数才能被覆盖(重写)。
  8. C++中,在函数末尾处加final关键字之后,其派生类不能再覆盖(重写)该函数。
  9. C++中,友元关系不能传递和继承。基类中的友元类在访问派生类成员时不具有特殊性。每个类负责控制各自成员的访问权限。
  10. C++中,通过在类的内部使用using声明语句,可以将该类的直接或间接基类中的任何可访问成员标记出来。
  11. C++中,在派生类中,使用using声明语句指定基类中的函数名字,而不指定形参列表,就可以把基类中该函数的所有重载实例添加到派生类之中。特例:当using所指定的函数是基类的构造函数名称时,则不论using放在哪个权限修饰符下,其访问权限都与基类的构造函数权限一致。
  12. noexcept 需要跟在const及引用说明符之后,而在final、override虚函数的 =0 之前
  13. 如果一个虚函数承诺不会抛出异常,后续的派生类中虚函数也必须做出同样的承诺。
  14. 设置cpu亲和力(通俗地讲就是让线程在绑定的cpu上长时间的运行而不被迁移到其他处理器的倾向性。)
#include<stdlib.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/sysinfo.h>
#include<unistd.h>

#define __USE_GNU
#include<sched.h>
#include<ctype.h>
#include<string.h>
#include<pthread.h>
#define THREAD_MAX_NUM 100  //1个CPU内的最多进程数

int num=0;  //cpu中核数
void* threadFun(void* arg)  //arg  传递线程标号(自己定义)
{
    cpu_set_t mask;  //CPU核的集合
    cpu_set_t get;   //获取在集合中的CPU
    int *a = (int *)arg;
    printf("the a is:%d\n",*a);  //显示是第几个线程
    CPU_ZERO(&mask);    //置空 对于每个线程来说必须
    CPU_SET(*a,&mask);   //设置亲和力值  把当前线程绑定到 id = *a 的处理器上 
    if (sched_setaffinity(0, sizeof(mask), &mask) == -1)//设置线程CPU亲和力
    {
        printf("warning: could not set CPU affinity, continuing...\n");
    }
    if (1)
    {
        CPU_ZERO(&get);
        if (sched_getaffinity(0, sizeof(get), &get) == -1)//获取线程CPU亲和力
        {
            printf("warning: cound not get thread affinity, continuing...\n");
        }
        int i;
        for (i = 0; i < num; i++)
        {
            if (CPU_ISSET(i, &get))//判断线程与哪个CPU有亲和力
            {
                printf("this thread %d is running processor : %d\n", *a,i);
            }
        }
    }

    return NULL;
}

int main(int argc, char* argv[])
{
    num = sysconf(_SC_NPROCESSORS_CONF);  //获取核数   num = 4
    pthread_t thread[THREAD_MAX_NUM];
    printf("system has %i processor(s). \n", num);
    int tid[THREAD_MAX_NUM];
    int i;
    for(i=0;i<num;i++)
    {
        tid[i] = i;  //每个线程必须有个tid[i]
        pthread_create(&thread[i],NULL,threadFun,(void*)&tid[i]);
    }
    for(i=0; i< num; i++)
    {
        pthread_join(thread[i],NULL);//等待所有的线程结束,线程为死循环所以CTRL+C结束

    }
    printf("END!\n");
    return 0;
}

运行结果:
system has 4 processor(s).
the a is:0
the a is:1
this thread 1 is running processor : 1
this thread 0 is running processor : 0
the a is:2
this thread 2 is running processor : 2
the a is:3
this thread 3 is running processor : 3
END!

  1. 未命名的命名空间:指关键字namespace后紧跟花括号括起来的一系列声明语句。未命名的命名空间中定义的变量拥有静态的生命周期,他们在第一次使用前创建,并且直到程序结束才销毁。取代了用static的静态声明
  2. 运行时类型识别:有两个运算符实现: typeid(返回表达式的类型);dynamic_cast(将基类的指针或引用安全的转成派生类的指针或引用。)
  3. enum money : unsigned long long {…}; 可在枚举名称的后面加冒号指定枚举类型的大小。默认情况下,对于限定作用域的enum成员类型是int,对于不限定作用域的来说不存在默认类型,只知道成员的潜在类型足够大,最好指定成员类型。
  4. 要初始化一个enum对象,必须使用该enum类型的另一个对象,或者它的一个枚举成员。
  5. ( .* )和( ->* )可以解引用指针并获取该对象的成员。
  6. 数据成员指针和成员函数指针,想要通过成员指针调用函数,必须使用(->*)或(.*)运算符。另一种,可使用标准库模版function从指向成员函数的指针获取可调用对象,类似的还有bind、mem_fn:
#include <iostream>
using namespace std;

class Screen
{
public:
    using Action = Screen& (Screen::*)();
    Screen& up()    { str = "up"; cout << "Up" <<endl; return *this; }
    Screen& down()  { str = "down"; cout << "down" <<endl; return *this; }
    Screen& left()  { str = "left"; cout << "left" <<endl; return *this; }
    Screen& right() { str = "right"; cout << "right" <<endl; return *this; }
	Screen& Change()
    {
        this->str = nullptr;
        return *this;
    }
    enum Directions{UP, DOWN, LEFT, RIGHT};

    Screen& move(Directions dir)
    {
        return (this->*menu[dir])();
    }

    const string Screen::* GetStr()
    {
        return &Screen::str;
    }

private:
    static Action menu[];
    string str;
};
Screen::Action Screen::menu[] = {   &Screen::up, &Screen::down,
                                    &Screen::left, &Screen::right
                                };

int main(int argc, char* argv[])
{
    if(1 == argc)
    {
        Screen myscreen;
        myscreen.move(Screen::UP);
        
		auto func = mem_fn(&Screen::Change);
        func(myscreen);
        
        auto func0 = bind(&Screen::Change, std::placeholders::_1);
        func0(myscreen);
        
        function <Screen& (Screen&)> func1 = &Screen::Change;
        func1(myscreen);
        
        const string Screen::* pdata;
        pdata = myscreen.GetStr();
        cout << myscreen.*pdata <<endl;
    }else
        cout << "The programe need configue command" << endl;
    return 0;
}
  1. bind、function、mem_fn:
#include <iostream>     // std::cout
#include <functional>   // std::bind

// a function: (also works with function object: std::divides<double> my_divide;)
double my_divide (double x, double y) {return x/y;}

struct MyPair {
  double a,b;
  double multiply() {return a*b;}
};

int main () {
  using namespace std::placeholders;    // adds visibility of _1, _2, _3,...

  // binding functions:
  auto fn_five = std::bind (my_divide,10,2);               // returns 10/2
  std::cout << fn_five() << '\n';                          // 5

  auto fn_half = std::bind (my_divide,_1,2);               // returns x/2
  std::cout << fn_half(10) << '\n';                        // 5

  auto fn_invert = std::bind (my_divide,_2,_1);            // returns y/x
  std::cout << fn_invert(10,2) << '\n';                    // 0.2

  auto fn_rounding = std::bind<int> (my_divide,_1,_2);     // returns int(x/y)
  std::cout << fn_rounding(10,3) << '\n';                  // 3

  MyPair ten_two {10,2};

  // binding members:
  auto bound_member_fn = std::bind (&MyPair::multiply,_1); // returns x.multiply()
  std::cout << bound_member_fn(ten_two) << '\n';           // 20

  auto bound_member_data = std::bind (&MyPair::a,ten_two); // returns ten_two.a
  std::cout << bound_member_data() << '\n';                // 10

  return 0;
}
// mem_fn example
#include <iostream>     // std::cout
#include <functional>   // std::mem_fn

struct int_holder {
  int value;
  int triple() {return value*3;}
};

int main () {
  int_holder five {5};

  // call member directly:
  std::cout << five.triple() << '\n';

  // same as above using a mem_fn:
  auto triple = std::mem_fn (&int_holder::triple);
  std::cout << triple(five) << '\n';

  return 0;
}
// function example
#include <iostream>     // std::cout
#include <functional>   // std::function, std::negate

// a function:
int half(int x) {return x/2;}

// a function object class:
struct third_t {
  int operator()(int x) {return x/3;}
};

// a class with data members:
struct MyValue {
  int value;
  int fifth() {return value/5;}
};

int main () {
  std::function<int(int)> fn1 = half;                    // function
  std::function<int(int)> fn2 = &half;                   // function pointer
  std::function<int(int)> fn3 = third_t();               // function object
  std::function<int(int)> fn4 = [](int x){return x/4;};  // lambda expression
  std::function<int(int)> fn5 = std::negate<int>();      // standard function object

  std::cout << "fn1(60): " << fn1(60) << '\n';
  std::cout << "fn2(60): " << fn2(60) << '\n';
  std::cout << "fn3(60): " << fn3(60) << '\n';
  std::cout << "fn4(60): " << fn4(60) << '\n';
  std::cout << "fn5(60): " << fn5(60) << '\n';

  // stuff with members:
  std::function<int(MyValue&)> value = &MyValue::value;  // pointer to data member
  std::function<int(MyValue&)> fifth = &MyValue::fifth;  // pointer to member function

  MyValue sixty {60};

  std::cout << "value(sixty): " << value(sixty) << '\n';
  std::cout << "fifth(sixty): " << fifth(sixty) << '\n';

  return 0;
}
  1. union联合体中不能有引用类型成员,成员的访问权限默认都是public。union不能继承其他类,也不能作为基类,所以union不能有虚函数,可以有构造函数和析构函数等成员函数。当为union的一个数据成员赋值时,其他数据成员会变成未定义状态
  2. 对于匿名的union其成员不可用protectprivate修饰,可在类外直接访问,也不能定义成员函数
  3. volatile:直接处理硬件的程序常包含这样的数据元素,他们的值由程序直接控制之外过程控制,并告诉编译器不应对这样的对象优化。对于类来说,只有volatile的成员函数才能被volatile的对象调用。
  4. 尾置返回允许我们在参数列表之后声明返回类型:
template <typename It>
auto func(It begin, It end) -> 
			typename remove_reference<decltype(*begin)>::type
{
	... ...
	return *begin;
}
  1. 引用折叠只能应用于间接创建的引用的引用,如类型名或函数模版。
    a. X& &、X& &&、X&& &都折叠成类型X&;
    b. 只有类型X&& &&才折叠成X&&.
    显然,我们可以将任意类型的实参传递给T&&类型的函数参数。
  2. 完美转发:
template <typename F, typename T1, typename T2>
void flip(F f, T1&& t1, T2&& t2)
{
	f(std::forward<T2>(t2), std::forward<T1>(t1));
}
void g(int&& i, int& j)
{
	cout << i <<" "<< j <<endl;
}
flip(g, i, 42); 	//i将以int& 传递给g, 42将以int&&类型传给g。
  1. C++锁的管理-- std::lock_guard和std::unique_lock
  2. 遍历数组:
#include <iostream>     // std::cout
#include <vector>       // std::vector, std::begin, std::end
using namespace std;
int main() {
    //定义一个普通数组
    int arr[] = { 1,2,3,4,5 };
    //创建一个空 vector 容器
    vector<int> myvector;
    //将数组中的元素添加到 myvector 容器中存储
    for (int *it = begin(arr); it != end(arr); ++it)
        myvector.push_back(*it);
    //输出 myvector 容器中存储的元素
    for (auto it = myvector.begin(); it != myvector.end(); ++it)
        cout << *it << ' ';
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值