作者:李春港
出处:https://www.cnblogs.com/lcgbk/p/14643010.html
目录
- 前言
- 一、C++常用后缀
- 二、头文件
- 1、C++输入输出
- 2、在C++中使用C的库函数
- 三、 指针与动态内存分配
- 1、C
- 2、C++
- 四、命名空间
- 1、作用
- 2、定义
- 3、使用空间成员
- 4、命名空间嵌套
- 5、匿名空间
- 六、引用
- 1、引用特点:
- 2、引用的应用:
- 七、函数重载
- 八、函数缺省参数(默认参数)
- 九、类与对象
- 1、类
- 2、对象
- 十、构造和析构函数
- 1、构造函数
- <1>构造函数定义及重载
- <2>构造函数参数列表初始化
- <3>拷贝构造函数
- 2、析构函数
- 1、构造函数
- 十一、类的内存空间
- 十二、类继承
- 1、继承方式
- 2、继承
- (1)继承构造方法和析构方法的定义和调用
- (2)继承后成员函数调用
- 3、多继承
- 4、虚拟继承
- 十三、虚函数、虚表
- 十四、纯虚函数(抽象函数)、抽象类
- 十五、多态、虚析构
- (1)多态
- (2)虚析构
- 十六、友元
- 十七、友元类
- 十八、运算符重载
- 1、双目运算符重载
- (1)+ -重载
- (2)输出、输入,运算符重载
- 3、单目运算符重载
- (1)++A、A++重载
- (2)重载中括号[ ]
- 4、用运算符重载实现数据类型转换
- (1)转换构造函数
- (2)用运算符重载实现数据类型转换
- 1、双目运算符重载
- 十九、模板函数
- 二十、模板类
- 1、模板类的定义
- 2、模板类友元重载输出
- 3、模板类继承
- 4、模板类中的静态成员
- 二十一、强制类型转换const_cast、static_cast、reinterpert_cast、dynamic_cast
- 1、const_cast把常量转为变量
- 2、static_cast静态转化
- 3、reinterpret_cast强制类型转换符
- 4、dynamic_cast类转换
- 二十二、异常捕捉和处理
- (1)异常抛出和捕捉语句
- (2)异常的处理规则
- (3)实例
- (4)总结
- 二十三、STL标准模板库
- 1、vector(顺序表)
前言这篇文章是对C++的知识点做了一些简单的总结,基本包含了所有的C++基础知识点 。以下提到的知识点并非深入讲解,只是大概讲解了各个知识点的基本使用 。如需要深入了解,可以针对某个知识点去深入学习 。
一、C++常用后缀cpp, .h, cc, cxx, hpp
二、头文件1、C++输入输出
- 头文件#include
- 标准输入(standard input)与预定义的 istream 对象 cin 对应
- 标准输出(standard output) 与预定义的 ostream 对象 cout 对应
- 标准出错(standard error)与预定义的的 ostream 对象 cerr 对应
#include <iostream>int main(void){ int a=0, b=0; char c=0; std::cout<<"please input type: a+b" <<std::endl; std::cin>>a>>c>>b; switch(c) { case '+': std::cout<<a<<c<<b<<"="<<a+b<<std::endl; break; case '-': std::cout<<a<<c<<b<<"="<<a-b<<std::endl; break; case '*': std::cout<<a<<c<<b<<"="<<a*b<<std::endl; break; case '/': std::cout<<a<<c<<b<<"="<<a/b<<std::endl; break; } return 0;}2、在C++中使用C的库函数extern "C"{#include <stdlib.h>#include <string.h>}三、 指针与动态内存分配静态内存分配(全局变量,局部变量),动态内存分配(在 c 中用 malloc 分配的堆空间 free 来释放)c++中用 new 分配堆空间 delete 释放 。1、C
char *name =(char*) malloc(100);free(name);2、C++- 整形数: int *p = new int(10) ; 分配空间并且初始化为 10 释放 delete p
- 整形数组:int *arr = new int[10] ; 分配十个连续整数空间 释放 delete []arr
- 字符型:char *p = new char('a'); 释放 delete p;
- 字符串:char *arr = new char[100];分配 100 个字符空间 释放 delete []arr;
1、作用
- 避免名称冲突;
- 模块化应用程序;
- 匿名的命名空间可避免产生全局静态变量,创建的 “匿名” 命名空间只能在创建它的文件中访问 。
namespace 空间名{函数,变量,类型}例子:namespace class01{std::string name="jack";int age=19; int number = 123;}3、使用空间成员1、 直接通过空间名::成员名 --标准使用--提倡使用的方法class01::name -- ::所属符号2、using指示符指引using namespace calss01; // 把class01空间了里面的内容暴露在当前位置,当文件有变量与命名空间的成员一样时,则后期使用该成员或变量时,程序运行时会报错;但能编译通过3、 using声明using class01::number;// 当文件也有number变量时,则编译的时候就报错,相当于定义了两次该变量 。所属符号::
文章插图
4、命名空间嵌套
namespace AAA{namespace BBB{int number=0;}}使用:- AAA::BBB::number;
- using namespace AAA::BBB; number;
- using AAA::BBB::number;
static 。
定义:
namespace {int data;}匿名空间与static的异同:static 无法修饰自定义类型;static 产生当前 符号只在当前源文件有效,是因为它修改了符号的Bind属性,使之变为局部的;而匿名空间虽然可以产生相同效果,但是符号还是具有外部链接属性 。匿名命名空间内的变量与函数,只在当前源文件内有效;不同源文件的匿名命名空间,可以存在同名符合 。static需要在每个变量加上
六、引用引用:就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样 。
引用的声明方法:类型标识符 &引用名=目标变量名;(别名)
int a = 10;
int &ra = a; (ra 就是 a 的引用 ,也称 a 的别名)
1、引用特点:
- &在此不是求地址运算符,而是起标识作用 。
- 类型标识符是指目标变量的类型 。
- 声明引用时,必须同时对其进行初始化 。
- 引用声明完毕后,相当于目标变量有两个名称即该目标原名称和引用名,且不能再把该引用名作为其他变量名的别名 。
- 声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元,但引用本身是有大小的,一个指针的大小,在64位系统:sizeof(&ra) = 8,32位为4字节,sizeof(ra)=sizeof(a)被引用对象的大小 。故:对引用求地址,就是对目标变量求地址 。&ra 与&a 相等 。
- 不能建立数组的引用 。因为数组是一个由若干个元素所组成的集合,所以无法建立一个
引用的一个重要作用就是作为函数的参数 。以前的 C 语言中函数参数传递是值传递,如果有大块数据作为参数传递的时候,采用的方案往往是指针,因为这样可以避免将整块数据全部压栈,可以提高程序的效率 。但是现在(C++中)又增加了一种同样有效率的选择
2.常引用
常引用声明方式:const 类型标识符 &引用名 = 目标变量名;
用这种方式声明的引用,不能通过引用对目标变量的值进行修改,从而使引用的目标成为 const,达到了引用的安全性 。
3.引用作为函数返回值
要以引用返回函数值,则函数定义时要按以下格式:
类型标识符 &函数名 (形参列表及类型说明){ 函数体 }
特点:
- 以引用返回函数值,定义函数时需要在函数名前加&
- 用引用返回一个函数值的最大好处是,在内存中不产生被返回值的副本 。
- 不能返回局部变量的引用
int open();
int open(const char* filename);
int open(const char *filename , int flag);
c++中编译程序的是检测函数,通过函数名和参数列表
如果在一个文件中出现同名的函数但参数列表不同,那么这些函数属于重载
函数重载的依据:
- 函数名相同,
- 参数列表不同,(个数,类型) add(int, doube) add(double, int)
- 如果参数是指针,或引用,那么const修饰也可以作为重载依据
- 函数的返回不能作为函数重载的依据
- 参数是否为默认参数不能作为函数重载的依据
int open(const char *filename="c++", int flag=10)
int open(const char *filename="c++", int flag) 错误
注意: 若给某一参数设置了默认值,那么在参数表中其后(也就是右边)所有的参数都必须也设置默认值
九、类与对象1、类(1)定义:
class 类名{类的特征(属性) 成员变量类的行为(功能) 成员方法,函数};注意:当类里面的成员参数函数有默认值时,若需要在外部定义该函数时,不能写默认值,默认值只能在类里面声明的时候写默认值 。例子: class Tdate{public:void Set(int m, int d, int y ){month = m ;day = d ;year = y ;}private:int month;int day;int year;};struct和class在C++都能定义类,其区别:- struct作为数据结构的实现体,它默认的数据访问控制是public的,而class作为对象的实现体,它默认的成员变量访问控制是private的 。
- 默认的继承访问权限 。struct是public的,class是private的 。
- public 公有 公有段的成员是提供给外部的接口,在外部可以访问
- protected 保护 保护段成员在该类和它的派生类中可见,在类外不能访问(也就是在外部创建对象时不能访问)
- private 私有 私有段成员仅在类中可见(友元函数或者友元类可以访问),在类外不能访问(也就是在外部创建对象时不能访问)
- 类内访问:在类的成员函数中访问成员(没有任何限制)
- 类外访问: 在类的外部通过类的对象访问类的成员
class Tdate{public:int num;void set(int m, int d, int y ){month = m ;day = d ;year = y ;}private:int month;int day;int year;};//定义对象Tdate A;Tdate *B = new Tdate( );//对象成员访问A.set(1,1,1);A.num = 2;B->set(1,1,1);B->num = 2;十、构造和析构函数1、构造函数构造函数是成员函数,函数名与类名相同,函数没有返回值,函数不需要用户调用,在创建对象的时候自动调用 。(1)如果创建一个类你没有写任何构造函数,则系统会自动生成默认的无参构造函数,函数为空,什么都不做 。
(2)只要你写了一个下面的某一种构造函数,系统就不会再自动生成这样一个默认的构造函数,如果希望有一个这样的无参构造函数,则需要自己显示地写出来
(3)参数列表初始化:只有构造函数才有参数列表初始化 。若要在类内声明,类外定义构造函数,且使用参数列表初始化参数时,则在类内声明的时候不允许参数列表初始化,只能类外定义的时候进行参数列表初始化
(4)函数默认参数:无论是成员函数还是构造函数,若需要类内声明和类外定义的时候,默认参数值在声明或者定义的时候都可赋值,但声明和定义的参数不能有默认值
<1>构造函数定义及重载
class Complex{public:Complex()//构造函数定义{m_real = 0.0;m_imag = 0.0;}Complex(int a,int b)//重载构造函数定义{m_real = a;m_imag = b;}private :double m_real;double m_imag;};//创建对象Complex A;//这个时候不能有()Complex A(1,1);Complex *A = new Complex( );//可以有()也可以没有Complex *A = new Complex(1,1);<2>构造函数参数列表初始化Student(string n="name", int num=1000) :name(n),number(num){ //name = n; //number = num;}注意:* name、number:是本类里面的成员;* n、num:是对成员赋的值或者变量;* 不能在类里面声明的时候用参数列初始化,声明的时候可以加默认值;对对象成员进行列表初始化:class A{public:A(int a,int b){}}class B:public A{A a;public:B(int c,int d ):a(c,d){}}使用原因及用处:- 构造函数是成员函数,必须创建对象后才能调用
- 参数列表初始化是在申请空间的同时就初始化
- 如果成员是const修饰的成员、引用成员、继承时候调用父类构造函数,这几种情况就必须用参数列表初始化 。
Student Jack; //构造函数Student Rose = Jack; //拷贝构造函数Student Tom (Jack); //拷贝构造函数后面两种拷贝构造函数不会再次调用构造函数(2)深拷贝构造函数class Student{public: Student(int age, const char *n){ this->age = age; this->name = new char[32]; strcpy(this->name, n); cout<<"Student()"<<endl; }//this指针就是函数调用者 ~Student(){ delete []this->name; cout<<"~Student()"<<endl; } //深拷贝构造函数 Student(Student& s) { cout<<"Student(Student&)"<<endl; this->age = s.age; this->name = new char[32]; strcpy(this->name, s.name); //this->name = s.name; }private: int age; char *name;};2、析构函数- 函数名有类一样在函数名前面添加~符号
- 析构函数没有返回值,也没有参数
- 析构函数在对象销毁的时候自动调用(如果new一个对象,构造函数会自动执行,只有在delete的时候才调用析构函数)
class Complex{public:Complex( )//构造函数定义{cout << "complex" << endl;}~ Complex( )//析构函数定义{cout << "~complex" << endl;}};int main( ){Complex a;Complex *p = new Complex();delete p;return 0;}结果:complexcomplex~complex~complex十一、类的内存空间类本身是一个数据类型,在没有定义对象前是不占用内存空间的,定义对象的时候才会分配空间 。- 计算一个类的对象占用多少空间用sizeof(类名或对象)
- 类的对象大小是其数据成员(非静态数据段),和虚函数表指针(一个类里最多只能有两个指针,一个是虚函数的指针,一个是虚继承的指针)大小和 。普通方法(普通函数)不占用内存,但用virtual修饰的虚函数占用一个指针大小的内存 。注:一个指针的大小、内存的对齐方式和编译器有关;64位的话,大小为8;32位的话,大小为4 。
- 如果一个类中没有数据成员,也没有虚表那么这个类的大小规定为 1 个字节 。
- 新的类从已知的类中得到已有的特征的过程
- 新类叫派生类/子类
- 已知的类叫基类/父类
- 如果直接将派生类的对象赋值给基类对象,派生类自身的成员就会被丢弃,只保留基类继承来的成员 。
- 将基类指针指向派生类对象是安全的,因为派生类对象“是”它的基类的对象 。但是要注意的是,这个指针只能用来调用基类的成员函数 。
继承可以减少重复的代码 。比如父类已经提供的方法,子类可以直接使用,不必再去实现 。
类的继承格式:
class 子类名 :继承方式 父类{子类成员};例如:class Base{public:Base() {}int b;};class Child: public Base{};1、继承方式继承方式: 公有继承,保护继承,私有继承- 公有继承(public):继承时保持基类中各成员属性不变,并且基类中的private成员被隐藏 。派生类的成员只能访问基类中的public/protected成员,而不能访问private成员;派生类的对象只能访问基类中的public成员 。
- 保护继承(protected):继承时基类中各成员属性均变为protected,并且基类中的private成员被隐藏 。派生类的成员只能访问基类中的public/protected成员,而不能访问private成员;派生类的对象不能访问基类中的任何的成员 。
- 私有继承(private):继承时基类中各成员属性均变为private,并且基类中private成员被隐藏 。派生类的成员也只能访问基类中的public/protected成员,而不能访问private成员;派生类的对象不能访问基类中的任何的成员 。
注意2: 无论那种继承子类的大小为子类+父类(所有成员都要加起来,包括私有成员)
2、继承(1)继承构造方法和析构方法的定义和调用因为有父类才有子类,所以调用顺序如下:
构造函数的调用顺序父类构造函数—对象成员构造函数—子类构造函数 。
析构函数则相反 。
注意:
- 当派生类的构造函数为B(){cout << "Afather\n";}时,创建一个派生类默认会调用没有参数的父类构造函数A() 。
- 如果父类构造函数带无默认值参数,派生类构造函数怎么写?
如下:
例子一:父类构造函数public: Person(string name, string sex, int age):name(name),sex(sex),age(age) { cout<<"Person()"<<endl; }子类构造函数public: Student( ):Person("jack","man",19){ cout<<"Student()"<<endl; } //==>Person();例子二:class Animal{public:Animal(int w, const char *color, int age){this->weight = w;this->age = age;strcpy(this->color, color);}protected:int weight;int age;char color[32];};class Cat:public Animal{public:Cat(int w, const char *color, int age, const char *type):Animal(w, color, age){strcpy(this->type, type);}void show(){cout << "weight=" << weight << "\nage=" << age << "\ncolor=" << color <<endl;}protected:char type[32];};(2)继承后成员函数调用- 父子类成员函数名相同时,不是重载,这时父类的此函数会别隐藏
- 子类调用成员函数时候会检测子类是否存在,如果存在就调用自己的,如果不存在就调用父类的(前提是父要有这个函数)
- 如果子类和父同时存在这个函数,一定要调用父类函数,可以用(父类名::函数名( ))调用 。
#include <iostream>using namespace std;class A{public:A(){cout << "Afather\n";}~A(){cout << "~Afather\n";}void fun( ){cout << "father fun\n";}};class B:public A{public:B(){cout << "Bchildren\n";}~B(){cout << "~~Bchildren\n";}void fun(){cout << "children fun\n";}};int main( ){B x;x.A::fun( );//调用父类的funreturn 0;}输出结果:AfatherBchildrenfather fun~~Bchildren~Afather3、多继承(1)语法:class <派生类名>:<继承方式1> <基类名1>,<继承方式2><基类名2>,…{<派生类类体>};(2)例子1:class A{public: A(){cout<<"A()"<<endl;} ~A(){cout<<"~A()"<<endl;}protected: int dataA;};class B{public: B(){cout<<"B()"<<endl;} ~B(){cout<<"~B()"<<endl;}protected: int dataB;};class C:public A, public B{public: C(){cout<<"C()"<<endl;} ~C(){cout<<"~C()"<<endl;}protected: int dataC;};注意:创建子类对象构造顺序 A->B->C如果改为:class C:public B, public A,创建子类对象构造顺序 B->A->C
(3)例子2: 如果父类构造函数带参数
继承关系class C:public A, public B父类带参数A(int a):dataA(a){cout<<"A()"<<endl;}B(int b):dataB(b){cout<<"B()"<<endl;}C(int a, int b, int c):A(a), B(b),dataC(c){cout<<"C()"<<endl;}(4)多个父类有同名的成员多个父类有同名的成员,在子类中访问会出现歧义
- 显示调用对应父类中的成员
c.A::info();
c.B::info(); - 在子类中添加父类的同名成员
这个时候系统会将父类的同名成员隐藏,调用子类的成员
class D :virtual public B{ //虚拟继承 ...};
文章插图
多继承中多级继承时候多个父类同时继承同一个基类出现二义性问题--用虚拟继承解决 。
例如:
class A{ public:void fun(){}};class B:virtual public A{ };class C:virtual public A{ };class D:public B,public C{ };int main(void){D x;x.fun();//如果不是虚继承,会出现二异性,因为在D类继承了两次A类return 0;}十三、虚函数、虚表定义: 在类的成员函数声明前面添加virtualvirtual void show(){cout<<data<<endl;}- 如果一个类中包含虚函数,那么这个类的对象中会包含一个虚表指针vptr
- 虚表指针保存在对象空间的最前面
- 虚表中存储的是类中的虚函数地址
- 对象调用类中虚函数,会查询虚表指针再执行函数
- 一个类里最多只有两个虚表指针(一个是虚函数的指针,一个是虚继承的指针)
- 用virtual修饰的虚函数占用一个指针大小的内存 。64位的话,大小为8;32位的话,大小为4 。
- 同一个类的不同实例共用同一份虚函数表, 它们都通过一个所谓的虚函数表指针__vfptr(定义为void**类型)指向该虚函数表.

文章插图
#include <iostream>using namespace std;class Base{public:Base(){}virtual ~Base(){}public:virtual void show(int a=123){cout<<"Base::show()"<<a<<endl;}};class Child:public Base{public:Child(){}~Child(){}virtual void show(int a=321){cout<<"Child::show()"<<a<<endl;}virtual void info(){cout<<"Child::info()"<<endl;}};int main(){Child c;Base *p = &c;p->show();return 0;}结果:Child::show()123注意:(1)当show函数不是虚继承时,输出结果为Base::show()123,因为父类的指针只能调用自己的成员,如果有虚继承,则虚表里面父类的show函数的地址会被子类的show函数地址覆盖,被覆盖的前提是:两个函数的名称和参数类型、个数和返回值类型一样 。例子2:通过指针调用虚表中的虚函数(在ubuntu下运行,虚表地址通过qt调试查看)#include <iostream>using namespace std;class Base{public:Base(){}virtual ~Base(){}protected:virtual void show(int a= 0){cout<<"Base::show()"<<endl;}};class Child:public Base{public:Child(){}~Child(){}virtual void show(){cout<<"Child::show()"<<endl;}virtual void info(){cout<<"Child::info()"<<endl;}};int main(){Child c;typedef void (*Fun)();c.show();Fun f = (Fun)(((long*)(*((long*)(&c))))[2]);f();return 0;}结果:Child::show()Base::show()十四、纯虚函数(抽象函数)、抽象类(1)纯虚函数--虚函数不需要实现直接赋值为0,纯虚函数有时称为抽象函数 。定义:
virtual void run()=0;(2)抽象类- 如果一个类中包含纯虚函数,那么这个就是抽象类,抽象类是不能创建对象 。
- 抽象类可以派生出子类,如果在子类中没有把父类中的纯虚函数全部实现,那么子类照样是抽象类 。
#include <iostream>#include <pthread.h>#include <windows.h>#include <time.h>using namespacestd;class Thread{public:Thread(){}~Thread(){}void start();virtual void run()=0;protected:pthread_t id;};void *handle(void *arg){Thread* th = (Thread*)arg;th->run();}void Thread::start(){int ret = pthread_create(&id, NULL, handle, (void*)this);if(ret < 0){cout<<"create fail"<<endl;}}//派生一个线程子类--获取系统时间class TimeThread: public Thread{public:virtual void run(){while(1){cout<<"TimeThread::run()"<<endl;Sleep(1000);time_t t;time(&t);cout<<pthread_self()<<"-----------"<<ctime(&t)<<endl;}}};int main(){TimeThread tth;tth.start();TimeThread tt;tt.start();while(1){}return 0;}十五、多态、虚析构(1)多态<1>概念C++中,多态性是指具有不同功能的函数可以用同一个函数名,这样就可以用一个函数名调用不同内容的函数 。
在面向对象方法中一般是这样表述多态性的:向不同的对象发送同一消息(调用函数),不同的对象在接收时会产生不同的行为(即方法,不同的实现,即执行不同的函数) 。可以说多态性是“一个接口,多种方法” 。
多态性分为两类:
(1)静态多态性:在程序编译时系统就能决定调用的是哪个函数,因此又称为编译时的多态性,通过函数的重载实现(运算符重载实际上也是函数重载);
(2)动态多态性:在程序运行过程中才动态地确定操作所针对的对象,又称为运行时多态性,通过虚函数实现 。
区别:函数重载是同一层次上的同名函数(首部不同,即参数个数或类型不同),虚函数是不同层次上的同名函数(首部相同) 。
<2>动态多态性和虚函数
父类引用(指针变量)指向子类对象时,调用的方法仍然是父类中的方法 。如果将父类中的该方法定义为virtual,则调用的方法就是子类中的方法了 。
说明:本来,父类指针是用来指向父类对象的,如果指向子类对象,则进行类型转换,将子类对象的指针转为父类的指针,所以父类指针指向的是子类对象中的父类部分,也就无法通过父类指针去调用子类对象中的成员函数 。但是,虚函数可以突破这一限制!如果不使用虚函数,企图通过父类指针调用子类的非虚函数是绝对不行的!
注意:父类中非虚函数被子类重写后,父类指针调用的是父类的成员函数,子类指针调用的是子类中的成员函数,这并不是多态!因为没有用到虚函数!
例如:

文章插图
#include <iostream>using namespace std;class Person{public:Person(){}~Person(){}virtual void work(){cout<<"Person::work()"<<endl;}protected:int data;};class ChildPerson: public Person{public:ChildPerson(){}~ChildPerson(){}virtual void show(){cout<<"ChlidPerson::show()"<<endl;}virtual void work(){cout<<"ChildPerson::work()"<<endl;}virtual void info(){}protected:int number;};class A: public Person{public:A(){}~A(){}virtual void show(){cout<<"A::show()"<<endl;}virtual void work(){cout<<"A::work()"<<endl;}virtual void info(){}protected:int num;};int main(){ChildPerson cp;A a;Person* p = &a;Person* pson = &cp;pson->work(); //ChildPerson::work();p->work();//A:work();return 0;}(2)虚析构多态的时候,用父类指针指向子类对象,在delete 父类指针的时候默认只会调用父类析构函数,子类析构函数没有执行(可能会导致子类的内存泄漏)--通过设置父类析构函数为虚函数类解决,执行子类析构函数后,自动执行父类析构函数 。例如:
#include<iostream>using namespace std;class Base{public: Base(){cout<<"create Base"<<endl;} virtual ~Base(){cout<<"delete Base"<<endl;}};class Der : public Base{public: Der(){cout<<"create Der"<<endl;} ~Der(){cout<<"Delete Der"<<endl;}};int main(int argc, char const* argv[]){ Base *b = new Der; delete b; return 0;}十六、友元友元:是c++里面一个特性,为了解决在函数中可以访问类的私有,或保护成员 。友元函数是可以直接访问类的私有成员的非成员函数 。它是定义在类外的普通函数,它不属于任何类 。
- 友元优点: 可以在函数中直接访问成员数据,可以适当提高程序效率
- 友元缺点:在函数类的权限失效,破坏了类的封装性
friend关键声明友元函数,或类 。
class Data{public: Data() {} void setA(int a) { this->a = a; }protected: int a;private: int b; //在Data类中声明函数fun为友元函数 friend void fun();};void fun(){ Data data; //data.a = 120; data.setA(120); data.b = 220;}友元声明只放类内部声明,可以放在类内任意位置第二种定义情况:类内定义例如:class Data{public: Data() {} void setA(int a) { this->a = a; }protected: int a;private: int b; //在Data类中声明函数fun为友元函数 friend void fun(); //声明show为友元函数,show不是成员函数 friend void show() { Data data; data.a = 130; cout<<data.a<<" "<<data.b<<endl; }};void show(); //在外面声明函数十七、友元类在一个类中的成员函数可以访问另外一个类中的所有成员比如在 A 类中的成员函数可以访问 B 类中的所有成员 。有两种方法如下:(1)在 B 类中设置 A 类为友元类 。
(2)A::fun 要访问B类中的所有成员,把A::fun函数声明为B类的友元函数 。
(1)例如:在 B 类中设置 A 类为友元类,A类成员函数可以访问B类的protected、private成员,B类不能访问A类,如果要双向访问则要在两个类中声明对方为友元类 。友元关系不能被继承 。
class B{public:B(){}friend class A;//在 B 类中声明 A 类为友元类private:int bdata;};class A{public:A(){}void showB(B &b){b.bdata = https://tazarkount.com/read/100;//在 A 类中成员函数使用 B 类的私有数据}private:int adata;};(2)A::fun 要访问B类中的所有成员,把A::fun函数声明为B类的友元函数#include <iostream>using namespace std;//前向声明----只能用于函数形参,定义指针,引用,不能使用类具体成员class B;class A{public:void fun(B& b);};class B{public:B(){}protected:int mb;private:int nb;friend void A::fun(B& b);//在B类中声明fun为友元函数};void A::fun(B& b){cout<<b.mb<<b.nb<<endl;}int main(){cout << "Hello World!" << endl;return 0;}十八、运算符重载运算符重载 关键子函数operator- 根据实际应用需求来重载运算符,重载的时候必须保持不能改变运算符本来的特性
- 只能重载c++已有的运算符,不能自己新创建运算符
- 双面运算符 (+,-,*,/,%)
- 关系运算符 (==,!=, <, >, <=, >=)
- 逻辑运算符 (||, &&, !)
- 单目运算符 (*,&,++,--)
- 位运算符 (|, &, ~,^, <<, >>)
- 赋值运算符 (=, +=, -=, .....)
- 空间申请运算符 (new , delete)
- 其他运算符 ((), ->, [])
- .(成员访问运算符)
- .*(成员指针访问运算符)
- ::(域运算符)
- sizeof(数据类型长度运算符)
- ?:(条件运算符,三目运算符)

文章插图
3、格式:
返回类型说明符 operator 运算符符号(<参数表>){函数体}4、重载方式:1.重载方式---成员函数重载
Complex C = A+B;--》A.operator+(B);规定:左值是函数调用者,右值函数的参数2.重载方式--友元重载(普通函数重载)(可以在类里面定义,也可以在类外定义类内声明)Complex C = A-B;-->operator-(A, B);规定:左值为第一个参数,右值为第二个参数1、双目运算符重载(1)+ -重载#include <iostream>using namespace std;class Complex{public:Complex(int r, int i):real(r), image(i) {}//成员重载实现重载加法+Complexoperator+ (Complex &B){cout<<"operator"<<endl;Complex C(0,0);C.real = this->real +B.real;C.image = this->image + B.image;return C;}//成员重载实现 对象加一个整型数int operator+(const int &a){this->real += a;this->image += a;return this->real;}private:int real;int image;friend Complex operator- (Complex &A, Complex &B);};//友元重载实现重载减法Complex operator- (Complex &A, Complex &B){Complex C(0,0);C.real = A.real -B.real;C.image = A.image - B.image;return C;}int main(){Complex A(2,2);Complex B(1,1);Complex C = A+B; //==>A.operator+(B)int c=A+100; //==>A.operator+(100)Complex D = A-B; //operator-(A, B)return 0;}(2)输出、输入,运算符重载#include <iostream>using namespace std;class Point{public:Point (int x=0, int y=0):x(x),y(y){}void show(){cout<<"("<<x<<","<<y<<")"<<endl;}private:int x, y;//声明友元函数friendostream& operator<<(ostream &out, Point& p);friend istream&operator>>(istream &in, Point& p);};//重载输出ostream&operator<<(ostream &out, Point& p){out<<"("<<p.x<<","<<p.y<<")"<<endl;return out;}//输入重载istream&operator>>(istream &in, Point& p){in>>p.x>>p.y;return in;}int main(){Point p(10,20);p.show();cout<<p<<endl;//==> ostream& operator<<(cout, p)Point A(0,0);cin>>A;cout<<A;return 0;}3、单目运算符重载(1)++A、A++重载成员函数重载#include <iostream>using namespace std;class Data{public:Data(int d=0):data(d) {}//重载A++Data operator++(int){Data old(*this);//保存原先的数捍this->data += 1;//对原数进行自劍return old;//返回未加之前的数捍}//重载++AData& operator++(){this->data += 1;//对原数进行自劍return *this;}private:int data;friend ostream &operator<<(ostream& out, Data &d);};ostream &operator<<(ostream& out, Data &d){out<<d.data<<endl;return out;}int main(){Data A;Data d = A++; //==>A.operator++(int)cout<<d<<A<<endl;Data &c = ++A;cout<<c<<A<<endl;return 0;}友元函数重载#include <iostream>using namespace std;class A{int data;public:A(int d = 0):data(d) {}void show(){cout << this->data << endl;}//友元函数重载++Afriend A& operator++ (A &a);//友元函数重载A++friend A operator++ (A &b,int);//友元函数重载<<friend ostream& operator<< (ostream &out,A &a);};//友元函数重载++AA& operator++ (A &a){a.data += 1;return a;}//友元函数重载A++A operator++ (A &b,int){A old(b);b.data += 1;return old;}//友元函数重载<< ostream& operator<< (ostream &out,A &a) {out << a.data;returnout; }int main(int argc,char **argv){A a(5);A b = ++a;cout << a << " " << b << endl;A c(5);A d = c++;cout << c << " " << d << endl;return 0;}(2)重载中括号[ ]#include <iostream>using namespace std;class Array{public:Array(int n):length(n) {this->ptr = new int[this->length];}~Array(){delete []this->ptr;}//拷贝构造函数---深拷贝(类的成员有指针指向堆空间)Array(Array& array){this->length = array.length;this->ptr = new int[this->length];memcpy(this->ptr, array.ptr , this->length);}//重载[]int& operator[](int i){cout<<i<<endl;return this->ptr[i];//返回第i个对象}private:int length;int *ptr;};int main(){Array mArr(10);mArr[0] = 100;//return 0;}4、用运算符重载实现数据类型转换(1)转换构造函数转换构造函数的作用:是将一个其他类型的数据转换成一个类的对象? 当一个构造函数只有一个参数,而且该参数又不是本类的const引用时,这种构造函数称为转换构造函数 。转换构造函数是对构造函数的重载 。例如:
#include <iostream>using namespace std;class Complex{public:Complex():real(0),imag(0){cout << "test1\n";}Complex(double r, double i):real(r),imag(i){cout << "test2\n";}// 定义转换构造函数Complex(double r):real(r),imag(0){cout << "test3\n";}/*// 拷贝构造函数Complex(Complex &a){cout << "test4\n";}//当此函数存在时,Complex c = 1;Complex c2 = c1 + 3.1;编译时都会报错*/void Print(){cout<<"real = " << real <<" image = "<<imag<<endl;}Complex operator+(Complex c){Complex ret(this->real + c.real, this->imag + c.imag);returnret;}private:double real;double imag;};int main(){Complex c;c = 4;// 调用转换构造函数将1.2转换为Complex类型,此时会调用转换构造函数c.Print();Complex c1(2.9, 4.2);Complex c2 = c1 + 3.1; // 调用转换构造函数将3.1转换为Complex类型c2.Print();return 0;}输出结果:test1test3real = 4 image = 0test2test3test2real = 6 image = 4.2注意:- 1、用转换构造函数可以将一个指定类型的数据转换为类的对象?但是不能反过来将一个类的对象转换为一个其他类型的数据(例如将一个Complex类对象转换成double类型数据)?
- 2、如果不想让转换构造函数生效,也就是拒绝其它类型通过转换构造函数转换为本类型,可以在转换构造函数前面加上explicit 。
类型转换函数的作用是将一个类的对象转换成另一类型的数据?
#include <iostream>using namespace std;class Person{public:Person(int age=0):age(age) {}operator int() //通过运算符重载来实现数据类型转换{cout<<"int"<<endl;return age;}operator long() //通过运算符重载来实现数据类型转换{cout<<"long"<<endl;return num;}int getAge(){return age;}private:int age;long num;};int main(){Person Jack(19);int age = Jack;int a = Jack.getAge();long b = Jack;return 0;}注意:- 1、在函数名前面不能指定函数类型,函数没有参数?
- 2、其返回值的类型是由函数名中指定的类型名来确定的?
- 3、类型转换函数只能作为成员函数,因为转换的主体是本类的对象,不能作为友元函数或普通函数?
- 4、从函数形式可以看到,它与运算符重载函数相似,都是用关键字operator开头,只是被重载的是类型名?double类型经过重载后,除了原有的含义外,还获得新的含义(将一个Complex类对象转换为double类型数据,并指定了转换方法)?这样,编译系统不仅能识别原有的double型数据,而且还会把Complex类对象作为double型数据处理?
2、格式:
template <typename T> //T为类型名数据类型 函数名(参数列表){函数体}3、注意:- (1)在编译时,根据变量生成实例 。
- (2)template T只对其下面的函数模板有效 。如果要定义第二个模板函数时,则要再写template 。
- (3)typename也可以用class 。
- (4)T名字可以随便取 。
- (5)当参数不一样时,可以这样定义参数列表template <class T,class Tp>
- (6)参数列表可以带默认类型,template <class T,class Tp = int> 。
- (7)模板函数只有在使用(不是运行)的时候才会检测错误 。
【【C++】 C++知识点总结】
//设计一个模板函数实现两个对象交换template <typename T>void mswap(T &a, T &b){ T c = a; a = b; b = c;}int main(){ int a = 10; int b = 20; cout<<a<<" "<<b<<endl; mswap(a, b); cout<<a<<" "<<b<<endl; return 0;例子2://错误,因为不能够确定返回值的类型template <class T, class Tp>Tp fun(T &a){return a;}//修改,但返回值定死了template <class T, class Tp = int>Tp fun(T &a){return a;}//调用函数时指定类型template <class T, class Tp = int>Tp fun(T &a){return a;}int main(void){int a = 2;double ret = fun<int, double>(a)}4、模板函数与函数普通同时存在该如何调用template <typename T>void mswap(T &a, T &b){ cout<<"template"<<endl; T c = a; a = b; b = c;}//普通函数void mswap(int &a, int &b){ cout<<"std"<<endl; int c = a; a = b; b = c;}调用(1) int a = 10; int b = 20; cout<<a<<" "<<b<<endl; mswap(a, b);//---普通函数 cout<<a<<" "<<b<<endl;调用(2)double a = 10; double b = 20; cout<<a<<" "<<b<<endl; mswap(a, b);//---模板函数 cout<<a<<" "<<b<<endl;如果模板函数和普通函数同时存在,调用的时候会根据参数选择最优函数二十、模板类1、模板类的定义//设计一个模板类 -模板类的类名 A<T>//template< class T , class Ty> //A<T, Ty>template< class T > //A<T>class A{public: A() {}protected: T dataA;};int main(){ A<int> a;//定义模板类对象 return 0;}注意:- (1)如果是浮点型或者其他普通类型,是指针或者是引用 template <double &N,class T=int>
class array{...}
定义对象: array<N,int> a ;这里的 N 必须是全局变量 。 - (2)参数列表可以带默认类型,template <class T,class Tp = int> , 如果是默认类型,与函数的默认参数类似,必须是如果从那个一个开始默认,那么后面的所有模板类型多必须有默认类型 。
- (3)如果使用数值为整型( char,short,int,long) 时候 。template <int N,class T=int> class array{...},这里的N只能是常量不能是变量,例如 array<10,int> a或者const int a = 5;array<a,int> 。
#include <iostream>using namespace std;template< class T >class MVector{public:MVector(){this->size = 1024;this->count = 0;this->ptr = new T[this->size];}~MVector(){delete []this->ptr;}//拷贝构造函数MVector(MVector& mv){this->size = mv.size;this->ptr = new T[this->size];memcpy(this->ptr, mv.ptr, this->size*sizeof(T));}//添加数据void append(const T &data){this->ptr[this->count] = data;this->count++;}//重载<<追加数据void operator<<(int data){this->ptr[this->count] = data;this->count++;}#if 1//在类里面定义重载函数//声明友元重载输出<<friend ostream& operator<<(ostream& out, MVector &mv){for(int i=0; i<mv.count; i++){out<<mv.ptr[i]<<" ";}out<<endl;return out;}#endif//template<class Ty>//friend ostream& operator<<(ostream& out, MVector<Ty> &mv);protected:int count;int size;T* ptr;};#if 0 //在类内声明,在类外定义重载函数//重载输出<<运算符template< class Ty >ostream& operator<<(ostream& out, MVector<Ty> &mv){for(int i=0; i<mv.count; i++){out<<mv.ptr[i]<<" ";}out<<endl;return out;}#endif//模板函数在使用(不是运行)该函数的时候才会检查语法int main(){MVector<int> mvs;mvs.append(100);mvs<<200;cout<<mvs;return 0;}3、模板类继承如果在派生子类的时候父类类没有确定class B: public A,那么子类也是模板类 。例如:
#include <iostream>using namespace std;//设计一个模板类A<T>template< class T >class A{public:A(T a) {}protected:T data;};//设计一个子类B 继承模板类A<T> --B类也是模板类template< class T >class B: public A<T>{public:B(T a):A<T>(a){}protected:int datab;};//设计一个子类C 继承模板类A<int> --C类就是一个具体类class C: public A<int>{public:C():A<int>(10){}};int main(){A<char> a(10); //模板类创建对象B<string> b("hello"); //模板类子类创建对象C c;return 0;}4、模板类中的静态成员编译时根据模板生成的不同类的静态成员是不同内存空间的;在同一个类中创建的对象的静态成员是共用一个内存空间的 。如下:
#include <iostream>using namespace std;template<class T>class Data{public:Data() {}void show(T msg){data = https://tazarkount.com/read/msg;cout<T Data::data ;int main(){//创建一个对象Data mydata;//编译的时候会生成一个 T为int的类mydata.show(100);Data::data ="hello"; //编译的时候会生成一个T 为string的类cout<<Data<string>::data<<endl;Data<string> mystr;cout<<mystr.data<<endl;return 0;} 二十一、强制类型转换const_cast、static_cast、reinterpert_cast、dynamic_cast注意:以上,如果转换失败的时候会返回空1、const_cast把常量转为变量
#include <iostream>using namespace std;int main(){const int a = 10;const int *p = &a;int *ptr = (int*)(&a);//c语言转换(在c语言可以这样写:int *ptr=&a,只是会警告,一样可以操作,c++不允许)*ptr = 1;cout<<a<<endl;cout<<*ptr<<endl;int &ra = const_cast<int&>(a);ra = 2;cout<<a<<endl;cout<<ra<<endl;int *x = const_cast<int*>(p);*x = 3;cout<<a<<endl;cout<<*x<<endl;return 0;}输出结果:101102103解释:因为a是const修饰的,此时a的值会存在符号表中,也就是改变a地址所指向的值,也不会改变a的值,当调用a的时候,编译器回到符号表中取值,而不是从a的地址取值 。(1)为何要去除const限定原因(1)是,我们可能调用了一个参数不是const的函数,而我们要传进去的实际参数确实const的,但是我们知道这个函数是不会对参数做修改的 。于是我们就需要使用const_cast去除const限定,以便函数能够接受这个实际参数 。
例如:
#include <iostream>using namespace std;void Printer (int* val,string seperator = "\n"){cout << val<< seperator;}int main(void) {const int consatant = 20;//Printer(consatant);//Error: invalid conversion from 'int' to 'int*'Printer(const_cast<int *>(&consatant));return 0;}原因(2):还有一种我能想到的原因,是出现在const对象想调用自身的非const方法的时候,因为在类定义中,const也可以作为函数重载的一个标示符 。
2、static_cast静态转化static_cast < type-id > ( expression )该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性 。它主要有如下几种用法:
- ①用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换,不允许不相关的类进行转换 。
进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的 。 - ②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum 。这种转换的安全性也要开发人员来保证 。
- ③把空指针转换成目标类型的空指针 。
- ④把任何类型的表达式转换成void类型 。
例如
#include <iostream>using namespace std;int main(){char a = 'a';int b = (int)a;double g = static_cast<int>(a);//为什么不能转换普通类型指针,却能转换对象指针和void指针(规定的)void *pp;double *pp1 = static_cast <double*>(pp);int *xx;void *xx1 = static_cast <void*>(xx);//double *xx2 = static_cast <double*>(xx);//错误写法return 0;}3、reinterpret_cast强制类型转换符reinterpret_cast (expression)type-id 必须是一个指针、引用、算术类型、函数指针或者成员指针 。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,再把该整数转换成原类型的指针,还可以得到原先的指针值) 。
- reinterpret_cast可以转换任意一个32bit整数,包括所有的指针和整数 。可以把任何整数转成指针,也可以把任何指针转成整数,以及把指针转化为任意类型的指针 。但不能将非32bit的实例转成指针 。总之,只要是32bit的东东,怎么转都行!
- 因为任何指针可以被转换到void
- 春季老年人吃什么养肝?土豆、米饭换着吃
- 三八妇女节节日祝福分享 三八妇女节节日语录
- 老人谨慎!选好你的“第三只脚”
- 校方进行了深刻的反思 青岛一大学生坠亡校方整改校规
- 脸皮厚的人长寿!有这特征的老人最长寿
- 长寿秘诀:记住这10大妙招 100%增寿
- 春季老年人心血管病高发 3条保命要诀
- 眼睛花不花要看四十八 老年人怎样延缓老花眼
- 香槟然能防治老年痴呆症? 一天三杯它人到90不痴呆
- 老人手抖的原因 为什么老人手会抖
