C++汇总

Hi,大家好

已工作一年余月,四年未登博客,最近总结了一下C++,想起大学时老师让我们写的博客,今天突然想登录再重温一下,可是404了。

只能重新申请个账号分享了,这是这个账号分享的第一篇博客,若大家喜欢请双击,有不对的地方请指出,谢谢大家。

About work in 2020
 
主要涉及到编译过程,elf,C语言与C++区别,及C的知识点:
类 、引用、指针、内存、STL、智能指针、typedef、文件操作、函数模板、红黑树、i++ & ++i、extern、assert 、指针数组 & 数组指针
左值与右值、explicit、强制类型转换
 
.C++具体知识
一:编译过程
a. 预处理
 
  • Remove comments from the source code.
  • Macro expansion.
  • Expansion of included header files
       #error命令是C/C++语言的预处理命令之一,当预处理器预处理到#error命令时将 停止编译 并输出用户自定义的错误消息
         #pragma用于指示编译器完成一些特定的动作 
              #pragma message 用于自定义编译信息
             #pragma once 用于保证头文件只被编译一次
             #pragama pack用于指定内存对齐(一般用在结构体)
 
b. 编译
  • Check C program for  syntax errors .
  • Translate the file into intermediate code i.e. in assembly language.
  • Optionally optimize the translated code for better performance.
 
c. 汇编
Assembler accepts the compiled source code ( compilation.s ) and translates to low level machine code
 
d. 链接
It links all the function calls with their original definition
 
 
二:elf相关
 
a.定义
       1. 可重定位的对象文件(Relocatable file)     .o
       2.  可执行的对象文件(Executable file)           exe
       3. 可被共享的对象文件(Shared object file)  .so
 
       ELF is the abbreviation(缩写) for  Executable and Linkable Format  and defines the structure for binaries, libraries, and core files. 
 
       ELF files are just for binaries or executables. We already have seen they can be used for partial pieces (object code). Another example is  shared libraries  or even  core dumps  (those core or a.out files)
  
        ELF files are for execution or for linking. Depending on the primary goal, it contains the required segments or sections. Segments are viewed by the kernel and mapped into memory (using mmap). Sections are viewed by the linker to create executable code or shared objects.
 
b.组成
  • ELF header: 描述整个文件的组织。
  • Program Header Table: 描述文件中的各种segments,用来告诉系统如何创建进程映像的。
  • sections(节) 或者 segments(段):segments是从运行的角度来描述elf文件,sections是从链接的角度来描述elf文件,一个segment包含若干个section。
  • Section Header Table: 包含了文件各个segction的属性信息。
 
 
c.几个命令
readelf  is a Unix binary utility that displays information about one or more ELF files.
 
objdump  provides a wide range of information about ELF files and other object formats. objdump  uses the  Binary File Descriptor library  as a back-end to structure the ELF data.
 
file  is a Unix binary utility that displays information about the  architecture  of ELF files.
 
readelf -S            查看section
readelf -h            查看ELF文件头
objdump -s -d 反汇编查看代码
objdump -h          获得这个目标文件的文件头
ldd                        To determine what external libraries are being used, simply use the ldd on the same binary
 
Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        00000000 000034 000062 00  AX  0   0  1
  [ 2] .rel.text         REL             00000000 0002a8 000028 08   I 11   1  4
  [ 3] .data             PROGBITS        00000000 000098 000008 00  WA  0   0  4
  [ 4] .bss              NOBITS          00000000 0000a0 000004 00  WA  0   0  4
  [ 5] .rodata           PROGBITS        00000000 0000a0 000004 00   A  0   0  1
  [ 6] .comment          PROGBITS        00000000 0000a4 000036 01  MS  0   0  1
  [ 7] .note.GNU-stack   PROGBITS        00000000 0000da 000000 00      0   0  1
  [ 8] .eh_frame         PROGBITS        00000000 0000dc 000064 00   A  0   0  4
  [ 9] .rel.eh_frame     REL             00000000 0002d0 000010 08   I 11   8  4
  [10] .shstrtab         STRTAB          00000000 0002e0 00005f 00      0   0  1
  [11] .symtab           SYMTAB          00000000 000140 000100 10     12  11  4
  [12] .strtab           STRTAB          00000000 000240 000065 00      0   0  1
 
 
d.几个重要的段
text        代码段         保存代码编译后的指令
data       数据段         已经初始化 了的全局静态变量和局部静态变量    Initialized data, with read/write access rights              gvar
bss       快数据段      存储未被明确初始化的全局数据         Uninitialized data, with read/write access rights (=WA)
rodata    只读数据     只读变量(const修饰的变量和字符串常量)     Initialized data, with read access rights only (=A)
symtab   符号表段    以数组结构保存符号信息(函数和变量)
 
栈区   该段内存由编译器自动分配和释放的,主要用于参变量和局部变量等
堆区   用于程序中分配的,并需要自己释放    两个紧挨着定义的指针变量,所指向的malloc出来的两块内存并不一定的是紧挨着的,所以会产生内存碎片
 
0fffffff  高地址
 
0f000 地地址
 
char* p = new char[20];
// 这行代码在Heap中开辟了20个char长度的空间,同时在Stack上压入了p,
// 指针变量p存在于栈上,其值为刚刚在堆上开辟的空间的首地址
 
 
 
三:C语言与C++区别
 
C        面向过程
C++    面向对象    兼面向过程
 
面向过程的思路:分析解决问题所需的步骤,用函数把这些步骤依次实现。
面向对象的思路:把构成问题的事务分解为各个对象,建立对象的目的,不是完成一个步骤,而是描述某个事务在解决整个问题步骤中的行为。
 
C++ 对 C 的增强,表现在六个方面:
  • 增强了类型检查机制
  • 增加了面向对象的机制
  • 增加了泛型编程的机制(template)
  • 增加了异常处理
  • 增加了重载的机制 (函数名相同,参数不同)
  • 增加了标准模板库(STL)
