#include "iostream" //包含输入输出库的头文件
using namespace std;
//使用c++标准命名空间 里面的定义,c++中很多标识符都定义在里面
// 说白了就是一个作用域(标识符的作用范围),不写该行,用对象时得标明是哪里的,如:std::cout<<endl;
// 定义一个类,其实就是定义一个数据类型
// 在c++里面struct 是跟class关键字 一样的,只不过struct定义的属性、方法默认是public的,class默认是private的
// 空类占1字节(占位符,标明存在这个类,有函数、无变量也是1字节),只要有成员变量(非静态的),比如
// 下面的Circle类,占16字节,占位符的字节不计了
// 所有的类对象有自己的独立的成员变量空间(sizeof能得到的空间),但函数是共享代码区的同一份
struct Circle
{
double r;
double area = r*r*3.14; // 默认值
void setR(double n)
{
r = n;
}
double getR()
{
return r;
}
};
void main()
{
// 把字符串左移到 cout对象里面去(c++对左移运算符进行了重载)
// cout 是标准的输入输出终端-屏幕
// << 操作之后 返回的还是cout对象,可以继续 << ,
// 从最左边开始执行的
cout << "呵呵" << endl;
int a;
// 从屏幕输入一个数
cin >> a;
// 定义一个变量,只是分配内存,不会调用里面的方法
// 不用加struct了
Circle c1;
cin >> c1.r;
cout << c1.area;
/*
这里试图通过输入半径,获得园的面积,
但是从内存四区的角度分析,在定义c1变量时,r分配了空间(没有初始化,r的值数随机的,分配变量空间了再执行构造函数)
(也就是说定义c1就开辟空间了,构造函数只是去初始化的)
area也分配了空间,并执行了r*r*3.14;表达式,给这个空间赋了值,由于r是随机的不确定数,导致area也不确定
接下来输入r的值,这时r的那片空间被赋值了
再接下来,读取area空间的值,但是area的值在定义c1变量时就确定了(执行了表达式),给r赋值时,并没有改变area的值
只是去获取area那片空间的值,不会执行r*r*3.14;表达式,所以获取的值是定义c1的时候的值
总结:用成员方法计算面积
*/
printf("哈哈!");
}
#命名空间
所谓namespace,是指标识符的各种可见范围。C++标准程序库中的所有标识符都被定义于一个名为std的namespace中
<iostream>和<iostream.h>,在编译器include文件夹可以看到,二者是两个文件,里面的代码不一样
.h的头文件c++标准已经明确提出不支持了(早期为了让C支持面向对象编程,对C的改造->C++ 用的头文件是有.h的,是之后去掉的,文件没有后缀了)
c++标准为了和C区别开,规定头文件不使用后缀.h
1)当使用<iostream.h>时,相当于在c中调用库函数,使用的是全局命名空间,也就是早期的c++实现;
2)当使用<iostream>的时候,该头文件没有定义全局命名空间,必须使用namespace std;这样才能正确使用cout
(即iostream头文件里面没有申明using namespace std,所以在调用时,自己的程序里面要申明那句话)
避免头文件被重复包含,在第一行加上
#pragma once,相当于C的#idndef....
.hpp 与 .h 是一样的,都是头文件
(一般用于C++,或者函数申明与函数实现写在一起的头文件)
由于namespace的概念,使用C++标准程序库的任何标识符时,可以有三种选择:
1、直接指定标识符,std::cout << std::hex << 3.4 << std::endl;
2、使用using关键字。 using std::cout; using std::endl; using std::cin; 以上程序可以写成 cout << std::hex << 3.4 << endl;
3、最方便的就是使用using namespace std,cout << 3.4 << endl;
/*
C中的命名空间
在C语言中只有一个全局作用域
C语言中所有的全局标识符共享同一个作用域
标识符之间可能发生冲突
C++中提出了命名空间的概念
命名空间将全局作用域分成不同的部分
不同命名空间中的标识符可以同名而不会发生冲突
命名空间可以相互嵌套
全局作用域也叫默认命名空间
*/
C++命名空间的定义:
namespace name { … }
C++命名空间的使用:
使用整个命名空间:using namespace name;
使用命名空间中的变量:using name::variable;
使用默认命名空间中的变量:::variable
默认情况下可以直接使用默认命名空间中的所有标识符(即没有定义命名空间,直接就定义变量等等)
不加命名空间限定,函数、变量是全局的(与C类似)
namespace NameSpaceA
{
int a = 0;
}
namespace NameSpaceA // 定义同名的命名空间,就是扩展命名空间,添加新的内容(原先的东西还有),不能出现相同的变量等
{ // 因为扩展特性,命名空间用的最多就是迭代开发
int b = 0;
}
namespace nn = NameSpaceA; // 简写命名空间,取别名
namespace NameSpaceB
{
int a = 1;
namespace NameSpaceC // 嵌套也是没问题的,不能出现相同的变量等
{
struct Teacher
{
char name[10];
int age;
};
}
}
无名命名空间,外界直接调用,不用加命名空间
namespace
{
int xzc = 0;
}
int main()
{
cout<<xzc<<endl; // 就是引用上面的无名命名空间
using namespace NameSpaceA; // 申明默认的命名空间,默认就使用它里面的标识符,
// 申明之后以下的代码有效,同变量的作用域,只在{}内有效,出了{}申明无效了
// (同时要先定义命名空间)
// 同时使用多个命名空间时,会有冲突,要加空间名指明
using NameSpaceB::NameSpaceC::Teacher; //::域作用符
printf("a = %d\n", a);
printf("a = %d\n", NameSpaceB::a);
Teacher t1 = {"aaa", 3};
printf("t1.name = %s\n", t1.name);
printf("t1.age = %d\n", t1.age);
system("pause");
return 0;
}
#变量定义增强
C++定义变量不再局限于在作用域的开始定义,其他地方也可以了
{
int a;
printf("");
int b; // 这样也可以了,C中不行,只能定义在开始
int *p(new int);// C++的初始化方式->int *p=new int;
int n(6); // 就相当于int n =6;初始化,C++的初始化方式
char *str("xzc"); // 相当于 char *str="xzc";
}
#register增强
C++编译器有自己的优化方式,不使用register也可能做优化(使用次数多了,自动优化)
C++中可以取得register变量的地址,C++编译器发现程序中需要取register变量的地址时,register对变量的声明变得无效
早期C语言编译器不会对代码进行优化,因此用register关键字
#全局变量增强
在C语言中,重复定义多个同名的全局变量是合法的
在C++中,不允许定义多个同名的全局变量
C语言中多个同名的全局变量最终会被链接到全局数据区的同一个地址空间上
int g_var;
int g_var = 1;
C++直接拒绝这种二义性的做法。
必须extern 避免二义性,申明引用全局变量,
只是申明不会开启空间,所以这里不能赋值 (extern int a=9;)
extern int a;
#struct加强
C语言的struct定义了一组变量的集合(不能定义方法),C编译器并不认为这是一种新的类型,定义变量时要加struct关键字
C++中的struct是一个新类型的定义声明,定义变量时不用加struct关键字
struct Student
{
char name[100];
int age;
};
int main(int argc, char *argv[])
{
Student s1 = {"wang", 1};
Student s2 = {"wang2", 2};
return 0;
}
#变量和函数都必须有类型
/*
C++中所有的变量和函数都必须有类型
C语言中的默认类型在C++中是不合法的
函数f的返回值是什么类型,参数又是什么类型?
函数g可以接受多少个参数?
*/
f(i)
{
printf("i = %d\n", i);
}
g()
{
return 5;
}
int main(int argc, char *argv[])
{
f(10);
printf("g() = %d\n", g(1, 2, 3, 4, 5));// 任意多个参数,任意类型,返回值也任意
getchar();
return 0;
}
/*
在C语言中
int f( );表示返回值为int,接受任意参数(个数、类型任意)的函数
int f(void);表示返回值为int的无参函数
在C++中
int f( );和int f(void)具有相同的意义,都表示返回值为int的无参函数
*/
C++更加强调类型,任意的程序元素都必须显示指明类型
#枚举enum
enum color:char{red,yellow}; // char是指定存储类型为char
enum color:char{red='A',yellow}; // red的值就是一个字符A
color clr = red;
clr = 'A'; // C++类型严谨了,不允许这样赋值
clr = 0; // C++类型严谨了,不允许这样赋值
clr = color::red;
color cl(red);
char a=-200;
unsigned int b = a;
// 运算提升问题
// 有的编译器是将一个字节的-200(二进制11001000),会提示至4字节
// 由于最高位是1,表示负数,所有提升时,前面填充1,填满4字节(正数提升时填充0),
// 这时是int类型,然后把int作为unsigned int,提升了两次
// 有的编译器,不把一个字节的-200(二进制11001000)看成负数,所以填充的是0,提升后还是相等的
if(a==b)
{
printf("a==b\n");
}
else
{
printf("a!=b\n");
}
#新增bool类型
占1字节,值为true/false(1/0)
int n=4;
bool b=true;
b=n; // 将非0值给bool变量,结果b都是1,将0赋值给b还是0
b= false;
n = b;// 将bool变量赋值给int变量,真是1,假是0
#新增类型转换方式
rtti:实时类型检测
// <>内表示要转换为 什么类型,()内表示要转换的数
// 本质是一个函数
// 静态转换,即转换不成功,程序编译不过去
// 用于void * 与 其他类的指针转换,否则转换失败
int n = static_cast<int>(9.2);
//const int num = 10,可以修改无法生效,编译的时候不读内存
//const int *p指向变量限定权限,只读不可写,
//const_cast 去掉常量指针const属性,可以修改数据了,const 与 非const数据类型之间的转换
int num =9;
const int *p = num;
int *pnew = const_cast<int *>(p); // 去掉p的const属性
*pnew=10;
int num = 3;
char *p = reinterpret_cast<char *>(&num);
// 专业转换指针,最安全,不能转化,转化失败,指针赋值为NULL
// 重解释转换,跟原生强转一样的
//dynamic_cast 类的指针之间的转换
//动态转换,即运行时检测,如果转换不成功就报错
//(只能父类与子类,否则转换不成功,且父类中一定要有虚函数,同一父类的两子类也不行,必须是父子之间)
#三目运算符增强
int a = 10;
int b = 20;
//返回一个最小数 并且给最小数赋值成30
//三目运算符是一个表达式 ,表达式不可能做左值(因为运算结果在寄存器中)
//(能当左值的条件是:必须存在内存空间,有地址可用来赋值)
// (a < b ? a : b )是一个表达式 表达式的运算结果在寄存器中,不能获取寄存器的地址,也就不能赋值,不能往空间写数据
// 字面量,或在寄存器中,不能赋值
// 引用可以当左值,并不是可以在左边的就是左值,是可以赋值、取地址的才是左值
//原来的C中 返回的不是a本身(是内存地址),但是c++中,对三目运算符进行功能增强
//c++返回的是a本身(引用),于是可以做左值,可以赋值了
(a < b ? a : b ) = 30;
*(a < b ? &a : &b ) = 30;// 本质还是这样的,在C中就得这样赋值
C++检测到右值在内存有实体,自动转换为左值,因此可以赋值了
1)C语言返回变量的值,不能作为左值使用
2)C++中的三目运算符可直接返回变量本身,因此可以出现在程序的任何地方
注意:
3)三目运算符可能返回的值中如果有一个是常量值,则不能作为左值使用
(a < b ? 1 : b )= 30;
int fun(){} // 函数返回值一样的道理,函数返回地址只读不写,返回变量的引用、指针才能赋值
(++i)++,在C中不行,在C++中可以,
类似的运算结果一大把
#nullptr
C++的空指针,NULL是个宏(0,容易产生歧义)
void *p = nullptr; // nullptr是一个空指针,是指针类型
#const增强
const修饰的变量必须在定义时就初始化
于是
const int a=9;
int arr[a]; // 在C++中可以(编译期间就可以知道a的值了,编译时会直接替换对应的值,相当于宏定义),
// 在C中不行(不能确定数组大小)
C语言中const变量,有自己的存储空间,虽然不可以直接修改值,但可以通过指针修改
C++中的const常量
可能分配存储空间,也可能不分配存储空间
当const常量为全局,并且需要在其它文件中使用,分配一个内存空间
当使用 extern、&操作符 取const常量的地址时,分配一个内存空间
C++常量会放入一个 符号表 中(以键值对的形式,变量名是键),在寄存器中生成,不在内存中
编译过程中若发现使用常量则直接以符号表中的值替换
编译过程中若发现对const使用了extern或者&操作符,则给对应的常量分配存储空间(兼容C)
int main()
{
const int a = 10;
int *p = (int*)&a; // 这里取常量的地址,其实是另外开辟了一个空间,并赋值为a的值,返回那个空间的地址给指针变量
*p = 11;
// 这时a的值还是10,*p的值是11,因为两者(a 与 *p)在不同的内存空间了
// 这样就不会改变a的值了
return 0;
}
联想 const和#define的区别
对比加深
C++中的const常量类似于宏定义
const int c = 5; ≈ #define c 5
C++中的const常量在与宏定义不同
const常量是由编译器处理的,提供类型检查和作用域检查
宏定义由预处理器处理,单纯的文本替换
void fun1()
{
#define c 20
#define a 10 // 从这行开始,之后所有行的代码都可以用该宏(不用管作用域)
const int b = 20;
//#undef // 取消前面 #define 定义的所有宏,即该行之后 a,c 这个宏不再有效
//#undef a // 取消指定的某一个宏
}
void fun2()
{
printf("a = %d\n", a);
//printf("b = %d\n", b); // b常量使用作用域的,在这里用引用不到了
}
#引用,C++的概念
int a=9; //变量名的本质就是内存空间的别名
int n=90;
int &b=a; // 定义引用b,是a的别名,定义时就必须指定是谁的别名,一旦初始化就不能修改(不能是其他变量的别名了)
b=n; //只是给b的空间赋值,不是给n取别名了(只能在初始化时指定谁的别名,之后不能修改了)
// &a,&b 取地址,两者是一样的,
// 这里相当于给那块内存空间再取一个别名b(那块空间有两个名字了a、b),与a的含义完全一样,一样使用
int& c=a; // 这样也行
b = 100; // a、b、c的值都是100了
1) 引用是一个有地址,引用是常量,引用在C++中的内部实现是一个常指针,就是int *const p;
2)C++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同。
3)从使用的角度,引用会让人误会其只是一个别名,没有自己的存储空间。这是C++为了实用性而做出的细节隐藏
int &b=a; // C++编译器发现左值是引用&,于是C++编译器内部做了取址操作(&a),把a的地址给b
int &b(a); // 初始化
int m;
b = 100; // 使用b时,由于b是引用类型,C++编译器其实做了取值操作(*a)
b=m; // 这是赋值,不是取别名了,只能在定义时,取别名,之后就是使用了
int c = b;// 取值,使用时,C++编译器其实做了取值操作(*a),
// b 本身对地址有取值操作,&b取值有取址,就是原来地址(指针变量保留的指针),但不能&&b(不能取引用的地址)
int *p = b; // 由于做了取值操作,这样赋值过去是整数,不是地址
int *p = &b; // 取地址赋值过去,或者强转整数为地址
int &t=b; // 引用给引用赋值,a有多了一个别名t
// 同样的,a作实参 传进函数时,C++编译器发现fun的形参是引用,于是取a的地址传给n(形参也开辟了空间)
// 使用时,做了取值操作
void fun(int &n)
{
n=100;
}
int g1(int *p)
{
*p = 100;
return *p;
}
int& g2(int *p) //
{
*p = 100;
return *p; // C++编译器会自己做了取址操作,不用操心,不要自己取址
}
两个g2()一样的效果
int& g2(int &p) //
{
p = 100;
return p; // C++编译器会自己做了取址操作,不用操心,不要自己取址
}
int& g3() //
{
int n;
n = 100;
return n;
}
void main()
{
int a=90;
fun(a); // 这时a为100了
int &b =g2(&a); // 引用当左值时,直接将变量的值赋值过去就行了,不要取变量的地址(C++编译器会自己做,不用操心)
// 这是安全的,g2()里面的变量没被回收
// 给a取了个别名b
int &c=g3(); // 这样是不安全的,c还是一个引用,C++编译器不会做取值操作,是将地址赋值给c
// g3方法里面的变量n被回收了
int d = g3(); // 这样是安全的,虽然g3()返回值为引用,但赋值给的左值是一个int变量,C++编译器对返回值的地址做了取值操作,
// 不是定义应用,不是给返回的地址取别名了
// d 为100;
}
总之,关于引用的使用,记得有自动取值、取址操作
引用可当左值
//static修饰变量,在全局区,返回它的地址,可被修改
int j() // 返回的是值
{
static int a = 10;
a ++;
printf("a:%d \n", a);
return a;
}
int& j1()// 返回变量本身
{
static int a = 10;
a ++;
printf("a:%d \n", a);
return a;
}
int *j2()
{
static int a = 10;
a ++;
printf("a:%d \n", a);
return &a;
}
void main()
{
// j()的运算结果是一个数值,没有内存地址,不能当左值,不能j()=100;
//11 = 100;
//返回一个引用时,可以赋值了
j1() = 100;
*(j2()) = 200; //内部本质还是这样的
}
#引用 的sizeof
char &a;
sizeof(a); // 为1
class Stu
{
char &a;
char &b;
}
sizeof(Stu); // 为8,引用本质是指针,直接sizeof是被引用类型的大小,在类里面则是指针的大小
#指针引用
int a = 9;
int *p;
int* &n = p; // 不能 int* &n = &a,不能直接取地址赋给指针引用,是一个变量的别名(变量有内存地址)
// 即引用左值,(给可以做左值的变量取别名)
&:左值引用
&&:右值引用
int* &&n = &a // 右值引用,&a的结果在寄存器内,右值引用是用于操作cpu的,改变寄存器对应地址的值
n = &a; // 这里是改变了指针p的指向
int b =*n; // 理解为n是一级指针p的别名即可
// 说白了就是一个二级指针,只是自动做了取值、取址操作
int fun(int && n)
{
}
int fun2(int & n)
{
}
int a;
cout<<fun(a+1)<<endl;// a+1的结果在寄存器中,fun的参数是右值引用,可直接用寄存器的数据,不用为参数开辟内存空间了
// fun2(a+1),不行,左右引用不能取寄存器数据
// fun(a),不行了,右值引用必须传入寄存器的数据
fun(std::move(a)); // 将左值转换为右值,命名空间std的方法
左值右值转换
int a=9;
int b= a+1; // 右值->左值,即将计算结果赋值给一个变量
---------------
int add(int a,int b){}
int sub(int a,int b){}
int(*p)(int a,int b)(add); // 函数指针并初始化
int(*&rp)(int a,int b)(p); // 引用函数指针,不能直接赋值add,变量也不行int* &n = &a
int(* const &rp)(int a,int b)(p); // 函数指针 的 常引用
rp = sub; // 试图修改常量引用,错误,改变指针指向,不能rp(sub); , ()只用于初始化,
void fun( int(* & rp)(int,int) ) // 函数指针引用 做参数
{
}
// 返回 函数指针引用
int(*& )(int,int)
int(*& fun(int a) )(int,int) // fun函数的返回值为一个 函数指针引用 int(*&)(int,int) 类型
// 返回值为函数指针,将函数都包裹在内了
----------------
struct Teacher
{
char name[64];
int age;
};
int getTe(Teacher **myp )
{
Teacher *p = (Teacher *)malloc(sizeof(Teacher));
if (p ==NULL)
{
return -1;
}
memset(p, 0, sizeof(Teacher));
p->age = 33;
*myp = p; //
return 0;
}
//指针的引用,说白了myp就是一个二级指针,只是自动做了取值、取址操作
// myp 是 Teacher* 类型 的一个引用
int getTe2(Teacher* &myp)
{
myp = (Teacher *)malloc(sizeof(Teacher));
myp->age = 34;
return 0;
}
void main333()
{
Teacher *p = NULL;
//getTe(&p);
getTe2(p);
printf("age:%d \n", p->age);
system("pause");
}
#常引用
int a=9;
const int &n=a; // 常引用,不可以通过n修改a的值了,相当于const int *const p
// n=10; // 错误
int *p=&a;
*p=9;
a=8; // 但可以通过指针,变量本身直接修改
char str[10]="sadaf";
const char(& rr)[10](str); // 字符串引用,
当使用常量(字面量)对const引用进行初始化时,C++编译器会为常量值分配空间,并将引用名作为这段空间的别名
const int a=9;
const int &n=19;// 就相当于int常量,放入了 符号表,但由于C++编译器,内部有取址操作
// 于是开辟了空间,n就相当于那个空间的别名,指向那个空间
int &n=19; // 这样不行(不能给一个字面量取别名,没有内存空间),出错的,要加const
#引用数组
int a[10];
int(&ra)[10](a); // 建立引用数组,并用a数组初始化
int b[2][5];
int (&ra)[2][5](b); // 二维
for(auto i:ra) // 遍历数组
{
cout<<i<<endl;
}
#数组引用
int &w[2]; // 不存在这种语法
#内联函数
inline int myfunc(int a, int b)
{
return a < b ? a : b;
}// 必须这样,定义函数时,一定要写函数体,且与inline一起,否则inline无效
// 即 inline必须 与函数实现的地方,写在一起
// 函数模板也支持
void main()
{
int a=1;
int b=2;
int c=myfunc(a,b);
}
内联函数类似于宏,在函数调用处,C++编译器直接将函数体(编译后)插入函数调用处
(避免了函数入栈、出栈、返回的开销)
C++编译器可以将函数进行内联编译,被内联编译的函数 叫内联函数
C++编译器不一定准许内联请求,
内联函数由编译器处理,支架将编译后的函数体插入调用处
宏代码块(带参数的宏)由预处理器处理,只是文本替换,没有任何编译过程
C++编译器能够进行编译优化,即使没有inline申明,也可能被内联编译(默认内联/隐式内联)
能够函数进行强制内联(gcc/g++ 3.4.x版本)
如:g++的__attribute__((always_inline)),优化时无法识别inline函数中的ASM汇编,汇编语句块可能被丢弃;
__attribute__((noinline)),如有汇编,强制不内联,但是有的gcc可能会忽略 noinline
内联条件:
不能存在任何形式的循环语句
不能存在过多的条件判断
不能包含switch、goto语句
不能包含数组
不能包含静态数据
不能有递归
函数体不能太庞大
不能对函数进行取址操作
函数内联申明必须在调用之前
这些条件不是绝对的,当函数体执行开销 大于 函数入栈出栈时,内联将无意义
#默认参数
函数申明时,可为参数提供一个默认值,当函数调用时,如不写参数,编译器自动用默认值代替
C语言是不行的
void fun(int a=9)
{
printf("%d\n",a);
}
void main()
{
fun();// 将输出9
}
默认参数必须写在后面
void fun(int a,int b=9,int c=8){}// b之后的参数,都必须有默认值(即必须连续,不可插杂非默认参数)
void main()
{
fun(1);// 后两参数可不写
// 用函数指针调用时,没有默认参数,所有参数必须都写
}
#函数占位参数
C语言不行
不管是声明还是实现函数,参数名不用可以不写(这叫哑元)
void fun(int a,int)// 第二个参数没有名字,在函数内不能使用,只是占个位置(兼容旧系统,以后扩展)
{
printf("%d\n",a);
}
void main()
{
fun(1,3);// 但调用时,必须写第二个参数
}
#默认参数与占位参数结合
兼容C的一些不规范写法
为以后扩展
void fun(int a,int =8)
{
printf("%d\n",a);
}
void main()
{
fun(1,3);
fun(1);// 第二参数可要可不要
}
#函数重载
C语言没有重载,完全根据函数名确定一个函数
C++的,函数名+参数 -> 确定一个函数(编译后函数名变了,包含参数信息),方便重载
函数重载 与 默认参数 存在二义性,编程时得注意(可以通过函数指针区分)
函数重载 与 函数指针:
int func(int x) // int(int a)
{
return x;
}
int func(int a, int b)
{
return a + b;
}
int func(const char* s)
{
return strlen(s);
}
typedef int(*PFUNC)(int a); // int(int a)
typedef int(*PFUNC2)(const char *p); // int(const char *a)
// 定义指向那个函数的指针,函数参数必须一样,const都得一样
int main()
{
int c = 0;
PFUNC p = func;
c = p(1);
PFUNC2 myp2 = func; // 赋值的都是func,但具体调用那个函数是根据指针类型决定的
myp2("aaaa");
return 0;
}
#类的封装
封装:突破了C中函数的概念
继承:代码复用
多态:同样的调用语句,给它赋的对象不同,有不同的表现
可以使用未来的代码,是一个框架能支持后人的代码,解耦和,重点
设计模式的基础
原理就是函数指针做函数参数
面向过程编程,加工的是函数
面向对象编程,加工的是类
可以在类中定义一个已经定义(占用空间大小确定了)的类
如果一个类变量/函数与全局变量重名了,在类内默认使用的是类的变量
(::age=10,加::,则表示使用全局的变量,不用类内的、不用函数内的等等)
私有变量通过函数返回,如果不想被修改(返回指针时,返回const指针)
(非要强转为非const指针去修改,可以修改,其实是开辟了另外一个空间了)
class A // struct(cpp中的struct)也可以定义类,只不过默认是公有权限,一样有三种权限,可以有函数
{
int a;
void fun(){}
public: // 默认权限是私有,直到遇到下一个权限符
int b;
void fun1(){}
protected:
int c;
public: // 权限可以多次出现
int d;
}
void main()
{
A c;
}
类保存在代码区,对象保存在栈、堆中
#.h 与 .cpp分离
.h内写类的申明,
.cpp内写类、方法的实现,静态函数,在cpp中不要再加static
右击源文件-添加-类:VS会自动生成.cpp、.h文件,也可以自己手动建.h、.cpp文件
.h中
#pragma once
#include "Point.h" // 类申明中,用到的其他类包含进来即可
/*
// 申明要是用的类,申明一下有这个类,具体类长怎样不知道,
// 所以只能定义其指针、引用,不能调用任何方法,不能访问类的成员(包括构造函数)
class AI;
// 为什么不直接包含AI.h,因为有循环包含的问题
//#include "AI.h"
*/
class Circle
{
private:
//定义圆心和圆的半径
int x1;
int y1;
int r;
public:
Circle(); // 构造函数申明
void setCircle(int _x1, int _y1, int _r); // 申明 与 实现 函数头要完全一样,const都一样
void judge(int x0, int y0);
//类做函数参数的时候,类封装了属性和方法,在被调用函数里面, 不但可以使用属性,而且可以使用方法(成员函数);
//这也是面向对象和面向过程的一个重要区别。。。。
void judge(Point &p);
};
.cpp中
#include "Circle.h" // 包含类的申明,在这里实现类
#include "iostream" // 用到的东西也包含进来
using namespace std;
// :: 域作用符,限定实现的是哪个类的方法
void Circle::setCircle(int _x1, int _y1, int _r)
{
x1 = _x1;
y1 = _y1;
r = _r;
this->r=_r; // this指针一样的用
}
void Circle::judge(int x0, int y0)
{
int a = (x1 - x0)*(x1 - x0) + (y1 - y0)*(y1 - y0) - r*r;
if (a > 0)
{
cout << "点在圆外";
}
else
{
cout << "点在圆内";
}
}
void Circle::judge(Point &p)
{
int a = (x1 - p.getX())*(x1 - p.getX()) + (y1 - p.getY())*(y1 - p.getY()) - r*r;
if (a > 0)
{
cout << "点在圆外";
}
else
{
cout << "点在圆内";
}
}
.h中
#pragma once
class Point
{
public:
void setP(int _x0, int _y0);
int getX();
int getY();
private:
//点的x、y坐标
int x0;
int y0;
};
.cpp中
#include "Point.h"
#include "iostream"
using namespace std;
void Point::setP(int _x0, int _y0)
{
x0 = _x0;
y0 = _y0;
}
int Point::getX()
{
return x0;
}
int Point::getY()
{
return y0;
}
测试:
#include "iostream"
#include "Circle.h" // 用到的类包含进来
#include "Point.h"
using namespace std;
void main()
{
Point myp;
Circle c1;
myp.setP(1, 1);
c1.setCircle(2, 2, 3);
c1.judge(myp);
system("pause");
}
#构造函数
不写任何构造函数,编译器会提供默认的无参构造函数、复制构造函数、析构函数
写了任意的构造函数,编译器就不会提供无参构造函数了
写了复制构造函数,就不会提供复制构造函数了,不写赋值构造函数,就会提供默认的复制构造函数
被私有的构造函数,不能用来建立对象
变量开辟空间了再执行构造函数,构造函数只是初始化,不是开辟空间
无参构造函数-默认构造函数
不写构造函数C++编译器会自动调用一个默认的构造函数,写了无参的构造函数,就不调默认的了,调你写的
有参构造函数
class Test
{
public:
Test(int mya)
{
a = mya;
}
Test(int a,int b) // 参数可有默认值
{
a = mya;
}
private:
int a;
};
Test *p = new Test // 无参构造函数,括号可以不写
Test t0; //a. 自动调用无参的,或者没有写构造函数 调用默认的构造函数
Test t1(10); //c++默认调用有参构造函数 自动调用
Test t2 = 11; // 会调用Test(int mya)构造函数,有隐式转换,
如果explicit Test(int mya),就不会调用了,Test t2 = (Test)11; 强制转换也会调用
准确的指定,Test t1(10);才会调用Test(int mya)构造函数
避免与=操作符重载时,冲突;避免隐式转换
/*类 的类型转换:
1.类型转换函数:转出为其他类型
就是 类型强转重载,参考运算符重载
2.构造转换函数:其他类型转过来
如Test(int mya)就是,Test t2 = 11;时就有隐式转换
有个构造转换函数,函数参数也可隐式转换
void fun(Test t){} -> fun(12); // 这样也可以
自动会去匹配构造函数,Test(int mya,int a=0){} ,
也可以匹配Test t2 = 11;
fun(12);
*/
Test t4 = (1,2); //b. c++默认调用有参构造函数自动调用
Test t3 = Test(12); //c. 我们程序员手动调用构造函数
// 这样的话,t3的构造函数不会执行,这里是直接将匿名对象转换给t3(没有给t3开辟新的空间了)
// 匿名对象析构函数不会执行,到时回收时执行t3的析构函数
Test(12); // 其实是建立了一个匿名对象(临时对象),这句话一执行构造函数、析构函数都会执行(建立马上就销毁了)
Test arr[]={Test(1,2),Test(4,8)}; // 数组初始化
struct Stu
{
int a;
int b;
};
void main()
{
Stu t = {4,6}; // 成员变量全是公有的,可以这样初始化(相当于Stu(int,int))
system("pause");
}
struct Stu
{
int a;
int b;
Stu(int aa,int bb)
{
a = aa;
b = bb;
}
private:
int c;
};
void main()
{
// Stu t = {4,6,9}; // 建立对象并初始化成员变量的值
// 由于有的成员变量是私有的,不能这样初始化
Stu t = {4,6}; // 定义对应的构造函数,这里就是调用了Stu(int aa,int bb)构造函数
system("pause");
}
#复制构造函数
Teacher(const Teacher &obj)
{
a = obj.a;
}
Teacher t1;
Teacher t2=t1;//a. 定义对象,并初始化,调用了复制构造函数
t2 = t1; // 这是数据拷贝了,= 号操作,浅拷贝,不会调用任何构造函数
Teacher t3(t1); //b. 调用了复制构造函数
// 不写复制构造函数,默认也可以a、b操作(有默认的复制构造函数),就是把数据浅拷贝进去
// 写了复制构造函数,就会调用你写的,默认的就不会调了
//c. 当函数的参数是类时,会调用类的复制构造函数、析构函数(指针则不会)
// 其实 形参t,也是会开辟空间的,会调用复制构造函数
// 函数运行完,t会被回收,会调用析构函数
// 注意:实参 与 形参 是两个对象,两个块空间,实参回收会调用析构函数
void fun(Teacher t){}
//d. 当函数的返回值 是类时,会建立一个匿名对象
Teacher fun()
{
Teacher a;
return a; // 返回时,会建立匿名对象,将a的数据赋值给匿名对象(会调用匿名对象的复制构造函数)
}
Teacher b;
b = fun(); // 赋值时,会将匿名对象的数据复制给b(不会调用构造函数了),复制后,匿名对象被回收(调用析构函数)
// a 在匿名对象之前回收(函数调用完就回收了),赋值给b后,匿名对象才回收
Teacher c = fun();// 这样的话,c的构造函数不会执行,这里是直接将匿名对象转换给c(没有给c开辟新的空间了)
// 匿名对象析构函数不会执行,到时回收时执行c的析构函数
复制构造函数、= 号操作,都是浅拷贝
class Name
{
public:
Name(const char *pname)
{
size = strlen(pname);
pName = (char *)malloc(size + 1);
strcpy(pName, pname);
}
Name(Name &obj)
{
//用obj来初始化自己
pName = (char *)malloc(obj.size + 1);
strcpy(pName, obj.pName);
size = obj.size;
}
~Name()
{
cout<<"开始析构"<<endl;
if (pName!=NULL)
{
free(pName);
pName = NULL;
size = 0;
}
}
void operator=(Name &obj3) // 重载=操作符,进行深拷贝,执行=号赋值操作时,就会进入到这里面来
{
if (pName != NULL) // 一定把之前的内存释放,避免内存泄露
{
free(pName);
pName = NULL;
size = 0;
}
cout<<"测试有没有调用我。。。。"<<endl;
//用obj3来=自己
pName = (char *)malloc(obj3.size + 1);
strcpy(pName, obj3.pName);
size = obj3.size;
}
private:
char *pName;
int size;
};
//对象的初始化 和 对象之间=号操作是两个不同的概念
void playObj()
{
Name obj1("obj1.....");
Name obj2 = obj1; //obj2创建并初始化
Name obj3("obj3...");
//重载=号操作符
obj2 = obj3; //=号操作
cout<<"业务操作。。。5000"<<endl;
}
void main()
{
playObj();
system("pause");
}
-------------
c++11
#default、delete
//delete可以禁用默认生成的函数,禁用构造可以无法实例化
//禁用拷贝构造,可以实现禁止别人拷贝你
//default默认存在,可以使用,可以用于建立对象
class myclassA
{
public:
// myclassA() = delete;//delete关键字显式指示编译器不生成函数的默认版本,无法调用,不能用该构造函数建立对象
//myclassA() = default;//当自己定义了构造函数时,我们最好是声明不带参数的版本以完成无参的变量初始化,此时编译是不会再自动提供默认的无参版本了。
// 我们可以通过使用关键字default来控制默认构造函数的生成,显式地指示编译器生成该函数的默认版本
//myclassA(const myclassA &) = delete;//拷贝构造函数
//myclassA(const myclassA &) = default;
//myclassA & operatir=(const myClass&)=delete;
~myclassA();
} ta; // 定义类的时候也不能建立对象
// 当然,一旦函数被delete过了,那么重载该函数也是非法的,该函数我们习惯上称为删除函数
void main211()
{
//myclassA myclassa1;
//myclassA myclassa2(myclassa1);
//myclassA myclassa3 = myclassa1;//重载了=,根据类型
// myclassA a1;
}
delete的显式删除,并非局限于成员函数,由此我们也知default是只局限作用于类的部分成员函数的。
于是我们还可用delete来避免不必要的隐式数据类型转换。比如:
class MyClass
{
public:
MyClass(int i){};
MyClsss(char c)=delete; //删除char版本的构造函数
};
void Fun(MyClass m){}
int main()
{
Func(3);
Func('a'); //编译不能通过
MyClass m1(3);
MyClass m2('a'); //编译不能通过
}
对与普通函数delete也有类型的效果。比如:
void Func(int i){};
void Func(char c)=delete; //显式删除char版本
int main()
{
Func(3);
Func('c); //无法编译通过
return 0;
}
delete的有趣的用法还有删除operator new操作符,编码在堆上分配该类的对象如:
void* operator new(std::size_t)=delete;
析构函数也是可以delete的
这样做的目的是我们在指定内存位置进行内存分配时并不需要析构函数来完成对象级别的清理,这时我们可显示删除析构函数来限制自定义类型在栈上或者静态的构造。
-------------------------------------
#const对象只能调用类的const函数,可访问非const成员变量
------------------
class area
{
public:
int x;
int y;
mutable int z;//不受const约束的类成员,可被const函数访问
area() :x(10), y(10)
{
}
void printxy() const // const函数可读成员变量,不可以修改成员变量
{
z = z + 1;
std::cout << x << " " << y << "\n";
}
void add(int a)
{
x += a;
y -= a;
}
void go() const
{
}
};
void main()
{
const area area1;
area1.printxy(); // 只能调用const修饰的函数,其他函数不能调用
//area1.add(1);
area1.go();
}
------------------
#析构函数
只有一个
#构造函数的初始化列表
class A
{
public:
A(int _a1,int b)
{
a1 = _a1;
}
private:
int a1;
};
class B
{
public:
// 初始化列表,变量名(初始值,与构造函数的参数对应)
// A类,写了构造函数,没有了默认的无参构造函数,初始化A对象就得用初始化列表了
B():mya(12,4),mya2(100,6)
{
}
//成员变量的初始化顺序与声明的顺序相关,与在初始化列表中的顺序无关
B(int x, int y):mya(y,8),mya2(454,101),b1(122),b2(12) // 不是类对象也可以这样赋值
{ // 可用B的参数
// 引用、const常量必须用初始化列表赋初值
b1 = x;
}
B(int c) // 写了初始化列表的 就会初始化里面的A对象,不写的就不会
{
}
private:
const int b2;
int b1;
A mya2; // 先申明,初始化时,先调用构造函数
A mya;
public:
private: // 可以出现多次,作用范围直到遇到下一个作用符
};
B b(45,78); // 建立时,先调用mya2的构造函数,再mya的,再b的
// 回收时,先调用b的析构函数,再调用mya,再mya2(先创建的后析构)
class MyTest:public A
{
public:
// 初始化列表,对于继承的父类空间调用父类构造区初始化
// 父类没有定义构造函数,就不用调用,系统自动调用默认的构造函数
父类构造、当前类成员 可起
MyTest(int a, int b, int c):A(a,b),as(a)
{
this->a = a;
this->b = b;
this->c = c;
}
MyTest(int a, int b)
{
this->a = a;
this->b = b;
MyTest(a, b, 100); // 表面上是打算将构造函数串联起来,调用构造函数
// 但实际是,建立了一个匿名对象,马上就回收了,并不是给c赋值100
}
private:
int as;
int b;
int c;
};
#项目模块分层,项目开发,流程
1.项目的总体需求
2.着手项目启动
a.人员分工
b.模块分工(界面层、业务层、配置文件API)
c.初步项目模型搭建
d.资源的整合
3.项目开发流程
概要设计:具体模块的划分,总体的业务流,流程设计,结束标志:所有的表都定义好了
详细设计:流程细化,重要的流程可以demo实现
4.编码
要有测试工程,随时测试
5.项目扩展
界面层:test_interface.c
业务层:myop.h、myop.c
文件读写层:cfg_op.h、cfg_op.c
#new、delete,开辟、回收堆内存
本质还是malloc、free
int *p = new int; // 开辟一个int类型的空间,并返回首地址给p
int *p1 = new int(122); // 开辟空间,并给这个空间初始化值 122
delete p; // 释放空间,与malloc一样的,要自己是否堆内存
int *p2 = new int[10]; // 开辟一个长度为10的数组,
delete []p2; // 释放数组空间
delete p2; //本来是数组的,这样释放只是释放了一个int的空间,没有释放整个数组
// 不能释放void类型指针(不知道空间范围多少)
class Test
{
public:
Test(int a,int b)
{
}
private:
int a;
int b;
}
// 开辟类空间
// new 会自动调用对应的构造函数 初始化,注意构造函数的定义,一样遵循之前的规则(定义了构造函数,就不会有默认的了.... )
// delete 会自动的调用 析构函数
Test *p = new Test(45,57);
delete p;
new 开辟的空间,可以用 free()函数释放
malloc()开辟的空间,可以用delete 释放
但是:malloc(),free(),在处理类对象时,不会调用构造、析构函数
#new高级用法,在指定区域开辟空间
#include<iostream>
#include<new>
const int buf(512);//限定一个常量整数512
int N(5);//数组的长度
char buffer[buf] = { 0 };//静态区
//p1,p3,p5作为指针变量在栈区,存储的地址指向堆区
//手动释放内存
//p2,p4,p6作为指针变量在栈区,存储的地址在静态区。缓冲区。
//自动释放内存(就是可重复利用,不开辟新空间),用于分配用完了就不会再用的数据
//避免内存泄漏,自动释放内存。牺牲了内存访问独立性,
using namespace std;
void main()
{
double *p1, *p2;
std::cout << "\n\n\n";
p1 = new double[N];//分配内存,N个元素的大小
// 在指定区域(buffer代表的那片内存空间) 分配内存给p2
// p2的地址就是buffer的地址,从buffer是那片空间的起始位置,每次都是从这个位置开始分配
// 分配的内存在buffer内,在静态区
p2 = new (buffer)double[N];
for (int i = 0; i < N; i++)
{
p1[i] = p2[i] = i + 10.8;//对于数组初始化
std::cout << "p1=== " << &p1[i] << " " << p1[i];
std::cout << " p2=== " << &p2[i] << " " << p2[i] << std::endl;
}
double *p3, *p4;
std::cout << "\n\n\n";
p3 = new double[N];
// 再次分配还是从那片空间的起始位置分配,不会在那片空间开辟新空间
// 重复利用那一块空间
// p4修改空间的值了,p2访问时,就是修改的值了,(同一片空间)
p4 = new (buffer)double[N];
// 另外,不能delete p4,delete p2,
// 释放一次就可以了,那样是同一块地址释放两次了
for (int i = 0; i < N; i++)
{
p3[i] = p4[i] = i + 100.8;//对于数组初始化
std::cout << "p3=== " << &p3[i] << " " << p3[i];
std::cout << " p4=== " << &p4[i] << " " << p4[i] << std::endl;
}
for (int i = 0; i < N; i++)
{
// p1[i] = p2[i] = i + 10.8;//对于数组初始化
std::cout << "p1=== " << &p1[i] << " " << p1[i];
std::cout << " p2=== " << &p2[i] << " " << p2[i] << std::endl;
}
std::cin.get();
}
#静态成员变量、静态成员函数
类的所有对象共用
但一样受访问权限控制
程序一开始就存在,直到程序结束才清理
基类定义的静态成员变量,所有子类、孙类...都共享
基类定义的静态成员函数,所有子类、孙类...都可调用
class Test
{
public:
static void fun() // 静态函数只能访问静态变量
{ // 不能使用this指针
cout<<a<<endl;
}
void setA(int _a){a=_a;}
private:
static int a;
int b;
}
int Test::a=9; // 初始化静态变量,不仅是初始化,其实是告诉编译器要给我分配内存(在使用前,必须开辟空间)
// 私有的也得这样初始化
void main()
{
Test t1,t2;
Test::fun();// 可以直接通过类名使用静态变量、静态函数
t1.fun(); // 也可通过对象使用
}
#在类里面有 引用变量、const常量,定义时初始化,或者必须在构造函数时初始化
#include<iostream>
class myclass
{
public:
const int coint;
int & myint;
static const int dashu;
public:
//常量,引用,必须重载构造函数初始化,
myclass(int a, int b) :myint(a), coint(b)
{
const int *p = &coint;
int *px = const_cast<int *>(p);
*px = 12; // 类的const常量,去掉const,可以通过指针修改coint;
std::cout << coint << " " << *px << std::endl;
}
};
const int myclass::dashu = 20;//静态常量初始化
void main()
{
const int *px = &(myclass::dashu);
int *p = const_cast<int *> (px);
// *p = 123;//出错,类的静态常量区可以访问,但不可以修改,也不会建立备份
new myclass(8,9);
}
------------------------------
class mywindowW
{
public:
int #
public:
mywindowW(int data) :num(data)
{
}
};
int main()
{
mywindowW my1(20);
cout << my1.num; //不加endl输出的 是 20
cout << my1.num<<endl; //加上endl关闭输出,输出的是地址
cout << &my1.num << endl;
return 0;
}
-------------------------------------
#C++对象管理模型
C的内存四区,在C++中一样有效
1.C++类对象中的成员变量和成员函数是分开存储的
普通成员变量:存储于对象中,与C中的struct变量有相同的内存布局和字节对齐
静态成员变量:存储于全局区
成员函数:存在代码区
在代码区的函数,如何区分是哪个实体对象调用的?
2.C++编译器对普通成员函数的内部处理
说白了,还是C的结构体操作
class Test
{
private:
int m;
mutable int a;
static int c;
public:
Test(int i)
{
m=i;
this->m=i; //
}
int getM()
{
return m;
}
static void print()
{
printf("呵呵!");
}
void fun() const // 这里的const是修饰this的,也就是说对象不可以修改了
{
a++; // 不能这样,不能修改对象了
} // C++编译器内部处理后:本质是void fun(const Test *p){ p->a++;},所以不能修改了
}; // 只是因为this指针被隐藏,要修饰它,只能将const写在外面
// 函数加const了,可以修改 用mutable修饰的成员变量,没修饰的不能修改
Test a(12);
a.getM();
Test::print();
sizeof(a) == 8 // 占8字节,静态变量、静态成员函数、成员函数(代码区)不在内
----------------
在C++编译器内部处理后:
struct Test // 本质还是结构体
{
int m;
}
// 构造函数,在每一个普通函数的第一个参数是一个结构体指针(this指针),以区分不同的对象
// 就是C中的函数定义
void Test_initialize(Test* pThis,int i)
{
pThis->m=i;
}
int Test_getM(Test* pThis) // getM()函数
{
return pThis->m;
}
void Test_print() // 静态函数就没有加结构体指针了
{
printf("呵呵!");
}
Test a;
Test_initialize(&a,12); // 上面建立对象,并初始化的过程
Test_getM(&a);// 调用对象方法
Test_print(); // 调用静态函数
总结:
1.C++中类的普通成员函数(非静态函数)都隐式包含一个指向当前对象的this指针
Test(int i)
{
m=i;
this->m=i; // 这样是一样的,只是使用this指针,与Java的一样,this指针代表当前对象,任何对象内都有
}
Test fun()
{
return *this; // 返回当前自己对象
}
2.静态成员函数、静态成员变量属于类,不属于对象
3.静态成员函数与普通成员函数的区别
静态成员函数不包含指向具体对象的指针
普通成员函数包含一个指向具体对象的指针
void main()
{
Test t(1,2); //====>Test(this, 1, 2);===>Test(&t, 1, 2);// 其实对象已经被隐式的传入进去了
}
#友元函数、友元类
友元函数、友元类只作用于当前类,不作用于父类、子类
class A1
{
public:
A1()
{
a1 = 100;
a2 = 200;
}
int getA1()
{
return this->a1;
}
// 声明外部的函数为A1的一个友元函数,可以访问A1类的任意成员变量、成员函数
// 友元函数函数的申明不受位置、访问符的限制
// 外部也可以没有该函数,自己在这实现也行->friend void setA1(A1 *p, int a1){}
friend void setA1(A1 *p, int a1);
protected:
private:
int a1;
int a2;
};
// 就是该函数,对A1类有完全访问权,参数是任意的,当然不传对象进去怎么修改、访问啊
// 不会自动加this指针
void setA1(A1 *p, int a1)
{
p->a1 = a1;
}
//friend 破坏了类的封装性。。。。
//cc++ 1预编译gcc -E 2汇编 gcc -i 3编译gcc -c 3、链接ld ===》汇编代码
//汇编代码很难被反编译成源码,所有友元就是留的一个后门,可用于访问私有成员
//java---》1.class==》class==>java类
//Java可以通过反编译,获取源码
//java类的反射 sun jdk api
void main11()
{
A1 mya1;
cout<<mya1.getA1()<<endl;
setA1(&mya1, 300);
cout<<mya1.getA1()<<endl;
system("pause");
}
-------------
class A
{
//b是a的好朋友,当然如果B不在这申明,要包含申明的头文件进来
//或者 class XXX;,在前面申明一下
// 申明了不可定义类变量(不可实例化),要在类定义之后的代码才能建立对象,不然只能建立类指针
friend class B;
public:
void display()
{
cout<<x<<endl;
}
private:
int x;
};
class B // B对A类有完全访问权
{
public:
void setA(int x)
{
Aobj.x = x;
}
void printA()
{
cout<<Aobj.x<<endl;
}
private:
A Aobj; // 定义一个对象,不然操作谁啊
};
void main()
{
B b1;
b1.setA(100);
b1.printA();
system("pause");
}
#运算符重载
可以重载的运算符 + - * / % ^ & | ~ !
= < > += -= *= /= % ^= &= |= <<
>> >>= <<= == != <= >=
&& || ++ -- ->* ‘ -> [] () new delete new[] delete[]
不能重载的算符
. :: .* ?: sizeof
重载运算符函数可以对运算符作出新的解释,但原有基本语义不变:
不改变运算符的优先级
不改变运算符的结合性
不改变运算符所需要的操作数
不能创建新的运算符
操作符的操作对象一定有至少一个是自定义的类(避免循环递归调用重载)
如:Complex operator+(Complex &c1, Complex &c2);
不能
Complex operator+(int a, int b);// 两个操作对象都不是自定义类型,不行
不用记这些
#二元运算符重载
class Complex
{
public:
// friend Complex operator+(Complex &c1, Complex &c2);
Complex(int a, int b)
{
this->a = a;
this->b = b;
}
//2).通过类成员函数完成-操作符重载
// 左操作数由ObjectL通过this指针传递,右操作数由参数ObjectR传递
// Complex c3 = c1 - c2;当前对象就是左操作数
// 所以不用两个参数
Complex operator-(Complex &c2)
{
Complex tmp(a - c2.a, this->b - c2.b);
return tmp;
}
private:
int a;
int b;
};
// 1).通过全局函数重载,操作数(运算对象)通过参数传进去,参数就是操作符要求的操作数
// 这里只是重载在外面,访问不到私有成员,要定义在类里面,或申明友元函数
// 认真思考运算的结果是什么,返回值就是运算结果,或者是需不需要返回值
Complex operator+(Complex &c1, Complex &c2)
{
Complex c3(c1.a + c2.a, c1.b+c2.b);
return c3;
}
void main()
{
Complex c1(1, 2), c2(3, 4);
// 运算符重载本质是 函数,函数名固定了(重载的固定格式:operatorXXX),参数(用引用,指针效率高,直接传对象也是可以的,任意,只是效率不高)、返回值任意自己写
// 就相当于是一个函数,可用函数调用的形式使用
// 既然是函数,就符合函数重载,如:mystring operator=(const mystring& s),mystring operator=(const char* s)
//Complex c3 = operator+(c1, c2);
//Complex c3 c1.operator+(c2);
// 操作符重载 就是函数定义,既然是函数,一样可以有函数重载
String& operator=(String &s1); c3 = c1 = c2
String& operator=(const char *str);// c3 = "abcde";
// 也可直接+,会自动执行到函数内
// C++编译器去查找,看有木有实现operator+的全局函数、成员函数、友元函数(说白了就是重载+没有)
// 基本数据类型编译器知道怎么运算了,但类不知道
Complex c3 = c1 + c2;
// C++编译器会通过类型,来确定用默认的+,还是重载的+
// + --> operator+(c1, c2),相当于函数调用
// + --> c1.operator+(c2),在类里面重载时,左操作数会通过this指针,隐式传入,所以在c1的类中重载时,可不用再写c1参数
system("pause");
}
#一元运算符重载
class Complex
{
public:
friend Complex& operator++(Complex &c1); //前置++
Complex(int a, int b)
{
this->a = a;
this->b = b;
}
// 参数通过this指针隐式传入了,
//通过成员函数完成前置--
Complex& operator--()
{
this->a--;
this->b--;
return *this; // 返回当前对象
}
private:
int a;
int b;
};
//前置++,运算的结果是一个可用于赋值的变量,所以返回引用,参数也是引用,返回值为引用表明是前置++
// 参数不用引用的话,改变的是形参的值,实参对象的值没有改变
Complex& operator++(Complex &c1)
{
c1.a ++;
c1.b ++;
return c1;
}
void main()
{
Complex c1(1, 2), c2(3, 4);
//前置++ 全局函数
++c1;
//前置-- 成员函数
--c1;
}
#后置++
// 友元函数重载,没有this指针
// 友元函数不能重载的运算符=、()、[]、->
Complex operator++(Complex &c1,int) // 加个占位参数只是区分是后置,不加的话,函数名、参数与前置的一样了
{
Complex tmp=c1;
c1.a ++;
c1.b ++;
return tmp; // 返回临时变量,返回值不是引用了,参数:操作对象都是引用
// 不借助tmp,直接返回c1,前置后置++都一样(即放对象前放对象后一样)
}
//通过类成员函数完成后置--
Complex operator--(int)
{
Complex tmp = *this;
this->a--;
this->b--;
return tmp;
}
#<<重载
Complex c1(1, 2);
//operator<<(cout, c1); // 全局函数重载模型
//cout.operator<<(c1); // 成员函数重载模型,很显然不可能有cout对象的源码
// 要改只能通过AOP(面向切面编程,汇编级别的,做编译器的人做的)处理
// 于是只能通过全局函数重载,友元函数真正的用途(Complex中把该函数申明为友元函数)
void operator<<(ostream &out, Complex &c1)
{
//out<<"12345,生活真是苦"<<endl;
out<<c1.a<<" + "<<c1.b<<"i "<<endl;
}
cout<<c1<<"abcc";
// 其实就是 cout.operator<<(c1).operator<<("abcd");
// 以上的重载,返回值是void,输出"abcc"时出错->void.operator<<("abcd");
// 通过数据类型类确定操作符的运行
// 为了支持链式编程,返回一个引用
ostream& operator<<(ostream &out, Complex &c1)
{
//out<<"12345,生活真是苦"<<endl;
out<<c1.a<<" + "<<c1.b<<"i "<<endl;
return out;
}
#()重载,函数调用操作
必须在类内重载
类名、对象可当函数调用
class mystring
{
private:
char *s;
public:
void operator ()(const char *str)
{
strcpy(s,str);
}
void operator ()(int a) // 任意类型、任意参数个数都可重载
{
sprintf(s,"%s",a);
}
}
void main()
{
mystring str;
str("xxzc"); // ()操作,针对 对象,函数调用不算
}
#地址操作符*、&重载
int operator * ()//重载*操作符,返回int
int *operator & ()//重载&操作符,返回int *
#强制转换符 重载
1、定义函数时不允许有参数,不允许有返回类型,但函数必须返回所需类型的值
2、重载函数只可以是类成员函数,不可以为非类成员函数,友元函数也不行
class mystring
{
private:
char *s;
// 类 类型也可转,不同类对象之间也可转
// operator ClassA()
operator int() // mystring::operator int(),这样写在外部也行
{ // int 就是要转换为的结果类型
return atoi(s); // 操作int类型,必须返回int型
}
operator int* ()
{
return 整形变量地址;
}
}
void test(int a){}
void main()
{
mystring str("123456");
int n = int(str);// 初始化时,会隐式转换
// 或者int n = (int)str; // 强转
// 或者 int n = str // 隐式转换
test(str); // 重载了 强制转换符,自动会匹配函数参数
}
#[]、=、==、!=重载
class Array
{
private:
int mLength;
int* mSpace;
public:
Array(int length);
Array(const Array& obj);
int length();
void setData(int index, int value);
int getData(int index);
~Array();
public:
int& Array::operator[](int i);
//实现=等号操作符重载
Array& Array::operator=(Array &a1);
bool operator==(Array &a2);
bool operator!=(Array &a2);
};
Array::Array(int length)
{
if( length < 0 )
{
length = 0;
}
mLength = length;
mSpace = new int[mLength];
}
Array::Array(const Array& obj)
{
mLength = obj.mLength;
mSpace = new int[mLength];
for(int i=0; i<mLength; i++)
{
mSpace[i] = obj.mSpace[i];
}
}
int Array::length()
{
return mLength;
}
void Array::setData(int index, int value)
{
mSpace[index] = value;
}
int Array::getData(int index)
{
return mSpace[index];
}
//以下是运算符重载函数
//从数组里面拿元素
/*
int Array::operator[](int i) 3k
{
return mSpace[i];
}
*/
//一下是运算符重载函数
//从数组里面拿元素
int& Array::operator[](int i) // 返回引用,可赋值了,上一个函数不能给arr[i]赋值,只能读
{
return mSpace[i];
}
/*
//功能2
Array a3(20);
a3 = a1;
*/
Array& Array::operator=(Array &a1) // 返回引用,可以连续赋值
{
int i = 0;
if (this->mSpace != NULL) // 释放自己之前的内存
{
delete[] mSpace;
this->mLength = 0;
}
//a1的所有元素赋给a3
this->mLength = a1.mLength;
this->mSpace = new int[a1.mLength];
for (i=0; i<this->mLength; i++)
{
mSpace[i] = a1[i];
}
return *this;
}
a1[i] = i;
printf("array %d: %d\n", i, a1[i]); // [] 的读写
Array a2 = a1; // 会调用复制构造函数
Array a3(20);
a2 = a3 = a1; // 连续用 = 赋值
//功能3
//if (a1 == a2) //功能3
bool Array::operator==(Array &a2)
{
if (this->mLength != a2.mLength)
{
return false;
}
for (int i=0; i<this->mLength; i++)
{
if (this->mSpace[i] != a2[i])
{
return false;
}
}
return true;
}
// if (a1 != a2) //功能4
bool Array::operator!=(Array &a2)
{
return !(*this == a2); // == 重载了,直接用
}
# +=重载,+=重载实现不同对象之间的运算
-----------------
#include<iostream>
using namespace std;
class B;
class A
{
private:
int a;
int b;
public:
friend A operator +=(A &a, B b);
A(int aa, int bb)
{
a = aa;
b = bb;
}
/*A operator +=(A o) // 在类内部重载
{
a += o.a;
b += o.b;
return *this;
}*/
void out()
{
cout << a << " " << b << endl;
}
};
class B
{
private:
int a;
int b;
public:
friend A operator +=(A &a, B b);
B(int aa, int bb)
{
a = aa;
b = bb;
}
void out()
{
cout << a << " " << b << endl;
}
};
// 用友元重载,实现不同对象运算
A operator +=(A &a,B b)
{
a.a += b.a;
a.b += b.b;
return a;
}
void main()
{
A a(1,2),a1(3,4);
// A a2 = a += a1;
a.out();
// a2.out();
/*---*/
B b(8,9);
A a3 = a += b; // 由于参数用的引用,a的值也变了
a.out();
a3.out();
std::cin.get();
}
-----------------
#new delete 重载
重载new和delete必须在关键字operator和操作符之间留下空格,及如下形式:operator new
重载new第一个参数一定是size_t类型参数,函数必须返回无类型指针。
重载delete第一个参数一定是一个无类型的指针,函数必须没有返回值
void * operator new(size_t t) // 参数就是系统给你分配的大小
{
mystring *p = (mystring *)malloc(t);// 不能用new了,构造函数还是会调用了
//或者这样开辟 mystring *p = ::new mystring; // 避免死循环,调用全局的new,没重载的
// 但构造函数会被调用2次,operator new一次,::new mystring;一次,但对象只建立了一个
// 先开辟了空间,才调用构造函数初始化,operator new时还没开辟空间,::new 时才真正有空间
// (两次new调用完之后才,开始调用构造函数)
return p; // 返回给接收的指针变量,如果返回NULL,建立对象时,接收的指针就赋值为NULL(delete时,不会调用析构函数)
}
// 重载了new,就最好重载delete
void operator delete(void *p)
{
free((mystring *)p);// 不能释放void类型指针(不知道空间范围多少),析构函数还是会调用了
//或者 ::delete p; // 重载,避免死循环
// 但析构函数只调一次(先调用析构函数,再释放,先局部delete->再全局delete)
// (因为只有一个指针,只析构一次)
p=NULL;
}
#new[] delete[] 重载
// 这时分配的大小 t ,总是多4字节
// 如:sizeof(mystring)->4,mystring[10]->40,但这里分配时,是44(多出的4字节是方便释放用的)
// 数组的所有空间一次开辟、销毁完,但是构造、析构函数几个元素调用几次
static void * operator new[](size_t t)
{
mystring *p = (mystring *)malloc(t);//
return p;
}
// 重载了new,就最好重载delete
static void operator delete[](void *p)
{
free((mystring *)p);// 不能释放void类型指针(不知道空间范围多少)
p=NULL;
}
mystring *p = new mystring[10]; // 可用的是sizeof(mystring)*10字节,但多分配了4字节
#new、delete全局重载
a_41
对象不重载,默认使用的是全局的new、delete,(内部是malloc、free实现的)
可用于内存检测
一样的定义,只是全局函数而已
正确的重载是,在成员函数内重载
---------------------------
#继承
子类继承了父类,那么子类拥有了父类全部的成员函数、成员变量,在子类中占内存(父类在子类内存块的最前端)
只是因为权限父类私有的,子类不能访问,外界更不能访问
class A
{
private: // 访问控制3种
int a;
protected:
int b;
public:
int c;
}
// 继承方式3种,private、protected、public,默认不写是private继承
// 表示继承的东西在B内的访问权限
// 注意:父类的权限高于继承方式,如父类的私有成员,即使public继承,外界还是无法访问
// class B : private A // 表示从父类继承下来的东西,在该类(B)中是私有的,自己可访问,外界不可访问
// class B : protected A // 表示从父类继承下来的东西,在该类(B)中是保护的,自己可访问,一个族的可访问,外界不可访问
class B : public A // 表示从父类继承下来的东西,在该类(B)中是公有的,自己可访问,一个族的可访问,外界可访问
{
private int d;
};
子类能否访问父类的东西,看父类的访问权限
外界能否访问一个类的东西,看这个类的访问权限,以及其父类的访问权限
父类成员访问级别
继
承
方
式 public proteced private
public public proteced private 在子类中的访问权限
proteced proteced proteced private
private private private private
#赋值兼容原则
// 可以将子类赋值给父类,引用、指针、赋值、初始化都可以(两者有相同的内存区,父类在子类内存块的最前端)
class A
{
private:
int a;
protected:
int b;
public:
int c;
void print(){}
};
class B :public A
{
public:
int d;
void fun(){}
};
void main()
{
B b;
A &a1 = b;
A *a2 = &b;
A a3 = b;
A a4;
a4 = b; // 可以赋值
A *a = new B;
((B *)a)->fun(); // 父类指针可以访问子类变量、函数(new的是子类,还得强转)
// 静态成员变量也是一样的,子父类重名了,有两个空间,独立的
// 要强转,或者加类名区分
}
void main3()
{
B *p(nullptr);
p->fun(); // 空指针也可以调用类的函数,只要函数内没有使用类的成员变量(因为没有开辟空间)
// 因为函数在代码区,调用时明确是那个类、类对象即可区分
// 把父类强转为子类,可以调用子类的函数,只要函数内没有使用类的成员变量(因为new的父类,内部没有该内存数据)
// 因为函数在代码区,调用时明确是那个类、类对象即可区分
B *p1 = static_cast<B *>(new A);
// 虽然强转,但可调用父类的方法(即使方法内使用到成员变量,因为的确开辟了空间)
p1->A::print();
}
#异质链表
程序中,用基类类型指针,可以生成一个连接不同派生类对象的 动态链表,
即每个结点指针可以指向类层次中不同的派生类对象。
这种结点类型不相同链表称为异质链表
#继承中的构造与析构
子类对象创建时,会首先调用父类的构造函数,初始化继承下来的成员
父类的构造函数执行完,后再执行子类的
当父类的构造函数有参数时,需要在子类的初始化列表中显示调用
析构函数调用是先调用子类的,再调用父类的(与构造函数调用的顺序相反)
class A
{
public:
// A()
A(int a)
{
cout << "父类构造函数调用了" << endl;
}
};
class B :public A
{
public:
// 会默认调用A的无参/默认构造函数
//
// B(int a) :A(a) // 当A中定义了有参构造函数,没有默认构造函数了,又没有定义无参构造函数时,手动调用其构造函数
B()
{
cout << "父类构造函数调用了" << endl;
}
int d;
};
void main()
{
B b(2); // 会默认调用A的无参/默认构造函数
}
一个类中,既继承的父类,又包含了其他类,那么构造时,先调用父类的构造函数,再其他类的,最后自己的
class C:public A
{
B b;
}
// 构造顺序是 A、B、C
#同名的成员,加类名区分,默认用自己的,屏蔽父亲的,同名的函数(不管参数)也是用自己的,不会去父亲那找(除非不同名)
class A
{
public:
int a;
};
class B :public A
{
public:
int a = A::a; // 用父类的
int b;
};
void main()
{
B t;
t.a = 2; // 用自己的
t.A::a = 8;// 用父类的
}
-----------
class Parent01
{
public:
Parent01()
{
cout<<"Parent01:printf()..do"<<endl;
}
public:
void func()
{
cout<<"Parent01:void func()"<<endl;
}
void func(int i)
{
cout<<"Parent:void func(int i)"<<endl;
}
void func(int i, int j)
{
cout<<"Parent:void func(int i, int j)"<<endl;
}
};
class Child01 : public Parent01
{
public:
void func(int i, int j)
{
cout<<"Child:void func(int i, int j)"<<" "<<i + j<<endl;
}
void func(int i, int j, int k)
{
cout<<"Child:void func(int i, int j, int k)"<<" "<<i + j + k<<endl;
}
void go()
{}
void go() const // 在类内加了const,也算重载
{}
};
int main()
{
Parent01 p;
p.func();
p.func(1);
p.func(1, 2);
Child01 c;
//c.func(); // 编译出错,func函数与父类的函数重名了,默认调用是自己,不会去父类找,而自己又没有重载这个函数
// 如果一定要调用,用类名标明,c.Parent01::func(); 或者强转为父类
//c.func(1, 2);
return 0;
}
函数重载
必须在同一个类中
子类无法重载父类的函数,父类同名(名字相同即可,不用管参数)函数将被覆盖,不会重载父类的函数
(调用时,屏蔽掉了父类同名的函数,不会去父类找函数调用)
重载函数的调用,是在编译期间根据函数名、函数参数类型 个数决定调用哪个的
函数重写
依旧可以调用父类的函数,加类名限定
必须发生在父子类中
函数原型完全相同,返回值类型都得相同
没virtual修饰,看左边什么类型,可决定调用子类还是父类方法
virtual修饰后,看右边赋值什么类型,会发生多态;不用virtual修饰那叫重定义 不会发生多态
多态是在运行期间,根据具体对象的类型(指针所指向的空间)决定调用父类,还是子类的函数
内联成员函数
函数实现 要与类的定义在同一个文件内
----------------
#pragma once
#include <iostream>
class fushu
{
public:
int x;
int y;
public:
fushu();
~fushu();
inline void showall(int x, int y);//显式内联
/*
inline void showall(int x,int y)
{
//复合代码
std::cout << (this->x = x) << (this->y = y) << std::endl;
}
*/
};
//内联函数原则上放在头文件,在外部实现时 去掉inline标识符,申明时要加上
//内联函数需要展开,(VS2013是要求放在头文件的)
void fushu::showall(int x, int y)
{
std::cout << (this->x = x) << (this->y = y) << std::endl;
}
----------------
#继承中的static
class A
{
public:
static int a;
};
class B :public A
{
public:
int b;
void fun()
{
cout << a << endl; // a是静态的,一定要初始化
}
};
//int A::a=9; // 不管继承,不继承,使用前一定要初始化、开辟空间
// 所有子类都共享静态成员变量
// 如果B中定义同名的静态变量,两个空间时独立的,两个都要初始化
void main()
{
B b;
b.fun();
}
#多继承
class C:public A,private B // 在前面的父类,构造函数先调用
{
}
#多继承的二义性
class B { public : int b ;} ;
class B1 : public B { private : int b1 ; } ;
class B2 : public B { private : int b2 ; } ;
class C : public B1 , public B2 { public : int f ( ) ; private : int d ; } ;
// 由于B1、B2继承了B, B1、B2的内存中都有b的空间
// 这时C继承B1、B2,C中有两个b空间(两块B空间),分别是B1、B2中继承下来的(不用管B了,B的东西包含在B1、B2中了)
// 函数也包含在类内,只是放在代码区,所有对象共用而已
构造C时,B1,B2的构造函数会调用,B的构造函数会被调用2次(B1,B2都调了一次),B的析构函数也会被调用2次,很危险
C c ;
c . b ; // error,不知道用的是哪一个,有两个
c . B :: b ; // error,从哪里继承的?C中只有B1、B2的b,没有B中的b(继承谁,就将谁的内存数据搬过来)
c . B1 :: b ; // ok,从B1继承的,使用时,加以区分
c . B2 :: b ; // ok ,从B2继承的
c . B2 ::B:: b ; // ok
如果在多条继承路径上有一个公共的基类,那么在继承路径的某处
汇合点,这个公共基类就会在派生类的对象中产生多个基类子对象(都占了内存)
要使这个公共基类在派生类中只产生一个子对象(无论该基类在派生层次中出现多少次,共用一块内存),
必须对这个基类声明为虚继承,共享的基类被称为虚基类。
class A
{ public :
int b ;
A ( ) { cout << "class A" << endl ; }
} ;
class B : virtual public A // A中的b在B中占内存,虚继承
{ public :
B ( ) {cout << "class B" << endl ; }
} ;
class C : virtual public A // A中的b在C中占内存
{ public :
C ( ) {cout << "class C" << endl ; }
} ;
class D : public B , public C // D中只有一个b了,是来自A的 ,(不直接继承A了,不必加virtual)
{ public :
D ( ) {cout << "class D" << endl ; }
} ;
void main ( )
{
D dd ; // A的构造函数,析构函数 只调用了一次,因为内存中只有一块A的内存空间了
// 会出现地址重复释放错误,把A的析构函数改成虚析构就好了
A *p = new D();
delete p;
}
由于类 D 的对象中只有一个 A 类的b
所以,当以不同路径使用名字 b 访问 A 类时,所访问的都是
那个唯一的基类中的b。即
dd . B :: b 和 dd . C :: b
引用是同一个基类 A 的b,这些标识指向同一块空间
dd . b // ok
dd .A:: b // ok
#多态
C、C++是静态编译性语言,对于赋值兼容性原则
父类的指针在编译期间不知道指向父类,还是子类,不论传什么,都是调用父类的东西
class Parent
{
public:
Parent(int a = 0)
{
this->a = a;
}
void print()
{
cout<<"父类a:"<<a<<endl;
}
private:
int a;
};
class Child : public Parent
{
public:
Child(int b = 0)
{
this->b = b;
}
void print()
{
cout<<"子类b:"<<b<<endl;
}
protected:
private:
int b;
};
void main()
{
Parent p1;
Child c1;
// 赋值兼容性原则 遇上 同名函数的时候,给父类指针/引用传子类还是父类,调用的都是父类的函数
// 在父类的函数上 加上virtual,virtual void print(),子类中同名的函数可以加,也可以不加
// (绝对匹配,函数指针的匹配原则,参数、返回值、函数名都一样)
// (子类中 与 父类虚函数的同名函数 加不加 virtual 都是虚函数,加上只是代码可读性增强了)
// 可以实现给父类指针、引用(一定是指针/引用)传子类则调用子类的函数,传父类则调用父类的函数
// 这就是多态(父类指针/引用做函数参数一样,这就是多态的真正意义)
// 构造函数不能是虚函数,
Parent *p;
p = &c1;
p->print(); // 父类中加了virtual,调用的就是子类的方法了,不加,始终是父类的
// 如果父类的析构函数搞成虚函数,释放p时,按照右边类型依次调父类的析构函数(虚函数了,看右边类型)
// 否则只会按照左边的类型 依次调析构函数
Parent &myp = c1;
myp.print();
}
虚继承和虚析构是很好的习惯
#多态实现原理-虚函数
当类中申明虚函数时,编译器会在类中生成一个虚函数表,只要类中(不管父类子类)有虚函数,就为该类生成
(子类继承父类,就有了虚函数,即使自己没定义自己的虚函数,子类中也会生成该表的,子类、父类各自生成自己的一个虚函数表)
(子类中 与 父类虚函数的同名函数(函数完全相同,这是重写的条件) 加不加 virtual、重不重写 都是虚函数(自然子类中有了虚函数))
(在类中加了东西,好比多加了一个函数指针变量,编译器可以在你定义的类中任意操作,添加/去掉东西)
class{int a;int t;}
虚函数表是一个存储 虚函数 的数据结构(每一个元素保存的是一个函数指针)
虚函数表是由编译器自动生成与维护的
virtual会被放入虚函数表中
存在虚函数时,每个对象中都有一个指向 虚函数表 的指针(vptr指针)
(类中多了成员变量,有虚函数时sizeof()类,就会多了4字节,在类内存的最开头)
(编译器也不知道父类指针 指向的子类还是父类,只不过是在用类定义对象时,在对象内加了一个vptr指针,类中多了成员)
(父类定义对象,加一个vptr指针变量;子类定义对象加一个vptr指针变量)
(在函数调用时,不用管是父类还是子类对象,去vptr指针指向的表查找函数执行即可)
void run(Parent *p)
{
p->fun(); // 编译器确定fun是否为虚函数
} // fun不是虚函数,编译器可直接确定是调用Parent类的成员函数,在编译时就确定了调用的函数(这就是 静态链编)
// fun是虚函数,编译器根据对象的vptr指针所指向的虚函数表查找fun函数,并执行
// (查找和调用在运行时完成,这就是动态链编)
// 出于效率的考虑,没必要将所有的成员函数都申明为虚函数
联编:指程序中 模块、代码之间互相关联的过程
静态联编:程序的匹配、连接在编译阶段实现,如函数重载,在编译时就知道调用的是那个函数
动态联编:指程序的联编推迟到运行时进行,如switch、if语句就是动态联编
虚函数表查看:
1.运行VS 2013 开发者工具,编译源代码
c: 切换盘符
cd 到源代码目录
cl xxx.cpp 编译源码
2.查看类分析报告,
大小、成员(包含vfptr指针)、虚函数表($vftable@,前面的数是函数编号)等等信息
cl /d1 reportSingleClassLayoutxxx main.cpp
xxx:指定要查看的类名
aaa.cpp:指定类所在的源代码文件
会列出很多类的信息,找到自己要查看的类,信息如下:
class A
{
private:
int a;
protected:
int b;
public:
int c;
virtual void fun(){}
};
class B :public A
{
public:
int d;
void fun(){}
};
查看结果如下:
class B size(20): // 当前类size大小
+---
| +---(base class A) // 父类,及基类成员
0 | |{vfptr}
4 | | a
8 | | b
12 | | c
| +--- // 自己类内的成员
16 | d
+---
B::$vftable@: // 虚函数表,函数前面的是编号0,1,2....
| &B_meta
| 0
0 | &B::fun
B::fun this adjustor:0
/*----------------------*/
class Parent
{
public:
Parent(int a = 0)
{
this->a = a;
print();
}
virtual void print()
{
cout<<"父类a:"<<a<<endl;
}
private:
int a;
};
Child c1;
// 在父类的构造函数内调用虚函数,不会出现多态
// 因为子类对象的初始化是分步的
// 建立c1时,先调用父类的构造函数,这时子类的vptr指针被初始化 指向父类的虚函数表
// 当父类构造函数执行完时,子类的vptr指针被初始化执行自己的虚函数表
-------------------------
void howtoDel(Parent *pbase)
{
delete pbase;
}
void main()
{
Parent *pc1 = new Child();
howtoDel(pc1);
system("pause");
}
// 父类析构函数申明成虚函数virtual ~Parent(),在销毁时,会自动调用子类(先)、父类的析构函数
// 不申明为虚函数,像howtoDel()函数,销毁时,只是调用Parent的析构函数
/**虚函数 vptr指针实例理解*/
#include <iostream>
using namespace std;
typedef void(*Fun)(void);
class H
{
virtual void M()
{
cout << "H::M" << endl;
}
};
class A
{
//int num;
virtual void g()
{
cout << "A::g" << endl;
}
private:
virtual void f()
{
cout << "A::f" << endl;
}
virtual void j()
{
cout << "A::j" << endl;
}
};
/***--------------*/
class C :public A // C中有一个vptr指针,是从A继承过来的vptr指针,C新加的虚函数会追加在原虚函数表的后面
{
virtual void j()
{
cout << "C::j" << endl;
}
virtual void z()
{
cout << "C::z" << endl;
}
};
/***--------------*/
class D :public A, public C// D中有2个vptr指针,分别来自A C,A的vptr排在C的vptr前面
{
};
void main2()
{
D d;
Fun fd; // typedef void(*Fun)(void);
int i = 0;
// 调用A类虚函数表的所有函数,(私有也可调用)
for (i = 0; i < 3; i++)
{
fd = (Fun)*( (int *)*(int *)(&d) + i );
fd();
}
cout << "---------------" << endl;
// 调用第二个vptr指针,的虚函数表的函数(C类的,C的虚函数包括其父类A的),虚函数表里面的每一个元素保存的是函数指针
// (为什么要强转,1.星号取值操作时,确定取多大空间内的数据,2.确定步长。void * 类型的指针,取值不知道取多少空间,int * 就知道取4字节,char * 取1字节)
// 多个vptr指针,在类的前端线性排列
// 如果指针占8字节,就要强转为long类型(占8字节),
//(int*)&d: 类型转换,获得虚函数表的首地址。这里使用 int* 的原因是函数指针的大小的 4byte,使用 int* 可以使得他们每次的偏移量保持一致(sizeof(int*) = 4,32-bit机器)。
//*(int*)&d:解指针引用,获得虚函数表。
//(int*)*(int*)&d+0:和上面相同的类型转换,获得虚函数表的第一个虚函数地址。
for (i = 0; i < 4; i++)
{
fd = (Fun)*( (int *)*( (int *)(&d) +1 ) + i );
// *((int*)*(int*)&d+4),虚函数表的结束标志,不要强转为Fun
fd();
}
system("pause");
}
虚函数表的结束结点,就像字符串的结束符“/0”一样,其标志了虚函数表的结束。
这个结束标志的值在不同的编译器下是不同的。
在WinXP+VS2003下,这个值是NULL。
而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,这个值是如果1,表示还有下一个虚函数表,如果值是0,表示是最后一个虚函数表。
/***--------------*/
class B : public A, public H, public D // B中有4个vptr指针,来自A的1个,来自H的一个,来自D的两个
{
void g() // 重写A类的
{
cout << "B::g" << endl;
}
virtual void o() // 新加的,新加的虚函数,会追加在最前面的vptr指针指向的虚函数表中(即追加在来自A的vptr指针的虚函数表中)
{
cout << "B::o" << endl;
}
virtual void h() // 新加的
{
cout << "B::h" << endl;
}
};
void main()
{
B d;
Fun fd;
int i = 0;
// 调用来自 A类的虚函数
for (i = 0; i < 3; i++)
{
fd = (Fun)*((int *)*(int *)(&d) + i);
fd();
}
cout << "---------------" << endl;
// 调用来自 H类的虚函数
fd = (Fun)*((int *)*((int *)(&d) + 1) + 0);
fd();
cout << "---------------" << endl;
// 调用来自 D类中来自A的虚函数
for (i = 0; i < 5; i++)
{
fd = (Fun)*((int *)*((int *)(&d) + 2) + i);
fd();
}
cout << "---------------" << endl;
// 调用来自 D类中来自C的虚函数
for (i = 0; i < 4; i++)
{
fd = (Fun)*((int *)*((int *)(&d) + 3) + i);
fd();
}
system("pause");
}
void main1()
{
cout <<"A " << sizeof(A) << endl;
cout << "C " << sizeof(C) << endl;
cout << "H " << sizeof(H) << endl;
cout << "B " << sizeof(B) << endl;
cout << "D " << sizeof(D) << endl;
A a;
Fun fa;
int i = 0;
// 调用A类虚函数表的所有函数,(私有也可调用)
for (i = 0; i < 3;i++)
{
fa = (Fun)*((int *)*(int *)(&a) + i);
fa();
}
cout << "---------------" << endl;
C c;
A *p = new C;
// 子类继承父类,子类的虚函数表中包含有父类的虚函数
// 重写父类的虚函数,子类的虚函数表中不增加新的虚函数
// 子类新加的虚函数,子类的虚函数表才增加虚函数,追加在最前面的vptr指针指向的虚函数表中
for (i = 0; i < 4; i++)
{
fa = (Fun)*((int *)*(int *)(&c) + i);
fa();
fa = (Fun)*((int *)*(int *)(p) + i);
fa();
}
cout << "---------------" << endl;
D d;
for (i = 0; i < 3; i++)
{
fa = (Fun)*((int *)*(int *)(&d) + i);
fa();
}
cout << "---------------" << endl;
B b;
Fun pFun;
for (i = 0; i < 5; i++)
{
// 将类地址转换为4字节的指针地址,vptr地址,取出vptr指向的空间(虚函数表),转为4字节地址,+i,取出保存的函数地址,转为函数
// 父类的虚函数在表前面
pFun = (Fun)*((int*)* (int*)(&b) + i);
pFun();
}
//多继承的 类中 vptr指针,父类的 先继承的vptr指针放在类内存的最前面
// A的vptr在最前面,H的vptr随后
Fun pFun1 = (Fun)*((int *)*((int*)(&b) + 1));
pFun1();
system("pause");
}
/*----------------------*/
#父类指针、子类指针步长问题
class Parent01
{
protected:
int i;
int j;
public:
virtual void f()
{
cout<<"Parent01::f"<<endl;
}
};
class Child01 : public Parent01
{
public:
int k;
public:
Child01(int i, int j)
{
printf("Child01:...do\n");
}
virtual void f()
{
printf("Child01::f()...do\n");
}
};
viod main()
{
int i = 0;
Parent01* p = NULL;
Child01* c = NULL;
// 可以使用赋值兼容性原则,是用在多态的地方
// 不要轻易通过父类指针p++,来执行函数操作
// 问题的本质 子类指针 和父类指针 步长可能不一样
// (当子类没有新添加自己的成员变量时,即没有k变量时,步长是一样的,sizeof()子类父类得到的大小一样)
// vptr指针在类内存的最前面,c++移动的是正确的步长
// p++少移了4字节,刚好指在k的内存位置,不是vptr的位置,读取k的值,不是虚函数表的地址
// 导致寻址失败,找不到要调用的虚函数
Child01 ca[3] = {Child01(1, 2), Child01(3, 4), Child01(5, 6)};
p = ca; //第一个子类对象赋值给p,p是基类指针,
c = ca;
p->f(); //有多态发生
c->f();
p++; // p指向ca[0]的k变量地址了
c++; // 指向ca[1]元素了
p->f();
c->f();
}
#纯虚函数 抽象类
多继承在实际工程中是不建议使用的,代码维护困难
多继承实现的,都能用单继承现实
纯虚函数:是在基类中说明的虚函数,在基类中没有定义,要求任何派生类自己去实现
只是提供一个公共的界面
抽象类:具有纯虚函数(非纯虚函数 不行)的基类就是抽象类,不能定义对象,不能作为函数参数、返回值,可以定义指针、引用
用于接口的封装设计,模块、功能的划分
// 如果子类继承父类不去重写虚函数,那么子类还是抽象类
class Interface1
{
public:
virtual void print() = 0; // 纯虚函数,形式1
virtual int add(int i, int j) = 0 // 形式2,可有函数体,
{
cout<<"xxx"<<endl; // 可有语句,也可是空白
}
virtual void fun(){} // 虚函数,非纯虚函数
// 后面有没有=0,决定是不是纯虚函数
};
class Interface2
{
public:
virtual int add(int i, int j) = 0;
virtual int minus(int i, int j) = 0;
};
class parent
{
public:
int i;
};
class Child : public parent, public Interface1, public Interface2
{
public:
void print() // 实现纯虚函数了(全部都要实现),就不是抽象类了
{
cout<<"Child::print"<<endl;
}
int add(int i, int j)
{
return i + j;
}
int minus(int i, int j)
{
return i - j;
}
};
int main()
{
Child c;
c.print();
cout<<c.add(3, 5)<<endl; // 虽然Interface1, Interface2里面有一样的add函数,但由于是虚函数,没问题
cout<<c.minus(4, 6)<<endl; // 在Child内只有一份,调用的是Child内的函数
Interface1* i1 = &c; // 多态
Interface2* i2 = &c; // 多态
cout<<i1->add(7, 8)<<endl;
cout<<i2->add(7, 8)<<endl;
}
# rtti :实时类类型检测
typeid, dynamic_cast必须依赖于虚函数表
类型不匹配转换失败,返回为空。类型安全
class A
{
public:
int num;
static int data;
virtual void run()
{
std::cout << "Arun\n";
}
};
int A::data=1;
class B:public A
{
public:
int num=0;
static int data;
void run()
{
std::cout << "Brun\n";
}
void test()
{
std::cout << num<<"\n";
std::cout << "Btest\n";
}
};
int B::data = 2;
void main()
{
A a1;
B b1;
A *p1 = &a1;
A *p2 = &b1;
B *p3(nullptr);
//p3 = static_cast<B *>(p1);//直接强转,不安全,与虚函数无关,
p3 = reinterpret_cast<B*>(p2);// 一样的,只是专门用于指针
std::cout << p3 << "\n";
p3->test();
}
void main3()
{
A a1;
B b1;
A *p1 = &a1;
A *p2 = &b1;
B *p3(nullptr);
//p3 = dynamic_cast<B*>(p2);
//dynamic必须要有虚函数,根据虚函数表转换,否则不能转换
//p3 = dynamic_cast<B*>(p1);转换失败,p1赋值的不是B类型转换不了
//转换失败为空
//类的空指针可以调用不调用数据的函数
std::cout << p3 << endl;
p3->test();
}
void main2()
{
A a1;
B b1;
A *p1 = &a1;
A *p2 = &b1;
#include <typeinfo>
cout << typeid(p1).name() <<" "<< typeid(p2).name() <<endl;
cout <<( typeid(p1) == typeid(p2))<<endl; // p1 p2 都是A *类型一样的
// 判断两个类型是不是一样的,一样为1,不一样为0
// 类内如果没有虚函数,p1 p2实例类型一样的,(因为看左边)
// 类内有虚函数,p1 p2实例类型不一样的,(因为看右边)
// 取指针指向的实例类型
cout << typeid(*p1).name() << " " << typeid(*p2).name() <<endl;
cout << (typeid(*p1) == typeid(*p2)) << endl;//重载的方式判定类型是否一致
}
#socket库C++模型设计
#关联强弱-注入-控制反转(核心:回调函数),具体看a16项目
#函数指针
函数名代表函数首地址,函数名就是函数指针
通过函数指针可以找到函数入口,执行函数
函数代码在代码区,函数指针 指向的是代码区的地址
int add(int a){}
typedef int(FUNC)(int); // 定义一个函数类型,sizeof()不可获取函数类型的大小
FUNC *p=add; // 用类型定义一个变量
void (*fp)(int) = &add; // 定义一个函数指针变量,该函数地址包含了函数三要素信息(名称(函数地址)、参数、返回值)
// 对函数名取不取地址都是一个样,取多少次地址都可以,&&&add,没意思最终都只是一个函数地址,这样只是兼容老版本,C很多瑕疵
***fp=add;
// 同时,对函数指针取值也是没意思,取多少次都可以,最终还是相当于fp
p(1); // 通过指针直接调用函数
#函数指针做参数
回调、解耦和、降低模块的耦合度
void fun(int (*p)(int a,int b)) // fun函数只有一个参数p(函数指针变量),a、b写不写都没事
{
}
typedef int (*FUNC)(int a,int b); // 申明一个类型,方便点
void fun(FUNC p)
{
p(4,5); // 调用函数
}
#函数指针正向调用
可以不用编译器去加载动态库,不需要lib不文件
自己用WindowsAPI去加载dll,自己查找方法,再调用(dll里面私有的成员也可以访问了,因为绕过编译器了)
具体查看a_0017项目
MFC工程,选择对话框程序,字符集设置为"未设置",不然"cfzssd"这样的字符串不支持,要加L->fun(L"afaff");
(右击工程-属性-配置属性-常规-字符集)
#函数指针反向调用
回调
用一个 全局变量/结构体/类 保存函数指针,用的时候再调即可(需要提供注入接口)
dll库内同样可以调用后人传入的函数(dll内定义函数指针接收调用即可)
#auto、高级for循环
auto n = 10;
auto a = 1.5; // 定义自动变量,auto,自动根据数确定类型
void fun(auto n){} //不行, 自动变量不能做函数参数类型
int cts;
auto ct = cts;
typeid(ct);
typeid(a); // 获取变量的类型,返回一个结构体
cout<<typeid(a).name();// 获取变量类型的字符串 const char *
decltype(a) aa = 10;// 获取a的数据类型,申明一个类型,并且建立一个该类型的变量
// Java里面的反射原理
int arr[]={1,2,4,5};
double arr2[]={4,1,2,3.2,4};
for(auto i:arr) // 遍历数组、vector、等等,自动匹配类型
{
cout<<i<<endl;
}
for(auto i:arr2)
{
cout<<i<<endl;
}
int arr3[2][3];
for(auto i:arr3) // 只循环一维[2],为一维的第一个数
{
cout<<i<<endl;
}
#自动变量做返回值,要用->申明返回类型,结合decltype使返回值类型更灵活
auto get(int a,double b)->decltype(a*b)
{
return a*b;
}
template<typename T1,typename T2>
auto get1(T1 a, T2 b)->decltype(a*b)
{
return a*b;
}
#函数模板
函数模板定义:由 模板说明 和 函数定义 组成
template <typename T> 或者template <class T> // 函数模板申明,在函数头前
// typename 与 class 一样的
// template关键字告诉编译器,现在进行泛型编程
// typename 告诉编译器,T是数据类型,申明一个类型,请不要报错
void print(T *arr,int len) // 函数定义,也可以使用明确的类型->len
{
for(int i=0;i<len;i++)
{
cout<<arr[i]<<endl;
}
}
template <typename T1,typename T2>
void fun(T1 a,T2 b)
{
cout<<a<<endl<<b<<endl;
}
template <typename T1> // 函数模板跟普通函数一样,可以重载
void fun(T1 a)
{
cout<<a<<endl;
}
void fun(int a) // 普通函数 与 函数模板都匹配函数调用时,优先调用普通函数
{
cout<<a<<endl;
}
int n=9;
fun(n); // 优先调用普通函数,不会调用上面的模板函数->void fun(T1 a)
fun(3.0); // 普通函数是参数int类型的,这时函数模板更匹配,会调用模板函数
fun('A'); // 模板函数不会隐式转换,普通函数可以,所以调用普通函数
fun<>(n); // 本来是调用普通函数的,想要匹配模板函数,则加一个空模板参数列表
或者fun<int>(n);
void main()
{
int arr[]={1,4,5,8,9};
// 方式1.自动推导类型,不用自己指定类型,编译器根据传入的数据判定类型
print(arr,5);
// 方式2.指定具体类型,就是将T替换为int类型
print<int>(arr,5);
int a=0;
char c='a';
fun<int,char>(a,c); // 位置对应,将T1替换为int类型,T2替换为char类型
// 函数模板不提供隐式的类型转换,如c变量不能做第一个参数->char类型的不可给int类型的,类型不同
}
-----------------
模板重载
#include<iostream>
#include<array>
using std::array;
template<typename T>
void showarray(array<T, 10> myarray, int n)
{
using namespace std;
cout << "TTTTT" << endl;
for (int i = 0; i < n; i++)
{
cout << myarray[i] << " ";
}
cout << endl;
}
template<typename T>
void showarray(array<T*, 10> myarray, int n)
{
using namespace std;
cout << "T*T*T*T*T*" << endl;
for (int i = 0; i < n; i++)
{
cout << *myarray[i] << " ";
}
cout << endl;
}
void main()
{
array<int, 10> intarray = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
array<int*, 10> pintarray; // 集合的大小都必须匹配模板的大小
for (int i = 0; i < 10; i++)
{
pintarray[i] = &intarray[i];
}
// 二级指针的,也会匹配上面的指针模板
// 因为几级指针只是给人看的,编译器不区分级数
array<int**, 10> ppintarray;
for (int i = 0; i < 10; i++)
{
ppintarray[i] = &pintarray[i];
}
showarray(intarray, 10);
showarray(pintarray, 10);
showarray(ppintarray, 10);
std::cin.get();
}
-----------------
#模板参数可以是函数指针
template<typename T,typename F>
T run(T t,F f)
{
return f(t);
}
int ff(int a)
{
return a*2;
}
void main()
{
cout << run(2, ff) << endl; // 函数指针做模板参数
system("pause");
}
#函数包装器
#include <functional>
using namespace std;
int ff(int a)
{
return a*2;
}
void main()
{
// 建立一个对象,封装一个函数
// fun本质就是一个函数指针
// int(int, int),返回值为int类型,两int类型的参数
// [] 表示要开辟一个函数, 后面是函数参数、函数体
function<int(int, int)> fun = [](int a,int b)
{
return a + b;
};
function<int(int)> fun1 = ff; // 也可以将已存在的函数赋值过去
fun(2,3); // 调用封装的函数
fun1(6); // 调用封装的函数
system("pause");
}
int main()
{
int res=0;
int n;
function<int(int, int)> fun = [&res](int a, int b)
{
res = a + b; // 要使用的外部变量在[]内 引用/变量 传进来,否则不能使用
// 可传多个,用逗号分开,[&res,n],引用(可修改)/变量(只读)
// [&] 表示引用所有的变量
// function<int(int, int)>,说明了返回值为int类型
// n = a * 2; // 不能使用外部变量n
// [=](int a, int b){};// =表示可以读外界变量(当前语句块内),不能尝试写
return res;
};
[res](int a, int b)mutable{} // mutable修饰[]内的参数,{}内可以修改了,但只是修改的一个副本,不改变外部的res值
// 或者,用自动变量
auto fun = [](int a,int b){return a+b; };
[](int a, int b){cout<<"xxx"<<endl;}(); // 后面加(),直接调用了
[](int a, int b)->int{cout<<"xxx"<<endl;} // ->int,表示返回值类型
[res](int a, int b)mutable->int{}
[](int a, int b)->decltype(a/b){cout<<"xxx"<<endl;}// 自动确定类型
auto fun = [](int a, int b)->int{cout<<"xxx"<<endl;} // 同样可以赋值给一个变量
cout << res << " " << fun(5, 6) << endl;
}
其实就是lamda表达式:
[&n](int a, int b){}
[捕获列表],(参数列表),{函数体}
[&n]->int(int a, int b){}
->:指定返回值类型
// vector遍历时就可传该表达式
vector<int> v(5);
for_each(v.begin(), v.end(), [](int n){cout<<n});// 也可以用lamda表达式
#类里面的lamda表达式
class test
{
public:
vector<int> myv;
int num;
public:
void add()
{
//要用类的成员变量需要传入进this指针
int x = 3;
//auto fun1 = [this,x](int v){ cout << v+x+this->num << endl; };
//=按照副本引用this、当前块语句局部变量,不可以赋值,但是可以读取
//&按照引用的方式操作局部变量,this,可以赋值,可以读
auto fun1 = [&](int v){ cout << v + x + this->num << endl; x = 3; };
for_each(this->myv.begin(), this->myv.end(), fun1);
}
};
#函数包装器 与 函数模板结合
#include<functional>
//函数包装器
//第一,设计执行接口,接口设计关卡(),计数
//第二,函数包装器依赖于函数模板,实现通用泛型
//第三,函数代码可以内嵌在另外一个函数,实现函数怀孕
//函数包装器,用于管理内嵌函数(后面直接定义函数),外部函数调用(赋值一个以存在的函数)
//函数包装器, T数据类型, F是函数
template<typename T,typename F>
T run(T v, F f)
{
static int count = 0;
count++;//计数器
return f(v);//函数传入参数
}
void main()
{
double db = 12.9;
function<double(double)> fun1 = [](double u)
{
return u * 2;
};
cout << run(db, fun1) << endl;//函数模板传入 函数封装器封装的函数
}
#引用包装器
template<typename T>
void fun(T n)
{
n++;
}
void main()
{
int a = 9;
fun(ref(a)); // fun函数参数 是改变不了a的值的,但用引用包装器处理后传入,就可以改变啦
// ref()只 函数模板 才有效,普通函数无效
cout << a << endl;
}
#using取别名
------------------
#include <iostream>
namespace space //隔离模板,避免冲突
{
// 申明一个泛型,定义泛型指针
template<class T>
using ptr = T*;//模板的简写,用于定义一个指针
}
int add(int a, int b)
{
return a + b;
}
typedef int(*ADD)(int a, int b);
using FUNC = int (*)(int a, int b);//别名,类似typedef
using co = std::ios_base::fmtflags;//using只可以用于简写数据类型,不能给函数取别名
void main()
{
ADD p=add;
FUNC func = add; // 定义变量
// 用模板定义一个指针变量,需指明类型
space::ptr<int> pint(new int(15));
std::cin.get();
}
------------------
#函数模板覆盖
template<typename T>
T run(T t)
{
return t;
}
// 上面的模板是通用的,但对于某一数据类型,不想要通用的,用以下方式覆盖,该类型的数据之后调用就用下面这个方法
// 模板为空,函数参数类型明确
template<>
int run(int a)
{
return a*2;
}
void main()
{
cout << run(2) << endl;
system("pause");
}
#可变参数 模板函数
void fun(){} // 只要递归,就必须定义这个同名的空函数,用于结束递归
// 当参数包中剩余的参数个数等于递归终止函数的参数个数时,就调用递归终止函数,则函数终止。
// 另外,读取方式,va_xxxx,一样可以
template<typename T,typename... Args>
void fun(T t,Args... as) // 带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数
{
cout << t << endl;
cout <<"---" << sizeof...(as) << endl; // 打印变参的个数,fun()没有传入参数,所以参数包为空,输出的size为0
fun(as...); // 递归访问可变参数的每一个元素,直到为空 就会遇到上面的空函数结束递归
// 参数包as...在展开的过程中递归调用自己,每调用一次参数包中的参数就会少一个(少的那个用着参数t了,可变参数里面就少了一个元素),
// 直到所有的参数都展开为止,当没有参数时,则调用非模板函数print终止递归过程。
}
void main()
{
fun(1,46,"xzxz");
system("pause");
}
//设计可以修改原来的数据的 T &value, Args &...args
//设计可以修改副本 T value, Args ...args
//设计不可以可以改原来的数据不可以修改副本 const T value, const Args ...args
//设计引用原来的数据不可以修改 const T &value, const Args &...args
省略号...在可变参数模板中有两种用途:
1.省略号出现形参名字左侧,声明了一个参数包。使用这个参数包,可以绑定0个或多个模板实参给这个可变模板形参参数包。
2.省略号出现包含参数包的表达式的右侧,则把这个参数包解开为一组实参,使得在省略号前的整个表达式使用每个被解开的实参。
template <class T>
void printarg(T t)
{
cout << t << endl;
}
template <class ...Args>
void expand(Args... args)
{
int arr[] = {(printarg(args), 0)...};
}
void main()
{
expand(1,2,3,4);
}
这个例子将分别打印出1,2,3,4四个数字。这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的,
printarg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式实现的关键是逗号表达式。
我们知道逗号表达式会按顺序执行逗号前面的表达式,比如:
d = (a = b, c);
这个表达式会按顺序执行:b会先赋值给a,接着括号中的逗号表达式返回c的值,因此d将等于c。
expand函数中的逗号表达式:(printarg(args), 0),也是按照这个执行顺序,先执行printarg(args),再得到逗号表达式的结果0。
同时还用到了C++11的另外一个特性——初始化列表,通过初始化列表来初始化一个变长数组,
{(printarg(args), 0)...}将会展开成((printarg(arg1),0), (printarg(arg2),0), (printarg(arg3),0), (printarg(arg4),0) ),
最终会创建一个元素值都为0的数组int arr[sizeof...(Args)]。
-----------
#函数模板本质
看编译出来的汇编代码可知道:
― 编译器并不是把函数模板处理成能够处理任意类型的函数
― 编译器从 函数模板 通过 具体类型 产生不同的函数(说白了,还是定义了对应的函数)
― 编译器会对函数模板进行两次编译
― 在声明的地方对模板代码本身进行编译
― 在调用的地方对参数替换后的代码进行编译
template<typename T>
void swap2(T &a, T &b)
{
T t = a;
a = b;
b = t;
}
void swap2(int &a, int &b)// 检测到模板调用后,编译器定义的函数,main()函数中几个类型调用,就定义几个,再进行第二次编译
{
T t = a;
a = b;
b = t;
}
void swap2(float &a, float &b)
{
T t = a;
a = b;
b = t;
}
int main()
{
int x = 1;
int y = 2;
float x1 = 1.0;
float y1 = 2.0;
// 编译器检测到,这里调用了函数模板,其实还是会去定义void swap2(int &a, int &b)函数(根据模板定义的)
swap2<int>(x, y);
swap2<float>(x1, y1);
return 0;
}
#模板元 编程
---------------------
#include<iostream>
//模板元把运行时消耗的时间,在编译期间优化
template<int N>
struct data
{
enum a{ res = data<N - 1>::a::res + data<N - 2>::a::res };
};
template<>
struct data<1>
{
enum a{res =1};
};
template<>
struct data<2>
{
enum a{res= 1 };
};
int getdata(int n)
{
if (n==1 || n==2)
{
return 1;
}
else
{
return getdata(n - 1) + getdata(n - 2);
}
}
void main()
{
const int myint = 40;
int num = data<myint>::res;//<>内部不可以有变量
std::cout << num << std::endl;
std::cout << getdata(40) << std::endl;
std::cin.get();
}
//主要思想
//
//利用模板特化机制实现编译期条件选择结构,利用递归模板实现编译期循环结构,模板元程序则由编译器在编译期解释执行。
//
//优劣及适用情况
//
//通过将计算从运行期转移至编译期,在结果程序启动之前做尽可能多的工作,最终获得速度更快的程序。
//1.以编译耗时为代价换来卓越的运行期性能
//
//模板元编程技术并非都是优点:
//1.可移植性较差,对于模板元编程使用的高级模板特性,不同的编译器的支持度不同。
//2.调试困难,元程序执行于编译期,
//3.编译时间长,通常带有模板元程序的程序生成的代码尺寸要比普通程序的大,
---------------------
#仿函数,用函数指针调用类内部函数
--------------------
参数用时 多了没事
不能用函数指针 执行类函数调用:p=t.add;
p=&Stu::add; // 取函数地址
#include<iostream>
#include<functional>//处理函数
using namespace std;
using namespace std::placeholders;
//仿函数,创建一个函数指针,引用一个结构体内部或者一个类内部的公有函数
struct MyStruct
{
void add(int a)
{
cout << a << endl;
}
void add2(int a,int b)
{
cout << a +b<< endl;
}
void add3(int a, int b,int c)
{
cout << a + b +c<< endl;
}
static void go(){}
};
int main1()
{
MyStruct struct1;
//创建函数指针,类结构体,数据私有,代码共享
//函数通过调用,调用需要传递对象名进行区分(因为类多了,不知道调用的是那个类的函数)
// 加MyStruct::就是用来区分类的
void(MyStruct::*p)(int a) = &MyStruct::add;// 只能定义函数指针
(struct1.*p)(1); // 通过函数指针调用类成员函数,需用到类对象
void(*p)(int a) = &MyStruct::add; // 不行
void(*p)(int a) = struct1.add; // 不行
// 静态函数,定义对应的指针事不用加MyStruct::修饰指针
// 且可以调用函数
void(*p2)() = &MyStruct::go;//静态函数,与对象没有关系
p2();
用以下方式就可以
//auto自动变量,地址,函数指针,bind绑定
//参数1引用内部函数,参数2实体对象,参数3函数参数占位,表示调用func时需要传入的参数
auto func = bind(&MyStruct::add, &struct1, _1);
auto func2 = bind(&MyStruct::add2, &struct1,_1, _2);
auto func3 = bind(&MyStruct::add3, &struct1, _1, _2,_3);
//bind()返回的是函数包装器functional
func(100);
func2(10, 20); // func2(10, 20,4,1,4);// 可以传任意多个参数,但只有前面的说明占位的才有效
func3(10, 20,30);
return 0;
}
#include <stdio.h>
#include <functional>
void foo(int a)
{
printf("a is %d\n", a);
}
void foo1(int a, double b, char c)
{
printf("a=%d, b=%g, c=%c\n", a, b, c);
}
void foo1(char c, double b)
{}
int main()
{
std::function<void(int)> func;
func = foo;
func(10);
std::function<void()> func2;
func2 = std::bind(foo, 100); // 绑定一个函数,参数用具体值,不用占位符(调用时不用传参数了)
func2(); // 调用时,不需要设置函数参数了,相当于函数转换,转换成无参函数了(其实是bind时设置了值)
std::function<void(char, double, int)> func3;
func3 = std::bind(foo1, std::placeholders::_3, // 3表示是foo1函数的第3个参数,序号几就表示是第几个参数
std::placeholders::_2,
std::placeholders::_1);
func3('c', 1.1, 100); // // a=100, b=1.1, c=c
// lambda和bind和function
Func = std::bind([](int a, int b){return a + b; }, std::placeholders::_1, std::placeholders::_2);
a = lFunc(100, 1000);
return 0;
}
--------------------
#去掉转义字符
void main()
{
std::string path =R"( "C:\Program Files\Tencent\QQ\QQProtect\Bin\QQProtect.exe")";
//R"()" 括号之间去掉转义字符
system(path.c_str());
system("pause");
}
#类模板
类模板定义包含:模板说明、类说明
当类的申明 与 函数 实现分开在.h .cpp文件时,使用时两个文件都要include进来
类模板 一样可重载
// 模板说明,同模板函数
template<typename T> // template<typename T=int>,可指定默认类型,使用时,可以不指定类型:A<> a1;
// 之后的每一处(外部实现函数时等等)都要template<typename T=int>,这样申明
// 总之,保持一致
class A // 类说明,类定义
{
public:
A(int a) // 同样可以用具体类型
{
this->a = a;
}
T getA()
{
return a;
}
void setA(T a)
{
this->a = a;
}
private:
T a;
};
// template<class T> //不指明类型也可以,必须申明同样的模板
// void printA(A<T> *p)
// 或者直接指明类型,就不用申明模板了
void printA(A<int> *p) // 做参数也必须指明类型
{
cout<<"打印a:"<<p->getA()<<endl;
}
void printA2(A<char> *p)
{
cout<<"打印a:"<<p->getA()<<endl;
}
class C : public A<int> // 继承的时候,A必须指明具体类型,用到A的地方都得指明
{ // 这时C不是模本类了,就是一个普通类
public:
C(int c, int a) :A<int>(a) // 函数初始化列表时,用到A,也必须指明类型
{
this->c = c;
}
protected:
private:
int c;
};
void printC(C *myc) // C是具体类型了,不用指定类型了
{
cout<<myc->getA()<<endl;
}
void main()
{
A<char> a1; // 使用时,必须指明类型,有默认类型了也必须有<>,里面不写类型-A<> a1;
A<int> b1(19);
b1.setA(10);
cout<<"打印a:"<<b1.getA()<<endl;
//printA(&b1);
system("pause");
}
#模板参数
--------------------
头文件
Array.h
#pragma once
template<class T,int n> // n就是模板的参数,
class Array
{
public:
Array();
~Array();
int size();
T get(int num);
void set(T data, int num);
public:
T *pt;
};
----------------
Array.cpp
实现文件
#include "Array.h"
template<class T, int n>// n不可以修改,只能读,不是类的内部成员
Array<T,n>::Array()
{
this->pt = new T[n]; // 直接使用模板参数
}
template<class T, int n>//每一个函数都必须模板
Array<T,n>::~Array()
{
delete[] this->pt;
}
template<class T, int n>//每一个函数都必须模板
int Array<T,n>::size()
{
return n; // 直接使用模板参数
}
template<class T, int n>//每一个函数都必须模板
T Array<T,n>::get(int num)//num是数组的下标
{
return *(this->pt + num);
}
template<class T, int n>//每一个函数都必须模板
void Array<T,n>::set(T data, int num)
{
*(pt + num) = data;
}
----------
main.cpp
#include <iostream>
#include "Array.h"
#include "Array.cpp"
// 使用类模板,当类的申明 与 函数 实现分开在.h .cpp文件时,使用时两个文件都要include进来
//用到模板类的地方,都要申明一样的模板,或者明确数据类型
//这也就是一个函数模板了
template<class T, int n>
void print(Array<T,n> &myarray)
{
for (int i = 0; i < myarray.size();i++)
{
std::cout << myarray.pt[i] << std::endl;
}
}
void main()
{
Array<int, 5 > myarray;
for (int i = 0; i < myarray.size(); i++)
{
myarray.set(i, i);
}
print(myarray); // 必须明确类型了才能调用
}
--------------------
#override、final
两个函数都结合virtual 虚函数用
class ye
{
public:
//标明子类不可以重写该函数,
//子类不能出现 函数名、参数一样的函数,参数不一样可出现
virtual void print() final
{
std::cout << "爷爷\n";
}
virtual void run()
{
std::cout << "爷爷run\n";
}
};
class ba:public ye
{
public:
//警示作用,强调我覆盖了父类的方法,必须是虚函数
//父类必须存在该函数,函数名、返回值、参数都一样
void run () override
{
std::cout << "爸爸run\n";
}
};
#类模板与友元
#include "iostream"
#include "stdlib.h"
using namespace std;
template<class T>
class Complex
{
public:
Complex(T Real = 0, T Image = 0);
Complex(T a);
//Complex Complex<T>::operator+(Complex &c1, Complex &c2 )
friend Complex operator+(Complex &c1, Complex &c2);
friend Complex operator-(Complex &c1, Complex &c2) // 在类里面实现函数,不用再说明模板,不用模板参数
{
Complex tmp(c1.Real - c2.Real, c1.Image - c2.Image);
return tmp;
}
void print();
public:
protected:
private:
T Real, Image;
};
// 当成员函数,申明 与 实现 分开时,还要 说明模板
// 申明与实现分开在.h .cpp文件时,使用时要包含.h,又要包含.cpp,
// 或者打成库 就不用包含.cpp了
template<class T>
Complex<T>::Complex(T Real = 0, T Image=0 ) // 且用到类时,Complex<T> 要加 模板参数列表
{
this->Real = Real;
this->Image = Image;
}
template<class T>
Complex<T>::Complex(T a)
{
this->Real = a; this->Image = 0;
}
template<class T>
void Complex<T>::print() // 构造函数,普通函数一样,分开实现了 就要说明模板
{
cout<<this->Real<<" + "<<this->Image<<endl;
};
/*
无法解析的外部符号 "
class Complex<float> __cdecl operator+(class Complex<float> &,class Complex<float> &)
" (??H@YA?AV?$Complex@M@@AAV0@0@Z),该符号在函数 _main 中被引用
1>E:\01-work\10-就业班0529\day16\函数模板课堂操练\Debug\函数模板课堂操练.exe :
fatal error LNK1120: 1 个无法解析的外部命令
========== 生成: 成功 0 个,失败 1 个,最新 0 个,跳过 0 个 ==========
*/
/*
template<class T> // 但是友元函数,按上面的路子不行,会出现以上错误,是因为在使用Complex<float> c3 = c1 + c2;时
// 进行第二次编译,生成的代码不一样了
Complex<T> operator+(Complex<T> &c1, Complex<T> &c2 ) // 所以没办法,友元函数就得在类里面实现,
// 如:上面的friend Complex operator-(Complex &c1, Complex &c2)的重载
{
Complex<T> tmp(c1.Real+c2.Real, c1.Image + c2.Image);
return tmp;
}
如果实现要写在外面,则在类里面申明时,friend Complex operator-<T>(Complex &c1, Complex &c2)
在重载后面加上<T>
对于普通函数的友元声明
template<class T> // 由于下面的类定义用到 模板,申明类时,要说明模板
class Complex;
//由于申明函数时用到 后面定义的类,在前面申明一下类
template<class T>
Complex<T> addA(Complex<T> &c1, Complex<T> &c2); // 还要提前声明函数
template<class T>
class Complex
{
public:
friend Complex addA<T>(Complex &c1, Complex &c2); // 函数名后 照样<T>
};
template<class T> // 实现的时候正常实现
Complex<T> addA(Complex<T> &c1, Complex<T> &c2 )
{
Complex<T> tmp(c1.Real+c2.Real, c1.Image + c2.Image);
return tmp;
}
*/
void main()
{
Complex<float> c1(1.0, 2.0);
Complex<float> c2(3.0, 4.0);
c1.print();
//Complex<float> c3 = c1 + c2;
//c3.print();
Complex<float> c4 = c1 - c2;
c4.print();
}
#类模板与友元类
// 用到的类,提前申明
template <class T> class myclass;
template<class T> class runclass;
template <class T>
class myclass
{
public:
myclass(T t) :x(t)
{
}
friend class runclass<T>;//声明友元类,后面必须加<T>
private:
T x;
};
template<class T> // 用到了模板类,需申明模板
class runclass
{
public:
void print(const myclass<T> & my){}
};
void main()
{
myclass<double> my1(19.8);
runclass<double> run1;
run1.print(my1);
}
总之,与友元相关的模板 1.提前申明类、函数,2.记得加<T>
#类模板的继承
//类模板 继承 类模板
//普通类 继承 类模板
//类模板 继承 普通类
template<class T> //抽象模板类
class myclass
{
public:
T x;
myclass(T t) :x(t)
{
}
//virtual void print()
//{
// std::cout << x << std::endl;
//}
virtual void print() = 0; // 类模板内可以有虚函数->抽象类模板
};
//类模板 继承 类模板
(一个类继承模板类,不明确数据类型,保持模板,还得申明模板)
template<class T>
class newclass :public myclass<T>
{
public:
T y;
newclass(T t1, T t2) :myclass(t1), y(t2)
{
}
void print()
{
std::cout << x <<" " <<y<< std::endl;
}
};
void main()
{
myclass<int > *p=new newclass<int>(10,9); // 定义指针、new对象都要指明类型
p->print();
}
class xyz
{
public:
int x;
int y;
int z;
xyz(int a, int b, int c) :x(a), y(b), z(c)
{
}
void print()
{
std::cout << x << y << z;
}
};
// 模板类 继承 普通类,依旧是个模板类
template<class T>
class newxyz :public xyz
{
public:
T a;
newxyz(T t1,int a1,int b1,int c1) :xyz(a1,b1,c1),a(t1)
{
}
void print()
{
std::cout << "Ta=" << a << std::endl;
std::cout << x << y << z << std::endl;
}
};
//普通类 继承 模板类,即指明类型即可,不再是模板类,使用时不用指明类型
class classrun:public newxyz<int>
{
public:
int d = 1000; // 构造时,初始化列表用到类模板名称了,也要指明类型
classrun(int a2, int b2, int c2, int d2) :newxyz<int>(a2,b2,c2,d2)
{
}
void print()
{
std::cout << d << x << y << z << a;
}
};
void mainy()
{
classrun run1(1, 2, 3, 4);// 继承时实例化了,不用再指明类型
run1.print();
}
#类模板遇上static
template<typename T>
class A
{
public:
static int m_a;
protected:
private:
T a1;
};
// 静态成员变量 初始化在类外了,要说明模板、写模板参数列表
template<typename T>
int A<T>::m_a=0;
#include "iostream"
using namespace std;
void main()
{
// 根据具体类型(int),同一个类型的,定义了一个类,同一类型的(a1 a2 a3),共用同一个静态变量
A<int> a1;
A<int> a2;
A<int> a3;
// char类型,又定义了一个类,里面的静态变量 与上面的没关系(不是同一个变量,地址不同),b1 b2 b3,共用一个静态变量
// 静态函数也是一样的,同类型的共享,不同类型的独立
A<char> b1;
A<char> b2;
A<char> b3;
a1.m_a ++;
b3.m_a = 199;
cout<<a3.m_a<<endl; // 输出 1,或者A<int>::m_a;这样访问,指明类型
cout<<b2.m_a<<endl; // 输出 199
}
#类模板 做 类模板参数
------------------------
#include<iostream>
#include<string>
#include <typeinfo>
using namespace std;
template<typename G>
class ren
{
public:
G name;
ren(){} // 一定搞个无 无参构造函数,否则 创建people1 时,people构造函数中的 t1x = t1;执行不通过,只能在初始化列表中初始化
// error: no matching function for call to ‘ren<int>::ren()’
ren(G t) :name(t){}
};
template<typename S>
class Daren
{
public:
S name;
Daren(S t) :name(t){}
};
//使用 类模板 当作 模板参数
//T1 就是一个类模板,其泛型类型是T
// T1前必须是class,申明模板参数是一个类
// template<typename TT> class T1 中,template<typename TT>是说明T1类的泛型,TT可省略(申明,参数可省)
template<typename T, template<typename TT> class T1 >
class people
{
public:
T1<T> t1x ;
// T1<TT> t1x2 ; // 使用不到TT,TT是可省略的,只能使用people申明的泛型
people(T1<T> t1)//:t1x(t1) // 无 无参构造函数 在初始化列表中初始化
{
t1x = t1;
// t1是具体实例,指明了类型的了,不用<>带泛型
cout << typeid(t1).name() << endl;
// T1 没有指明类型,用<>带泛型
cout << typeid(T1<T>).name() << endl;
cout << t1.name << endl;
}
};
int main()
{
ren<string> ren1("hello8848"); //基本数据类型
people<string,ren> people1(ren1);//嵌套的类模板
// 泛型类 可以做为另个泛型的类型
Daren<ren<string>> dr(ren1);
cout << people1.t1x.name << endl;
cout << ren1.name << endl;
return 0;
}
/**定义模版类*/
template <typename T, typename E>
class Node {
public:
void sayHello(void) {
std::cout << "hello !" << std::endl;
}
};
template <typename T, typename E>
class Node2 {
public:
T t;
E e;
Node2(){} // 一定搞个参数构造函数,否则下面创建no3通不过
Node2(T t_,E e_)//:t(t_),e(e_)
{
this->t = t_;
this->e = e_;
}
void sayHello(void) {
std::cout << "hello !" << std::endl;
cout << t << " " <<e <<endl;
}
};
template <class G>
struct type_addition {
typedef G value_type;
typedef G* pointer;
typedef G& reference;
typedef const G* const_pointer;
typedef const G& const_reference;
};
/**创建模版类,其中模版的参数N也是一个模版类,且默认为Node<typanem T,typename E> 的模版*/
// 申明的模板,可以不带名字
template <
typename T,
template<typename, typename> class N = Node /** 重点! */>
class NodeOperator {
public:
// 如果不加这个关键字,编译器就不知道type_addition<T>::value_type到底是个什么东西?可能是静态成员变量,也有可能是静态成员函数,也有可能是内部类。
// typedef创建了存在类型的别名,而typename告诉编译器type_addition<T>::value_type是一个类型而不是一个成员。
// (因为type_addition<T>::value_type 就是一个使用typedef定义的类型了 )
typedef typename type_addition<T>::value_type value_type;
typedef N<T, value_type> node_type; /** 模版类参数的完全特化,其参数可使用外部模版类的参数 */
node_type node;
};
template <
typename T,
template<typename, typename,typename> class N = Node /** 重点! */>
class NodeOperator2 {
public:
};
int main(void) {
NodeOperator<char> no1;
no1.node.sayHello();
NodeOperator<int> no2;
no2.node.sayHello();
//----------,不使用默认类型了
Node2<int,int> n2(88,99);
n2.sayHello();
NodeOperator<int,Node2> no3;
no3.node.sayHello();
return 0;
}
------------------------
#类嵌套、类模板嵌套
#include<iostream>
template<class T>
class myclass
{
public:
class newclass
{
public:
int num;
}new1;//定义的方式
newclass new2;
template<class V> // 内部类的模板申明
class runclass
{
public:
V v1;
}/*<int> rr1*/;//类模板后面不可以直接初始化
runclass<T> t1; // myclass的类型 直接作为 内部类的类型
runclass<double> t2;
};
void main()
{
myclass<int > my1;
my1.new1.num = 10;
my1.t1.v1 = 12;
my1.t2.v1 = 12.9;
}
#类包装器
#include<iostream>
template<typename T, typename F>
T run(T t, F f) //包装器,实现一个操作接口,操作多个类的方法
{ // 同时在类里面要操作()操作符
return f(t);
}
class myclass
{
public:
int num;
myclass(int data) :num(data)
{}
int operator ()(int X)
{
return X*num;
}
};
class myclassA
{
public:
int num;
myclassA(int data) :num(data)
{}
int operator ()(int X)
{
return X - num;
}
};
void main()
{
myclass my1(5);
std::cout << run(101, my1) << std::endl;
std::cout << run(101, myclassA(51)) << std::endl;
std::cin.get();
}
#类成员函数指针
#include<iostream>
#include<stdio.h>
//类成员函数指针,类成员函数指针数组,类成员二级函数指针
class com
{
private:
int a;
int b;
public:
com(int x, int y) :a(x), b(y)
{
}
int jia(int a, int b)
{
return a + b;
}
int jian(int a, int b)
{
return a - b;
}
int cheng(int a, int b)
{
return a * b;
}
int chu(int a, int b)
{
return a / b;
}
};
void main1x()
{
com com1(100, 20);
auto fun1 = &com::jia;
// 定义类成员函数指针
int(com::*p)(int, int) = &com::jia;
// 调用函数
(com1.*p)(10, 20);
}
typedef int(com::*P)(int, int);
void main()
{
com com1(100, 20);
//P fun1[4] = { &com::jia, &com::jian, &com::cheng, &com::chu };
//类成员函数指针数组
int(com::*fun1[4])(int, int) = { &com::jia, &com::jian, &com::cheng, &com::chu };
for (int i = 0; i < 4; i++)
{
(com1.*fun1[i])(10, 20);
}
int(com::**funp)(int, int) = fun1;//指向类成员函数指针的指针
for (; funp < fun1 + 4; funp++)
{
(com1.**funp)(10, 20);
}
for (int i = 0; i < 4; i++)
{
auto func = fun1[i];
std::cout << typeid(func).name() << std::endl;
printf("%p", func);
}
}
----------
#联合体union,
C++的联合体就是一个封装的类(本质就是一个类),具备结构体所有的功能,因此可以有方法,有权限(默认公有)
但没有继承
#智能指针
for (int i = 0; i < 10000000; i++)
{
double *p = new double;//为指针分配内存
std::auto_ptr<double> autop(p);
//创建智能指针管理指针p指向内存
// 只能初始化一个指针变量,不能直接接收new 返回的内存地址
//智能指针 ,不用去管理释放了,出了{}就自动释放,没有地方使用那片空间了也会释放
//delete p;
}
C++ 11的智能指针
for (int i = 0; i < 10000000;i++)
{
// 可以直接接收new 返回的内存地址
// 没有变量执行、使用那片空间了就会释放,出了{}也会释放
std::unique_ptr<double> pdb(new double);
//double *p = new double;
}
#多线程
---------------------
#include <thread>
#include <vector>
#include <iostream>
using namespace std;
using namespace std::this_thread; // 线程API所在的命名空间
void fun()
{
cout << "xxx" << endl;
}
void fun1(int n)
{
cout << n << endl;
}
void main()
{
auto n = thread::hardware_concurrency();// 得到计算机有几个CPU
get_id(); // 获取当前线程ID
// 新建一个线程
// 参数是子线程要执行的代码
// 一建立线程,这里就开始执行了
thread trd(fun);
thread trd1(fun1,100); // 后面是可变参数,可任意多个,将会传入到fun1()的参数
// 加入线程执行,直到trd线程执行完,当前线程才继续往下执行
// 如果子线程没执行完,主线程就结束了 程序会abort()终止
// trd.join();// 其实已经在执行了,只是在这里等待执行完
trd.detach();// 脱离主线程单独执行,主线程退出了也在执行
// 多线程调试,冻结暂停线程(线程窗口-右击线程)
system("pause");
#include <mutex>
mutex mtx;
mtx.lock();// 加锁
mtx.unlock();// 解锁
}
---------------------
#静态断言
-----------------
#include <stdio.h>
#include<assert.h>
#include<iostream>
using namespace std;
#define N 10
void main()
{
int num = 100;
cout << num << endl;
// 宏
cout << __FILE__ << endl; // 当前程序所在的源代码文件的全名
cout << __LINE__ << endl; // 当前程序执行到哪一行
cout << __DATE__ << endl; // 获取程序运行到这的当前日期
cout << __TIME__ << endl; // 获取程序运行到这的当前时间
cout << __FUNCTION__ << endl; // 程序当前运行到 所在的函数
}
#define M
void main1()
{
char num = 10;
//字节>4
#ifdef M // 调试的时候才打开,正式时 关闭
// 静态断言,相当于 添加 编译规则,调试、测试的利器
// 期望的是>=4,如果小于4,就报错编译都编译不过
// static_assert(sizeof(num) >= 4, "yincheng error");
// 代码不可能不出错,最重要的是调试技能(经验的见证)
#endif
//调试代码,迅速代码错误在哪一行
}
-----------------
#输入输出流
iostream.h 包含操作所有输入/输出流所需的基本信息
含有cin、cout、cerr、clog对象,提供无格式和格式化的I/O
iomanip.h 用于指定数据输入输出的格式
fstream.h 处理文件信息,包括建立文件,读/写文件的各种操作接口
(1) cin istream 类的对象,通常连向键盘,可以重定向
(2) cout ostream 类的对象,通常连向显示器,可以重定向
(3) cerr ostream 类的对象,连向显示器。不能重定向
(4) clog ostream 类的对象,连向打印机。不能重定向
#include<iostream>
#include <iomanip>
using namespace std;
// 错误输出
void main1()
{
// 专门用于输出错误信息的
cerr << "error"<< endl;
}
// 输入
void main2()
{
char c;
char str[5] = { 0 };
int count = 0;
// cin >> c;// 输入一个字符,空格、回车分割,空格、回车不能输入
// 重载了所有基本类型的输入
// cin.get(c); // 输入任意字符,空格、回车可输入,回车结束
// c = cin.get();
// 输入一行字符串,最多输入5个字符(包含结尾符\0,即最多只能输入4字符),遇到指定字符'0'结束输入,默认是'\n'
// cin.get(str, 5,'0');
// 输入一行字符,空格、回车可输入,回车结束
// 最多输入指定个字符,不会预留\0,全部占满,所以要自己留一个\0位置
// cin.read(str,4);
// 输入一行,最多输入4字符(留了一个作届结束符)
// 空格可输入,遇到指定字符结束输入,默认是\n,结束符不可输入
// cin.getline(str,5,'\n');
// get ( ) 不从流中提取终止字符,终止字符仍在输入流中
// getline ( ) 从流中提取终止字符,但终止字符被丢弃
// cin.ignore(2);// 忽略、不读取接下来输入的2字符
// cin.getline(str, 5, '\n');
// c = cin.peek();// 返回流中下一个字符,但不从流中删除,不干扰读取
// cin.getline(str, 5, '\n');
// cout << c << endl;
// cin.getline(str, 5, '\n');
// cin.get(c);
// count = cin.gcount(); // 统计上一次读取,读取到的自字符数
// cout << count << endl;
//一般用于文件,文本、二进制都行
//cin.seekg(0);// 移动输入流指针,参数long类型,默认是当前位置-cur
//cin.seekg(2,ios::end);// beg、cur、end,负数是后退,参数2是指定参考点
long pos = cin.tellg();// 获取当前位置
cout << str;
}
// 输出
void main3()
{
char c = '9';
char str[] = "123456";
// cout.put(c);// 输入任意一个字符
// cout.write(str,7);// 输入字符串指定前n个字符
// cout.flush();// 刷新缓冲区,输出去
//一般用于文件,移动的前提是有那么长的文件长度
//文本、二进制都行
// seekp(streampos pos); //移动输入流指针
// seekp(streamoff off, ios::seek_dir dir);
// longtellp();// 返回当前指针
}
//流错误状态
void main4()
{
char c = '9';
char str[] = "123456";
// goodbit 0x00 状态正常
// eofbit 0x01 文件结束符
// failbit 0x02 I / O操作失败,数据未丢失,可以恢复
// badbit 0x04 非法操作,数据丢失,不可恢复
/*
int eof() const; 返回eofbit状态值。文件结束符时返回1,否则返回0
int fail() const; 返回failbit状态值
int good() const;
int operator void *() ; eofbit、failbit和badbit都没有被设置,则返回1
int bad() const;
int operator !() ; eofbit、failbit或badbit其中一个被设置,则返回1
int rdstate() const; 返回状态字
void clear( int nState = 0 ); 恢复或设置状态字
*/
}
//格式控制
void main5()
{
// ios 控制格式的函数
/*
状态标志 值 含义 输入/输出
skipws 0X0001 跳过输入中的空白 I 二进制0000000000000001
left 0X0002 左对齐输出 O 二进制0000000000000010
right 0X0004 右对齐输出 O
internal 0X0008 在符号位和基指示符后填入字符 O
dec 0X0010 转换基制为十进制 I/O
oct 0X0020 转换基制为八进制 I/O
hex 0X0040 转换基制为十六进制 I/O
showbase 0X0080 在输出中显示基指示符 O
showpoint 0X0100 输出时显示小数点 O
uppercase 0X0200 十六进制输出时一律用大写字母 O
showpos 0X0400 正整数前加“+”号 O
scientific 0X0800 科学示数法显示浮点数 O
fixed 0X1000 定点形式显示浮点数 O
unitbuf 0X2000 输出操作后立即刷新流 O
stdio 0X4000 输出操作后刷新stdout 和 stdree O
*/
/*
long flags( long lFlags ); 用参数lFlags更新标志字
long flags() const; 返回标志字
long setf( long lFlags ); 设置lFlags指定的标志位
long setf( long lFlags, long lMask ); 将lMask指定的位清0,然后设置lFlags指定位
long unsetf( long lMask ); 将参数lMask指定的标志位清0
int width( int nw ); 设置下一个输出项的显示宽度为nw
char fill( char cFill ); 空白位置以字符参数cFill填充
int precision( int np ); 用参数np设置数据显示精度
*/
/*
char *s = "Hello";
cout.fill('*'); // 置填充符
cout.width(10); // 置输出宽度
cout.setf(ios::left); // 左对齐
cout << s << endl;
cout.width(15); // 置输出宽度
cout.setf(ios::right, ios::left); // 清除左对齐标志位,置右对齐
cout << s << endl;
*/
/*
int a, b, c;
cin.setf(ios::dec, ios::basefield); // 置十进制输入
cin >> a;
cin.setf(ios::hex, ios::basefield);// 置十六进制输入
cin >> b;
cin.setf(ios::oct, ios::basefield);// 置八进制输入
cin >> c;
cout.setf(ios::dec, ios::basefield);
cout << "a = " << a << " b = " << b << " c = " << c << endl;
cout.setf(ios::hex, ios::basefield);
cout << "a = " << a << " b = " << b << " c = " << c << endl;
cout.setf(ios::oct, ios::basefield);
cout << "a = " << a << " b = " << b << " c = " << c << endl;
*/
double x = 0.1234567891564654;
cout.setf(ios::fixed | ios::showpos);// 定点输出,显示 +,即按小数输出,默认保留6位小数
cout.precision(12);// 设置保留几位小数,不足的补0
cout << x << endl;
// 清除原有设置,科学示数法输出
// ios::scientific根据方便,哪个短,按哪个形式输出
//x = 0.1234567891564654;// 小数点的形式
//x = 100000000000000000; // 会选择科学计算
cout.setf(ios::scientific, ios::fixed | ios::showpos);
cout << x << endl;
}
// istream和ostream类定义了一批函数,作为<< 、 >> 控制I/O格式
void main6()
{
/*
控制符 功能 输入/输出
endl 输出一个新行符,并清空流 O
ends 输出一个空格符,并清空流 O
flush 清空流缓冲区 O
dec 用十进制表示法输入或输出数值 I/O
hex 用十六进制表示法输入或输出数值 I/O
oct 用八进制表示法输入或输出数值 I/O
ws 提取空白字符 I
*/
int a;
cin >> oct >> a; // 八进制输入
cout << oct <<a<<endl;
cin >> hex >> a;
cout << hex << a << endl;// 十六进制输出
cout << ends ;
}
// iomanip的控制符
void main()
{
/*
控制符 功能 输入/输出
resetiosflags ( ios::lFlags ) 清除lFlags指定的标志位 I/O
setiosflags ( ios::lFlags ) 设置lFlags指定的标志位 I/O
setbase ( int base ) 设置基数,base = 8,10,16 I/O
setfill ( char c ) 设置填充符c O
setprecision ( int n ) 设置浮点数输出精度 O
setw ( int n ) 设置输出宽度 O
*/
int k = 618;
cout << setw(10) << setfill('#') << setiosflags(ios::right) << k << endl;
cout << setw(10) << setbase(8) << setfill('*')
<< resetiosflags(ios::right) << setiosflags(ios::left) << k << endl;
cout << setiosflags(ios::fixed | ios::showpos);
cout << setprecision(5) << k << endl;
cout << resetiosflags(ios::fixed | ios::showpos) << setiosflags(ios::scientific);
cout << setprecision(3) << 1.2345 << endl;
system("pause");
}
--------------------------
#字符串流
#include<iostream>
#include<sstream>
#include<string>
using namespace std;
void main1()
{
string mystring("china google microsoft 12.9 123 A");
istringstream input(mystring);
//创建一个字符串扫描流,指定指定字符串
string str1, str2, str3;
double db;
int num;
char ch;
// 从流内读取数据(从字符串中读取字符)
// 空格作为分割
input >> str1 >> str2 >> str3 >> db >> num >> ch;
}
void main()
{
ostringstream myout;
char str1[50] = "a1234567b";
// 向流写数据(向字符串写数据)
myout << "a1234b" << 123 << 234.89 << 'h' << str1 << endl;
cout << myout.str();// 读取写好的数据
//char str[100] = { 0 };
//自己指定一个字符串缓冲区
//本意想通过str直接获取写好的字符
//ostringstream myout(str,sizeof(str));
//cout <<str; // 但似乎没有改变str的数据,ostrstream的对象才能输出
}
// sstream字符串流的新版本:strstream
#include<strstream>
void main233()
{
char str[100] = { 0 };
ostrstream MYOUT(str, sizeof(str));//初始化,ostrstrean给char
char str1[50] = "a1234567b";
MYOUT << "a1234b" << str1 << ends;
cout << MYOUT.str() << endl;
cout<<str;// 指定的空间填充数据了,strstream的对象可输出
}
----------------------
#include <iostream>
#include <sstream>
#include <string>
#include <stdlib.h>
using namespace std;
void mainA()
{
// 可向字符串读、写的流
stringstream mystr;//字符串进行输入,
// stringstream mystr("ABC");// 初始化一些数据,但输入时会被覆盖
mystr.put('X').put('Y');//字符输入
mystr << "ZXCV";//字符串输入
cout << mystr.str();// 获取积累的字符
string str = mystr.str();//定义字符串接受值
char ch; //从字符串内部读取一个字符
mystr >> ch;
cout << "\n";
cout.put(ch);
cout << "\n";
cout << mystr.str();
std::cin.get();
}
--------------------
#文件流
void open (const char *,int mode,int = filebuf::openprot);
mode的取值:
标识常量 值 意义
ios::in 0x0001 读方式打开文件
ios::out 0x0002 写方式打开文件
ios::ate 0x0004 打开文件时,指针指向文件尾
ios::app 0x0008 追加方式
ios::trunc 0x0010 删除文件现有内容
ios::nocreate 0x0020 如果文件不存在,则打开操作失败
ios::noreplace 0x0040 如果文件存在,则打开操作失败
ios::binary 0x0080 二进制方式打开,默认为文本方式
openprot的取值:
filebuf::openprot 适应MS-DOS 模式
filebuf::sh_compat 适应MS-DOS 模式
filebuf::sh_none 无模式
filebuf::sh_read 读模式
filebuf::sh_write 写模式
用第一种方式打开文件:
打开一个已有文件datafile.dat,准备读:
ifstream infile ; // 建立输入文件流对象
infile.open( "datafile.dat" ,ios::in ); // 连接文件,指定打开方式
打开(创建)一个文件newfile.dat,准备写:
ofstream outfile ; // 建立输出文件流对象
outfile.open( "d:\\newfile.dat" ,ios::out ); // 连接文件,指定打开方式
// 打开失败,outfile被置为NULL
第二种方法打开文件:一建立就打开了,打开失败返回NULL
ifstream infile ("datafile.dat" , ios::in);
ofstream outfile ("d:\\newfile.dat" ,ios::out);
fstream rwfile ("myfile.dat" , ios::in|ios::out);
默认都是文本形式读写
//输出到文件
void main1()
{
ofstream fout;//ofstream.输出文件
fout.open("C:\\1.txt");//打开文件
fout << "1234abcdef";//写入文件
// 格式控制一样试用
fout << setw ( 10 ) << a << endl ;
fout << resetiosflags ( ios :: right ) << setiosflags ( ios :: left )
<< setfill ( '#' ) << setw ( 10 ) << a << endl ;
fout << resetiosflags ( ios :: left ) << setiosflags ( ios :: right )
<< setprecision ( 5 ) << setw ( 10 ) << 12.34567890 << endl ;
fout.close();
}
// 从文件输入
void main2()
{
ifstream fin("C:\\1.txt");//创建读取文件的流
char str[50] = { 0 };
fin >> str;//读取
fin.close();
cout << str;
//按照行来读取,指定最多读取数(最多读取49,结束符),参数3可指定结束符(默认是回车)
//返回的还是流对象,支持链式编程
char str[50] = { 0 };
fin.getline(str, 50);
}
// 输入 输出1
void main5()
{
fstream fio("C:\\3.txt", ios::in | ios::out);
fio << "锄禾日当午" << endl;//写入文件
fio << "地雷买下土" << endl;//写入文件
fio << "谭胜来跳舞" << endl;//写入文件
fio.close(); // 要关闭,再打开,才能读
fio.open("C:\\3.txt", ios::in | ios::out);
for (int i = 0; i < 4; i++)
{
char str[50] = { 0 };
fio.getline(str, 50);
cout << str << endl;
}
fio.close();
}
// 输入 输出2
void main6()
{
fstream fio("C:\\4.txt", ios::in | ios::out);
fio << "锄禾日当午" << endl;//写入文件
fio << "地雷买下土" << endl;//写入文件
fio << "谭胜来跳舞" << endl;//写入文件
fio << "炸成250" << endl;//写入文件
fio.seekg(ios::beg);//文件指针 ios::beg开始,然后就可以读了
// while( !inf.eof() ) // 判断是否结尾
for (int i = 0; i < 4; i++
{
char str[50] = { 0 };
fio.getline(str, 50);
cout << str << endl;
}
fio.close();
}
//写入文件,不需要转换为字符串(会自动转换为字符串)
//读取的时候,不需要吧字符串转换为其他类型的操作(会自动从字符串类型转换为具体类型)
void main()
{
ofstream fout;
fout.open("C:\\X.txt");
// 记得写的时候用空格 隔开,读取的时候就方便了
fout << "ABC" << " " << 123.23 << " "<<134<<" " << 'ch' << endl;//打印到文件
fout.close();
ifstream fin("C:\\X.txt");//创建读取文件的流
char str[10] = { 0 };//读取字符串
double num = 0;
int n=0;
char ch = '\0';
fin >> str >> num >>n >> ch; // 读取数据
std::cout << str << "\n" << num << "\n" << ch;
}
void main10()
{
//追加
ofstream fout("C:\\40.txt",ios::app);
fout << "天下英雄,谭胜第一";
fout.close();
}
#二进制形式读写,与C的read()、write()一样
#include<iostream>
#include<fstream>
using namespace std;
struct MyStruct
{
char *p = "北京是帝都";
int num = 20;
double db = 10.98;
char ch = 'a';
};
void main()
{
MyStruct my1;
ofstream fout("C:\\bin.bin", ios::binary);// 指明二进制形式
//get(),put()也行
fout.write((char *)&my1, sizeof(my1));//
//第一个参数是要写入文件的内存的首地址,
//第二个参数是要写的数据长度
// 写进去就占 sizeof(my1)个字节,整数占4字节,直接写入二进制
fout.close();
ifstream fin("C:\\bin.bin", ios::binary);
MyStruct newmy1;
fin.read((char*)&newmy1, sizeof(newmy1));
//保存文件读取到内存,内存首地址
//长度
cout << newmy1.p << endl;
fin.close();
}
--------------------------
#异常
try、catch是一个整体,之间不能插杂其他语句
class MyE{};
void main()
{
int n = 0;
cin >> n;
try
{
throw 10; // 抛出一个整数
}
catch (int n) // 捕获对应类型
{
cout << n << "xxxx" << endl;
}
try
{
throw "xxx"; // 抛出字符串
}
catch (const char *)
{
cout << n << "字符串" << endl;
}
try
{
if (n == 0)
{
throw MyE();// 抛出异常,抛出一个对象,可以抛出任何类型实体
}
int a = 8 / n;
}
catch (MyE e)// 捕获异常,变量名可以不要catch (MyE ),但用不了实例了
{
cout << "有一次" << endl;
}
system("pause");
}
#异常继承,异常虚函数
class A
{
public:
virtual void print()
{
cout << "出现错误!" << endl;
}
};
class B:public A
{
public:
void print()
{
cout << "出现错误B!" << endl;
}
};
class C :public A
{
private:
int a;
public:
C(int n)
{
a = n;
}
void print()
{
cout << "出现错误C!" << a << endl; // 与一般类一样的用,携带数据,访问数据
}
};
void main()
{
int n = 0;
cin >> n;
try
{
if (n == 1)
{
throw 10;
}
else if (n == 2)
{
throw A();
}
else if (n == 3)
{
throw B();
}
else if (n == 4)
{
throw C(999999);
}
}
catch (int n) // 捕获对应类型
{
cout << n << "xxxx" << endl;
}
catch (C c)
{
c.print();
}
catch (A a) // 父类在前,会拦截后面子类的异常
{
a.print();
}
catch (B b)
{
b.print();
}
system("pause");
}
#模板内的异常
template <class T>
class Array
{
private:
int size;
T *p;
public:
class A
{
public:
virtual void print()
{
cout << "wrong " << typeid(T).name(); // 在异常内输出 出错误的类型
}
};
class B :public A
{
public:
void print()
{
cout << " B wrong " << typeid(T).name();;
}
};
class C :public A
{
public:
void print()
{
cout << " C wrong "<< typeid(T).name();;
}
};
Array(int n)
{
if (n==1)
{
throw A();
}else if (n==2)
{
throw B();
}else if (n==3)
{
throw C();
}else
{
p = new T[n];
size = n;
}
}
};
void main()
{
try
{
Array<double> mya(3);
}
catch (Array<double>::B & b) // 在模板内的异常(/在类内部定义的异常),指明模板类型,/用::作用符指明哪个类即可
{
b.print();
}
catch (Array<double>::A & a)
{
a.print();
}
system("pause");
}
#异常与退栈
class ForTest
{
public:
~ForTest() //析构函数
{
cout << "ForTest类析构函数被调用\n";
}
};
float function3(int k) //function3中可能有异常
{
if (k == 0)
// 该异常是所有标准 C++ 异常的父类
throw exception("function3中出现异常\n"); //抛掷异常类对象,程序不往下执行,函数返回(退栈)
//如果不抛异常,直接exit()退出会导致堆内存泄露
else
return 123 / k;
}
void function2(int n)
{
ForTest A12;
function3(n); //返回到调用地方,还没有捕获异常,不往下执行,继续返回,函数退栈
cout << "xxxxxxxx" << endl; // 执行不到
}
void function1(int m)
{
ForTest A11;
function2(m); //
}
void main()
{
try
{
function1(0); //
}
catch (exception &error)
{ //打印异常信息
cout << error.what() << endl;
}
catch(...)// 能处理任何异常
{
}
}
std::exception 该异常是所有标准 C++ 异常的父类。
std::bad_alloc 该异常可以通过 new 抛出。
std::bad_cast 该异常可以通过 dynamic_cast 抛出。
std::bad_exception 这在处理 C++ 程序中无法预期的异常时非常有用。
std::bad_typeid 该异常可以通过 typeid 抛出。
std::logic_error 理论上可以通过读取代码来检测到的异常。
std::domain_error 当使用了一个无效的数学域时,会抛出该异常。
std::invalid_argument 当使用了无效的参数时,会抛出该异常。
std::length_error 当创建了太长的 std::string 时,会抛出该异常。
std::out_of_range 该异常可以通过方法抛出,例如 std::vector 和 std::bitset<>::operator[]()。
std::runtime_error 理论上不可以通过读取代码来检测到的异常。
std::overflow_error 当发生数学上溢时,会抛出该异常。
std::range_error 当尝试存储超出范围的值时,会抛出该异常。
std::underflow_error 当发生数学下溢时,会抛出该异常。