四:C++
主要介绍:
引用
指针
内存
STL
智能指针
typedef
文件操作
函数模板
红黑树
i++ & ++i
extern
assert 
指针数组 & 数组指针
左值与右值
explicit
强制类型转换
 
1.类   直接使用类名创建对象 和 使用new创建对象[指针])
 
主要介绍:
构造函数 析构函数 拷贝构造函数(浅 & 深)
静态 
友元
重载/写/隐藏  虚函数  静态联编 & 动态联编
虚基类
class && struct的大小
数据成员赋值问题
this 指针
public private protect
 
  • main只能访问public
  • 数据成员最好在构造中赋值
  • 类名::方法名
 
a.构造函数  : 必须放在public下面
class Line
{
   public:
      void setLength( double len );
      Line(double len);  // 或 Line(double len):length(len),xxx(x){}   初始化列表
   private:
      double length;
};
Line::Line( double len)
{
    length = len;
}
void Line::setLength( double len )
{
    length = len;
}
// 程序的主函数
int main( )
{
   Line line(10.0);
   return 0;
}

 

b.析构函数
虚析构函数: 为了避免内存泄漏, 而且是当子类中会有指针成员变量时才会使用到
// 基类
    class Base{
        public:
            Base(){};
            virtual ~Base(){  // 基类的析构函数是虚函数!
                cout << "delete Base\n";
            };
            virtual void DoSomething(){
                cout << "Do Something in class Base!\n";
            };
    };


    // 派生类
    class Derived: public Base{
        public:
            Derived(){};
            ~Derived(){
                cout << "delete Derived\n";
            };
            void DoSomething(){
                cout << "Do Something in Derived\n";
            };
    };


    int main(){
        Base *b = new Derived;
        b->DoSomething();  // 重写DoSomething函数
        delete b;
        return 0;
    }

 

(1) 如果基类的析构函数不加virtual关键字,那么就是普通析构函数
delete基类的指针时,只会调用基类的析构函数,不会调用派生类的析构函数。
(2) 如果基类的析构函数加virtual关键字,那么就是虚析构函数
delete基类的指针时,先调用派生类的析构函数,再调用基类中的析构函数。
 
 
c.拷贝构造函数  classname ( const classname & obj ) - 每调用一次 会析构一次 : 必须放在public下面
  • 通过使用另一个同类型的对象来初始化新创建的对象
  • 复制对象把它作为参数传递给函数
  • 复制对象,并从函数返回这个对象
 
   1)浅度拷贝: 对指针拷贝后会出现两个指针指向同一个内存空间,多次性析构出现问题
   2)深度拷贝: 拷贝后的对象指针成员有自己的内存空间
 
拷贝构造:
class Student
{
public:
    Student(string name="" ,int age=0); //普通构造函数
    Student(const Student &stu);  //拷贝构造函数(声明)
    ~Student();
private:
    string m_name;
    int m_age;
};
Student::Student(string name, int age )
{
    cout << "Student()" << endl;
    m_name = name;
    m_age = age;
}
Student::Student(const Student &stu)  //拷贝构造函数(定义)
{
    this->m_name = stu.m_name;
    this->m_age = stu.m_age;
    cout << "Copy constructor was called." << endl;
}
Student::~Student()
{
    cout << "~Student()" << endl;
}
int main()
{
    Student stu1("Student", 13);
    Student stu2 = stu1;  //调用拷贝构造函数  内存大小与stu1相同
    Student stu3(stu1);   //调用拷贝构造函数   内存大小与stu1相同
    return 0;
}
Student()
Copy constructor was called.
Copy constructor was called.
~Student()
~Student()
~Student()
浅拷贝:
class Student
{
private:
    int num;
    char *name;
public:
    Student();
    ~Student();
};
Student::Student()
{
    name = new char(20);
    cout << "Student" << endl;
}
Student::~Student()
{
    cout << "~Student " << (int)name << endl;
    delete name;
    name = NULL;
}
int main()
{
        Student s1;
        Student s2(s1);// 拷贝构造
        return 0;
}
//拷贝中默认是s2->name = s1->name
//若为name = s1->name 是ok的
Student
~Student  3735080
~Student  3735080


深度拷贝:
class Student
{
private:
    int num;
    char *name;
public:
    Student();
    ~Student();
    Student(const Student &s);//拷贝构造函数,const防止对象被改变
};
Student::Student()
{
    name = new char(20);
    cout << "Student" << endl;
}
Student::~Student()
{
    cout << "~Student " << (int)name << endl;
    delete name;
    name = NULL;
}
Student::Student(const Student &s)
{
    name = new char(20);
    memcpy(name, s.name, strlen(s.name));
    cout << "copy Student" << endl;
}
int main()
{
    {// 花括号让s1和s2变成局部对象,方便测试
        Student s1;
        Student s2(s1);// 复制对象
    }
    system("pause");
    return 0;
}
Student
copy Student
~Student 3804256
~Student 3800616

 

d.常
  • 常成员变量      : 必须在构造函数中初始化
  • 成员变量         :N/A
  • 常成员函数      : 可以调用 const 成员函数,不能调用非const修饰的函数;  成员变量-->只读
  • 普通成员函数  :可调用所有成员函数;成员变量-->读写
  • 常对象            :只能调用常成员(函数+变量)
  • 普通对象         :都可以调用
 
 
e.静态   
  • 静态成员变量: 无论创建多少个类的对象,静态成员都只有一个副本   不能直接赋值
  • 静态成员函数: 把函数与类的任何特定对象独立开来; 静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数
 
f.友元
  • 友元函数
  • 友元类
 
class Box
{
   double width;
public:
   friend void printWidth( Box box );
   void setWidth( double wid );
};
// 成员函数定义
void Box::setWidth( double wid )
{
    width = wid;
}
// 请注意:printWidth() 不是任何类的成员函数
void printWidth( Box box )
{
   /* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
   cout << "Width of box : " << box.width <<endl;
}
// 程序的主函数
int main( )
{
   Box box;
   // 使用成员函数设置宽度
   box.setWidth(10.0);
   
   // 使用友元函数输出宽度
   printWidth( box );
   return 0;
}

 

 
g.重载/写/隐藏
  • 重载  :函数名相同;参数不同
  • 重写  :函数名相同;参数相同 出现在继承中 virtual 动态联篇   上行
  • 隐藏/ 重定义    :函数名相同;访问子类-隐藏父类  
 
class A{
public:
     virtual  void  display(){  cout<<"A"<<ENDL; }
     };

class B :  public A{
public:
            void  display(){ cout<<"B"<<ENDL; }
     };
void doDisplay(A *p)
{
p->display();
delete p;
}
int main(int argc,char* argv[])
{
doDisplay(new B());
return 0;
}
这段代码打印出的结果为B,但是当把A类中的virtual去掉之后打印出的就为A

 

 
 
h.虚函数 & 纯虚函数
虚函数是指一个类中你希望重载的成员函数,当你用一个基类指针或引用指向一个继承类对象的时候,你调用一个虚函数,实际调用的是继承类的版本               
 
纯虚函数可以让类先具有一个操作名称,而没有操作内容,让派生类在继承时再去具体地给出定义。凡是含有纯虚函数的类叫做 抽象类 。这种类不能声明对象,只是作为基类为派生类服务  virtual void Show()= 0 ;
 
纯虚函数应该在派生类中重写,否则派生类也是抽象类,不能实例化
 
不管是虚函数还是纯虚函数, 基类都可以为提供他们的实现
 
 
 
class Parent  
{  
public:  
    void Function1();     
    virtual void Function2();   // 这里声明Function2是虚函数  -- 动态联篇
}parent;  
  
void Parent::Function1()  
{  
    printf("This is parent,function1\n");  
}  
  
void Parent::Function2()  
{  
    printf("This is parent,function2\n");  
}  
  
class Child:public Parent  
{  
    void Function1();  
    void Function2();  
} child;  
  
void Child::Function1()  
{  
    printf("This is child,function1\n");  
}  
  
void Child::Function2()  
{  
    printf("This is child,function2\n");  
}  
  
int main(int argc, char* argv[])  
{  
    Parent *p;  // 定义一个基类指针  
    if(_getch()=='c')     // 如果输入一个小写字母c      
        p=&child;         // 指向继承类对象  
    else      
        p=&parent;       // 否则指向基类对象  
    p->Function1();   // 这里在编译时会直接给出Parent::Function1()的入口地址。      
    p->Function2();    // 注意这里,执行的是哪一个Function2?  
    return 0;  
      
}  
输入c:
This is parent,function1
This is child,function2
为什么会有第一行的结果呢?因为我们是用一个Parent类的指针调用函数Fuction1(),虽然实际上这个指针指向的是Child类的对象,但编译器无法知道这一事实(直到运行的时候,程序才可以根据用户的输入判断出指针指向的对象),它只能按照调用Parent类的函数来理解并编译,所以我们看到了第一行的结果。
那么第二行的结果又是怎么回事呢?我们注意到,Function2()函数在基类中被virtual关键字修饰,也就是说,它是一个虚函数。虚函数最关键的特点是“动态联编”,它可以在运行时判断指针指向的对象,并自动调用相应的函数。
不输入c:
This is parent,function1
This is parent,function2

 

i.(对于重载重载)静态联编 & 动态联编
 
静态联编是指联编工作在编译阶段完成的,这种联编过程是在程序运行之前完成的
 
动态联编是指联编在程序运行时动态地进行,根据当时的情况来确定调用哪个同名函数,实际上是在运行时虚函数的实现
 
g.虚基类:解决的是菱形继承
//间接基类A
class A{
protected:
int m_a;
};
//直接基类B
class B: public A{
protected:
int m_b;
};
//直接基类C
class C: public A{
protected:
int m_c;
};
//派生类D
class D: public B, public C{
public:
void seta(int a){ m_a = a; } //命名冲突
void setb(int b){ m_b = b; } //正确
void setc(int c){ m_c = c; } //正确
void setd(int d){ m_d = d; } //正确
为了消除歧义,我们可以在 m_a 的前面指明它具体来自哪个类:
void seta(int a){ B::m_a = a; } || void seta(int a){ C::m_a = a; }
//间接基类A
class A{
protected:
int m_a;
};
//直接基类B
class B: virtual public A{ //虚继承
protected:
int m_b;
};
//直接基类C
class C: virtual public A{ //虚继承
protected:
int m_c;
};
//派生类D
class D: public B, public C{
public:
void seta(int a){ m_a = a; } //正确
void setb(int b){ m_b = b; } //正确
void setc(int c){ m_c = c; } //正确
void setd(int d){ m_d = d; } //正确

 

h.class && struct的大小
class:
1.空类
class A
{
};
sizeof(A); //1
类的实例化就是为每个实例在内存中分配一块地址;每个类在内存中都有唯一的标识,因此空类被实例化时,编译器会隐含地为其添加一个字节,以作区分。

2.虚函数类
class A
{
virtual void Fun();
};
sizeof(A); //4
当一个类中包含虚函数时,会有一个指向其虚函数表的指针vptr,系统为类指针分配大小为4个字节(即使有多个虚函数)

3.普通数据成员
class A
{
int a;
char b;
};
sizeof(A); //8
解析:普通数据成员,按照其数据类型分配大小,由于字节对齐,所以a+b=8字节

4.静态数据成员
class A
{
int a;
static int b;
};
sizeof(A); //4
解析:静态数据成员存放的是全局数据段,即使它是类的一个成员,但不影响类的大小;不管类产生多少实例或者派生多少子类,静态成员数据在类中永远只有一个实体存在。而类的非静态数据成员只有被实例化时,才存在,但类的静态数据成员一旦被声明,无论类是否被实例化,它都已存在,类的静态数据成员可以说是一种特殊的全局变量。

5.普通成员函数
class A
{
void Fun();
};
sizeof(A); //1
解析:类的大小与它的构造函数、析构函数以及其他成员函数无关,只与它的数据成员相关。

6.普通继承
class A
{
int a;
};
class B:public A
{
int b;
};
sizeof(B); //8
解析:普通类的继承,类的大小为本身数据成员大小+基类数据成员大小。

7.虚函数继承
virtual class A
{
int a;
};
class B:virtual public A
{
int b;
};
sizeof(B); //12
解析:虚函数类的继承,派生类大小=派生类自身成员大小+基类数据成员大小+虚拟指针大小(即使继承多个虚基类,也只有一个指向其虚函数表的指针vptr,大小为4字节)。

 

 
struct:
  原则一:结构体中元素是按照定义顺序一个一个放到内存中去的,但并不是紧密排列的。从结构体存储的首地址开始,每一个元素放置到内存中时,它都会认为内存是以它自己的大小来划分的,因此元素放置的位置一定 会在自己宽度的整数倍上开始(以结构体变量首地址为0计算)。
  struct X
 { 
  char a; 
  int b;
   double c;
}S1;
16
 
原则二:在经过第一原则分析后,检查计算出的存储单元是否为所有元素中最宽的元素的长度的整数倍,是,则结束;若不是,则补齐为它的整数倍。
struct X
{
  char a;
  double b
  int c;
 }S2;
24
 
struct X
 
double a;
char b;
 int c; 
}S3;
16
    
 
struct X
 
double a;
char b;
int c;
char d;   
}S4;
 
 
i. 数据成员赋值问题
  • 普通数据成员 -->最好在构造中赋值
  • const --> 只能在构造中赋值
  • static --> 不能在class中赋值
 
 
j. this  指针   只有成员函数才有  this  指针
静态成员函数有一个类范围,他们不能访问类的 this 指针
 
class Box
{
   public:
      // 构造函数定义
      Box(double l=2.0, double b=2.0, double h=2.0)
      {
         cout <<"Constructor called." << endl;
         length = l;
         breadth = b;
         height = h;
      }
      double Volume()
      {
         return length * breadth * height;
      }
      int compare(Box box)
      {
         return this->Volume() > box.Volume();
      }
   private:
      double length;     // Length of a box
      double breadth;    // Breadth of a box
      double height;     // Height of a box
};
int main(void)
{
   Box Box1(3.3, 1.2, 1.5);    // Declare box1
   Box Box2(8.5, 6.0, 2.0);    // Declare box2
   if(Box1.compare(Box2))
   {
      cout << "Box2 is smaller than Box1" <<endl;
   }
   else
   {
      cout << "Box2 is equal to or larger than Box1" <<endl;
   }
   return 0;
}

 

 
k. public private protect
 
  • 公有成员在程序中类的外部是可访问的。您可以不使用任何成员函数来设置和获取公有变量的值 && public 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:public, protected, private
  • 私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员 && protected 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成: private, private, private
  • 保护成员变量或函数与私有成员十分相似,但有一点不同,保护成员在派生类(即子类)中是可访问的 && 基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成: protected, protected, private
 
 
                 mian      友元        public                     private              protected
public        V            V           public                     private               protected   
 
private       X            V           private                    private                private       
 
protected  X            V           protected                private                 protected   
  
 
2.引用
 
a. 引用作为参数
b. 常引用
c. 引用作为返回值
 
b.
 
int a ;
const int &ra=a;
ra=1; //错误
a=1; //正确

 

c.
 
 
float temp;
float fn1(float r){
    temp=r*r*3.14;
    return temp;
}

float &fn2(float r){ //&说明返回的是temp的引用,换句话说就是返回temp本身
    temp=r*r*3.14;
    return temp;
}

int main(){
    float a=fn1(5.0);             //case 1:返回值
  //float &b=fn1(5.0);            //case 2:用函数的返回值作为引用的初始化值 [Error] invalid initialization of non-const reference of type '(有些编译器可以成功编译该语句,但会给出一个warning)
    float c=fn2(5.0);             //case 3:返回引用
    float &d=fn2(5.0);            //case 4:用函数返回的引用作为新引用的初始化值
    cout<<a<<endl;                //78.5
    //cout<<b<<endl;              //78.5
    cout<<c<<endl;                //78.5
    cout<<d<<endl;                //78.5
}

 

 
case 1
返回全局变量temp的值时,C++会在内存中创建临时变量并将temp的值拷贝给该临时变量。当返回到主函数main后,赋值语句a=fn1(5.0)会把临时变量的值再拷贝给变量a
 
case 2  
函数fn1()是以值方式返回到,返回时,首先拷贝temp的值给临时变量。返回到主函数后,用临时变量来初始化引用变量b,使得b成为该临时变量到的别名。由于临时变量的作用域短暂(在C++标准中,临时变量或对象的生命周期在一个完整的语句表达式结束后便宣告结束,也就是在语句float &b=fn1(5.0);之后) ,所以b面临无效的危险,很有可能以后的值是个无法确定的值。
 
 case 3 
函数fn2()的返回值不产生副本,而是直接将变量temp返回给主函数,即主函数的赋值语句中的左值是直接从变量temp中拷贝而来(也就是说c只是变量temp的一个拷贝而非别名) ,这样就避免了临时变量的产生。尤其当变量temp是一个用户自定义的类的对象时,这样还避免了调用类中的拷贝构造函数在内存中创建临时对象的过程,提高了程序的时间和空间的使用效率
 
 
case 4 
函数fn2()的返回值不产生副本,而是直接将变量temp返回给主函数。在主函数中,一个引用声明d用该返回值初始化,也就是说此时d成为变量temp的别名。由于temp是全局变量,所以在d的有效期内temp始终保持有效,故这种做法是安全的。
 
 
3.指针
a.if
if(1)
true is 1

b.NULL
int *ptr = NULL;
ptr is 0
if(ptr) not null

 

c.
  • 函数指针
  • 指针函数
 
函数指针: 每一个函数都占用一段内存单元,它们有一个起始地址, 指向函数入口地址 的指针称为函数指针
数据类型 (*指针变量名)(参数表)
在给函数指针变量赋值时,只需给出函数名,而不必给出参数
如下的函数:
int fn1(int x, int y);
int fn2(int x);
定义如下的函数指针:
int (*p1)(int a, int b);
int (*p2)(int a);
p1 = fn1; //正确
p2 = fn2; //正确
p1 = fn2; //产生编译错误


int max(int x, int y); //求最大数
int min(int x, int y); //求最小数
int add(int x, int y); //求和
void process(int i, int j, int (*p)(int a, int b)); //应用函数指针
int main(){
int x, y;
cin>>x>>y;
cout<<"Max is: ";
process(x, y, max);
cout<<"Min is: ";
process(x, y, min);
cout<<"Add is: ";
process(x, y, add);
getch();
return 0;}
int max(int x, int y){
return x > y ? x : y;}
int min(int x, int y){
return x > y ? y : x;}
int add(int x, int y){
return x + y;}
void process(int i, int j, int (*p)(int a, int b)){
cout<<p(i, j)<<endl;}

 

指针函数:  指针的函数,表示是一个函数,函数返回类型是某一类型的指针     int  *f(x,y)
 
 
4.内存
 
int main(void)
{
big_thing n = return_test() ;
}
big_thing return_test()
{
big_thing b ;
b.buf[0] = 0 ;
return b ;
}

 

* main函数在其栈中的局部变量区域中额外开辟一片空间,将其一部分作为传递返回值的临时对象temp。
* 将temp对象的地址作为隐藏参数传递给return_test函数。
* return_test函数将数据拷贝给temp对象,并将temp对象的地址用 eax 传出。
* return_test返回后,main函数将eax指向的temp对象的内容拷贝给n。
 
 
 
5.STL  Standard Template Library   标准模板库
 
STL 组件主要包括 容器 迭代器 算法 仿函数
STL 是 C++ 通用库,由迭代器、算法、容器、仿函数、配接器和配置器(即内存配置器)组成
 

容器 即用来存储并管理某类对象的集合   向量类vector <T>(数组)、关联数组容器 map <key, val>( 红黑树)、集合类 set <T>( 红黑树)、 链表类 list <T>(双向链表)、 队列 queue <T>、 双端队列容器deque <T>(循环队列)、 栈 stack <T> 

迭代器 用于在一个对象群集的元素上进行遍历动作  在命名空间 std 的范围内定义,通过包含头文件 <algorithm> 来获得使用权

常见的部分算法如下:
  • for_each();
  • find();
  • find_if();
  • count();
  • count_if();
  • replace();
  • replace_if();
  • copy();
  • unique_copy();
  • sort();
  • equal_range();
  • merge();

算法 用来处理群集内的元素,可以出于不同目的搜寻、排序、修改、使用那些元素

仿函数 仿函数具有泛型编程强大的威力,是纯粹抽象概念的例证

 
 
A: 向量类 vector <T> 数组 )   向量是一个能够存放任意类型的动态数组
 
   
vector<int>obj;                     //创建一个向量存储容器                              int
    for(int i=0;i<10;i++)           // 在数组最后添加数据                               push_back
    {
        obj.push_back(i);
        cout<<obj[i]<<",";    
    }
    for(int i=0;i<5;i++)            //去掉数组最后一个数据                               pop_back
    {
        obj.pop_back();
    }
    cout<<"\n"<<endl;
    for(int i=0;i<obj.size();i++)   //容器中实际数据个数                                  size
    {
        cout<<obj[i]<<",";          // 直接输出
    }
    vector<int>::iterator it;       //声明一个迭代器,来访问vector容器,作用:遍历或者指向vector容器的元素
    for(it=obj.begin();it!=obj.end();it++)                                                         iterator   begin( 数组头的指针)   end( 数组的最后一个单元+1的指针)
    {
        cout<<*it<<" ";
    }
   obj.clear();                     //清除容器中所以数据                                   clear                      
   sort(obj.begin(),obj.end());     //从小到大                                            sort
   reverse(obj.begin(),obj.end());  //从大到小                                            reverse    

 

 
B: 关联数组容器 map < key, val>   key不可重复    multimap可以
  • map::begin/cbegin:返回一个迭代器给开始beginning.
  • map::end/cend:返回一个迭代器给末尾end.
  • map::empty:检查容器是否为空。
  • map::size:返回元素的数量。
  • map::max_size:返回最大的可能元素数量。
  • map::clear:清除元素内容(所有)。
  • map::insert:插入元素或节点。
  • map::erase:擦除元素(一个)。
  • map::swap:交换内容。
  • map::count:返回匹配具体key的元素数量。
  • map::find:查找具体key的元素。
  • map::contains:检查容器是否包含具体key的元素。
std::map<std::string, int> map1;
map1["something"] = 69;
map1["anything"] = 199;
map1["that thing"] = 50;
std::cout << map1.first << ':' << map1.second << ' '; //first表示key, second表示value

 

 
C: 集合类 set <T>( 红黑树  
      不能有重复的元素 
      自动将 元素进行了排序
       元素的值不能直接被改变
 
begin()        ,返回一个迭代器,返回的值为set容器的第一个元素
end()      ,返回一个迭代器,返回的值为set容器的最后一个元素
clear()          ,删除set容器中的所有的元素
empty()    ,判断set容器是否为空
max_size()   ,返回set容器可能包含的元素最大个数
size()      ,返回当前set容器中的元素个数
 
 
D: 链表类 list <T>( 双向链表 )
 
assign() 给list赋值                     list2.assign(8,1); 1 1 1 1 1 1 1 1
back() 返回最后一个元素
begin() 返回指向第一个元素的迭代器
clear() 删除所有元素
empty() 如果list是空的则返回true
end() 返回末尾的迭代器
erase() 删除一个元素
front() 返回第一个元素
insert() 插入一个元素到list中
max_size() 返回list能容纳的最大元素数量
merge() 合并两个list
pop_back() 删除最后一个元素
pop_front() 删除第一个元素
push_back() 在list的末尾添加一个元素
push_front() 在list的头部添加一个元素
remove() 从list删除元素
rend() 指向list末尾的逆向迭代器
resize() 改变list的大小
reverse() 把list的元素倒转
size() 返回list中的元素个数
sort() 给list排序
splice() 合并两个list
swap() 交换两个list
unique() 删除list中重复的元素
 
 
  //创建一个list容器的实例LISTINT
   typedef list<int> LISTINT;
   //从前面向listOne容器中添加数据   
    listOne.push_front (2);   
    listOne.push_front (1);   
    //从后面向listOne容器中添加数据   
    listOne.push_back (3);   
    listOne.push_back (4);   
    //从前向后显示listOne中的数据   
    cout<<"listOne.begin()--- listOne.end():"<<endl;   
    for (i = listOne.begin(); i != listOne.end(); ++i)   
        cout << *i << " ";   
    cout << endl;   
 
 
6.智能指针

RAII 是 resource acquisition is initialization 的缩写,意为“资源获取即初始化”。其核心是把资源和对象的生命周期绑定,对象创建获取资源,对象销毁释放资源

why:
我们知道c++的内存管理是让很多人头疼的事,当我们写一个new语句时,一般就会立即把delete语句直接也写了,但是我们不能避免程序还未执行到delete时就跳转了或者在函数中没有执行到最后的delete语句就返回了,如果我们不在每一个可能跳转或者返回的语句前释放资源,就会造成内存泄露。使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个类,当超出了类的作用域是,类会自动调用析构函数,析构函数会自动释放资源
 
  • auto_ptr
  • shared_ptr
  • weak_ptr
  • unique_ptr 
 
A. auto_ptr   用于管理动态内存分配  auto_ptr对象生命周期结束时,其析构函数会将auto_ptr对象拥有的动态内存自动释放
a.构造函数
将已存在的指向动态内存的普通指针作为参数来构造
int *p=new int(33);
auto_ptr<int> aptr(p);
直接构造
auto_ptr<int> aptr(new int(33));

 

b.拷贝构造
利用已经存在的智能指针来构造新的智能指针
 
auto_ptr<string> p1(new string("name"));
auto_ptr<string> p2(p1);

 

因为 一块动态内存只能由一个智能指针独享 ,所以在拷贝构造和赋值时都会发生拥有权转移,在此拷贝构造过程中,p1将失去对字符串内存的所有权,而p2获得,对象销毁时,p2负责内存的自动销毁
 
c.赋值
auto_ptr<string> p1(new string("name"));
auto_ptr<string> p2(new string("sex"));
p1=p2;

 

d. reset   重新设置auto_ptr指向的对象,类似于赋值操作 会调用析构
auto_ptr<string> aptr(new string("name"));
aptr.reset(new string("sex"));

class Test
{
public:
    Test(string s)
    {
        str = s;
       cout<<"Test creat\n";
    }
    ~Test()
    {
        cout<<"Test delete:"<<str<<endl;
    }
    string& getStr()
    {
        return str;
    }
    void setStr(string s)
    {
        str = s;
    }
    void print()
    {
        cout<<str<<endl;
    }
private:
    string str;
};
  
  
int main()
{
    auto_ptr<Test> ptest(new Test("123"));//调用构造函数输出Test creat
    ptest->setStr("hello ");//修改成员变量的值
    ptest->print();//输出hello
    ptest.get()->print();//输出hello
    ptest->getStr() += "world !";
    (*ptest).print();//输出hello world
    ptest.reset(new Test("123"));//成员函数reset()重新绑定指向的对象,而原来的对象则会被释放,所以这里会调用一次构造函数,还有调用一次析构函数释放掉之前的对象
    ptest->print();//输出123
    return 0;//此时还剩下一个对象,调用一次析构函数释放该对象
}

 

 
e. release   返回auto_ptr指向的那个对象的内存地址,并且释放这个对象的所有权  用此函数初始化auto_ptr可以避免两个auto_ptr对象指向同一个对象的情况 不 会调用析构
 
auto_ptr<string> aptr(new string("name"));
auto_ptr<string> aptr1(aptr.get());//这是两个auto_ptr拥有同一个对象
auto_ptr<string> aptr2(aptr.release());//release可以先释放所有权,这样就不会指向同一个对象

 

release,这个函数只是把智能指针赋值为空,但是它原来指向的内存并没有被释放,相当于它只是释放了对资源的所有权,从下面的代码执行结果可以看出,析构函数没有被调用
那么当我们想要在中途释放资源,而不是等到智能指针被析构时才释放,我们可以使用ptest.reset()
 
    auto_ptr<Test> ptest(new Test("123"));
    ptest.release();
Test creat

 

f.   get    返回auto_ptr指向的那个对象的内存地址

int *p=new int(10);
auto_ptr<int> aptr(p);
&p与aptr.get() 相同
 
 
B:unique_ptr
 
与auto_ptr异同
1.ptest2 = ptest
  ptest2 = std::move(ptest)

2.if (ptest.get() == NULL)
  if (ptest == NULL )

    unique_ptr<Test> ptest(new Test("123"));//调用构造函数,输出Test creat
    unique_ptr<Test> ptest2(new Test("456"));//调用构造函数,输出Test creat
    ptest->print();//输出123
    ptest2 = std::move(ptest);
//不能直接ptest2 = ptest,调用了move后ptest2原本的对象会被释放,ptest2对象指向原本ptest对象的内存,输出Test delete 456
    if(ptest == NULL)cout<<"ptest = NULL\n";
//因为两个unique_ptr不能指向同一内存地址,所以经过前面move后ptest会被赋值NULL,输出ptest=NULL  
    Test* p = ptest2.release();
//release成员函数把ptest2指针赋为空,但是并没有释放指针指向的内存,所以此时p指针指向原本ptest2指向的内存
    p->print();//输出123
    ptest.reset(p);//重新绑定对象,原来的对象会被释放掉,但是ptest对象本来就释放过了,所以这里就不会再调用析构函数了
    ptest->print();//输出123
    ptest2 = fun(); //这里可以用=,因为使用了移动构造函数,函数返回一个unique_ptr会自动调用移动构造函数
    ptest2->print();//输出789
    return 0;//此时程序中还有两个对象,调用两次析构函数释放对象

 

C: share_ptr 
 
从名字share就可以看出了 资源可以被多个指针共享 ,它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。出了可以通过new来构造,还可以通过传入auto_ptr, unique_ptr,weak_ptr来构造。当我们调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放
 
  
  shared_ptr<Test> ptest(new Test("123"));//调用构造函数输出Test create
    shared_ptr<Test> ptest2(new Test("456"));//调用构造函数输出 Test creat
    cout<<ptest2->getStr()<<endl;//输出456
    cout<<ptest2.use_count()<<endl;//显示此时资源被几个指针共享,输出1
    ptest = ptest2;//"456"引用次数加1,“123”销毁,输出Test delete:123
    ptest->print();//输出456
    cout<<ptest2.use_count()<<endl;//该指针指向的资源被几个指针共享,输出2
    cout<<ptest.use_count()<<endl;//2
    ptest.reset();//重新绑定对象,绑定一个空对象,当时此时指针指向的对象还有其他指针能指向就不会释放该对象的内存空间,
    ptest2.reset();//此时“456”销毁,此时指针指向的内存空间上的指针为0,就释放了该内存,输出Test delete
    cout<<"done !\n";
    return 0;
 
 
D:weak_ptr
 
weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。
 
 
class B;
class A
{
public:
    shared_ptr<B> pb_;
    ~A()
    {
        cout<<"A delete\n";
    }
};
class B
{
public:
    shared_ptr<A> pa_;
    ~B()
    {
        cout<<"B delete\n";
    }
};
  
void fun()
{
    shared_ptr<B> pb(new B());
    shared_ptr<A> pa(new A());
    pb->pa_ = pa;
    pa->pb_ = pb;
    cout<<pb.use_count()<<endl;
    cout<<pa.use_count()<<endl;
}
  
int main()
{
    fun();
    return 0;
}

 

 
可以看到fun函数中pa ,pb之间互相引用,两个资源的引用计数为2,当要跳出函数时,智能指针pa,pb析构时两个资源引用计数会减一,但是两者引用计数还是为1,导致跳出函数时资源没有被释放(A B的析构函数没有被调用),如果把其中一个改为weak_ptr就可以了,我们把类A里面的shared_ptr<B> pb_; 改为weak_ptr<B> pb_; 运行结果如下,这样的话,资源B的引用开始就只有1,当pb析构时,B的计数变为0,B得到释放,B释放的同时也会使A的计数减一,同时pa析构时使A的计数减一,那么A的计数为0,A得到释放。
 
 
 
7.typedef 
 
a. 定义一种类型的别名,而不只是简单的宏替换
typedef char* PCHAR; // 一般用大写
PCHAR pa, pb; // 可行,同时声明了两个指向字符变量的指针
 
b. 用在旧的C的代码中(具体多旧没有查),帮助struct。以前的代码中,声明struct新对象时,必须要带上struct,即形式为: struct 结构名 对象名
struct tagPOINT1  
{  
    int x;  
    int y;  
};  
struct tagPOINT1 p1;  
typedef struct tagPOINT  
{  
    int x;  
    int y;  
}POINT;  
POINT p1; // 这样就比原来的方式少写了一个struct,比较省事,尤其在大量使用的时候  

 

c. 用typedef来定义与平台无关的类型
typedef long double REAL
在不支持 long double 的平台二上,改为:
typedef double REAL; 
 
d. 为复杂的声明定义一个新的简单的别名
char (*pFun)(int);
char glFun(int a)
{ return;}
void main()
{
    pFun = glFun;
    (*pFun)(2);
}

typedef char (*PTRFUN)(int);
PTRFUN pFun;
char glFun(int a)
{ return;}
void main()
{
    pFun = glFun;
    (*pFun)(2);
}

 

8.文件操作 :C与C++ 
C:
包含头文件:     #include<stdio.h>
 
a. 打开文件
FILE * fopen(const char * path,const char * mode)   如果文件成功打开,返回指向FILE对象的指针,否则返回NULL
r 以只读方式打开文件,该文件必须存在。
r+ 以可读写方式打开文件,该文件必须存在。
rb+ 读写打开一个二进制文件,允许读数据。
rt+ 读写打开一个文本文件,允许读和写。
w 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。
w+ 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。
a 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留)
a+ 以附加方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。 (原来的EOF符不保留)
wb 只写打开或新建一个二进制文件;只允许写数据。
wb+ 读写打开或建立一个二进制文件,允许读和写。
wt+ 读写打开或着建立一个文本文件;允许读写。
at+ 读写打开一个文本文件,允许读或在文本末追加数据
ab+ 读写打开一个二进制文件,允许读或在文件末追加数据
 
b. 读取文件块数据
size_t fread(void *buffer, size_t size, size_t count, FILE *file);
buffer是读取数据后存放地址,size是的块长度,count是块的数量,实际读取长度为size*count,返回值为块成功读取块的count数量
 
c. 写入文件块数据
size_t fwrite(const void *buffer, size_t size, size_t count, FILE *file);
 
d. fclose() 
fclose()函数用来关闭一个由fopen()函数打开的文件  fclose( fp );  
该函数返回一个整型数。当文件关闭成功时 返回0, 否则返回一个非零值
 
e. fseek()
定位到流中指定的位置
int fseek(FILE *stream, long offset, int whence);如果成功返回0
参数offset是移动的字符数,whence是移动的基准
基准位置 
  SEEK_SET 0 文件开头 
  SEEK_CUR 1 当前读写的位置 
  SEEK_END 2 文件尾部
 
C++:
 
#include <fstream>
ofstream         //文件写操作 内存写入存储设备
ifstream         //文件读操作,存储设备读区到内存中
fstream          //读写操作,对打开的文件可进行读写操作
 
 
9.函数模板
泛型编程 有函数模板与类模板
函数模板:
 
 
使用函数模板时有两种方式
    -  自动类型推到调用  Swap(a, b)
    -  具体类型显示调用  Swap<int>(a, b)
 
template <typename T>
void Swap(T& a, T& b)
{
    T tmp = a;
    a = b;
    b = tmp;
}
void main()
{
    int a = 10;
    int b = 20;
    Swap(a, b);    //自动推到调用
    //Swap<int>(a, b);//显示指定调用
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    float c = 12.3;
    float d = 23.4;
    //Swap(c, d); //自动推到调用


多参类型:
template <typename T1, typename T2, typename T3>
T1 add(T2 a, T3 b)
{
    T1 ret;
    ret = static_cast<T1>(a + b);
    return ret;
}
void main()
{
    int c = 12;
    float d = 23.4;
    //cout << add(c, d) << endl;    //error,无法自动推导函数返回值
    cout << add<float>(c, d) << endl;    //返回值在第一个类型参数中指定
35.4
35

 

 
10. 红黑树 红黑树是特殊的二叉查找树,意味着它满足二叉查找树的特征:任意一个节点所包含的键值,大于等于左孩子的键值,小于等于右孩子的键值
 
 
11. i++ & ++i
++i返回的是i自加后的内容,i++返回的是i未自加的内容。
举个例子:
int i = 10;
a = i++;
vs
int i = 10;
b = ++i;

 

12.extern
你现在编译的文件中,有一个标识符虽然没有在本文件或本文件当前位置中定义,但是它是在别的文件中或本文件其它位置定义的全局变量,你要放行
test1.h
extern char g_str[]; // 声明全局变量g_str
void fun1();

test1.cpp
#include "test1.h"       
char g_str[] = "123456"; // 定义全局变量g_str
void fun1() {
cout << g_str << endl;

test2.cpp
#include "test1.h"
void fun2()   
{
    cout << g_str << endl;   
}

 

13.assert 
为假( 表达式【包括指针】是零 ),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行。
 
 
14.指针数组 & 数组指针
指针数组-- 指针的数组-- 数组的所有元素都是指针类型-- char * arr [ 4 ] = { "hello" , "world" , "shannxi" , "xian" } ;
数组指针-- 数组的指针-- 这个指针指向一个数组的首地址
char a[4];
char (*pa)[4];
pa = &a;

 

15.左值与右值
左值(lvalue):指向内存位置的表达式被称为左值(lvalue)表达式。左值可以出现在赋值号的左边或右边。
右值(rvalue):术语右值(rvalue)指的是存储在内存中某些地址的数值。右值是不能对其进行赋值的表达式,也就是说,右值可以出现在赋值号的右边,但不能出现在赋值号的左边。
int g = 20; V
10 = 20; X

 

 
16.explicit
C++中的explicit关键字只能用于修饰只有一个参数的类构造函数
关键字的作用就是防止类构造函数的隐式自动转换
类构造函数默认情况下即声明为implicit(隐式)
class CxString // 没有使用explicit关键字的类声明, 即默认为隐式声明 {
public:
char *_pstr;
int _size;
CxString(int size)
{
_size = size; // string的预设大小
_pstr = malloc(size + 1); // 分配string的内存
memset(_pstr, 0, size + 1);
}
CxString string2 = 10; // 这样是OK的, 为CxString预分配10字节的大小的内存 若改外explicit 则不可以

 

17.强制类型转换
const_cast ,  static_cast , dynamic_cast , reinterpret_cast
 
const_cast: 去除指针或引用的const属性
a、转化常量指针为非常量的指针,并且仍然指向原来的对象;
b、转化常量引用为非常量的引用,并且仍然指向原来的对象;
c、const_cast一般用于修改指针。如const int *ptr形式
 
int ary[4] = { 1, 2, 3, 4 };
const int *c_ary = ary;
//c_ary[0] = 5;//错误,常量不能改变
int *ary2 = const_cast<int*>(c_ary);//const_cast将常量指针转化为非常量指针
ary2[0] = 5;//正确
int a = 2;
const int &c_a = a;
//c_a = 5;//错误,常量不能改变
int &a2 = const_cast<int&>(c_a);//const_cast将常量引用转化为非常量引用
a2 = 5;//正确

 

static_cast:转换数据类型,类的上下行转换

class Base{
};
class Derived :public Base{
};
//上行转换 Derived->Base,安全 Derived derived;
Base *base_ptr = static_cast<Base*>(&derived);
//下行转换 Base->Derived,由于没有动态类型检查,编译能通过但不安全
Base base;
Derived *derived_ptr = static_cast<Derived*>(&base);
 
 
 
dynamic_cast:安全的上下行转换。
  上行转换(子类到基类的指针转换),dynamic_cast成功转换,运行正常且输出预期结果。而下行转换(基类到子类的转换),dynamic_cast在转换时也没有报错,但是输出给base2deri是空指针,说明dynami_cast在程序运行时对类型转换对“运行期类型信息”(Runtime type information,RTTI)进行了检查,是安全的(转换后为空指针,不会指向未知内存,保证了使用安全)。而用static_cast由于没有动态类型检查,编译能通过但不安全。
 
class Base{
public:
Base() {}
~Base() {}
void print() {
std::cout << "This is Base" << endl;
}
virtual void virtual_foo() {}
};

class Derived : public Base{
public:
Derived() {}
~Derived() {}
void print() {
std::cout << "This is Derived" << endl;
}
virtual void virtual_foo() {}
};

void main(){
cout << "dynamic_cast测试" << endl;
//上行转换 Derived->Base
Derived *derived = new Derived();
derived->print();//输出This is Derived
Base* deri2base = dynamic_cast<Base*>(derived);
if (deri2base != nullptr){
derived->print();//输出This is Derived }
//下行转换 Base->Derived
Base *base = new Base();
base->print();//输出This is Base
Derived* base2deri = dynamic_cast<Derived*>(base);
if (base2deri != nullptr){//base2deri为空,不进行打印
base2deri->print();
}
Base *base2 = new Derived();
base2->print();//输出This is Base
//Derived* deri2 = new Base();//错误。不能直接将Base*转换为Derived*,即不能直接下行转换。

 

 
reinterpret_cast: 重新转换的含义,就相当于 C 语言中不相关类型的转换,强转
 
 
DONE
 
  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值