文章篇幅较大,内容充实
请在阅读的过程中保持一个批判的态度
C++有自己的头文件,例如iostream 没有.h
也可以用C的头文件 例如stdio.h 有.h
或者将C文件C++化 例如cstdio 前面加c没有.h
表示::后面的内容属于::前面。翻译成中文就是 的
如果::前面没有内容表示全局
using namespace 名字空间名
使用后该名字空间对于当前作用域可见,可以不再使用作用域限定符,一旦使用,不可再隐藏。
std名字空间全局可见。std标准库定义名字空间
声明或定义结构体变量,可以省略struct
内部可以定义函数
声明或定义联合变量,可以省略union
支持匿名联合
声明或定义枚举变量,可以省略enum
独立类型和整型不能隐式相互转换
namespace ICBC
{
int balance = 2000;
void save(int money)
{
cout << "工商银行存入:" << money << endl;
balance += money;
}
void draw(int money)
{
cout << "工商银行取出:" << money << endl;
balance -= money;
}
void show()
{
cout << "工商银行余额:" << balance << endl;
}
}
namespace ABC
{
int balance = 4000;
void save(int money)
{
cout << "农业银行存入:" << money << endl;
balance += money;
}
void draw(int money)
{
cout << "农业银行取出:" << money << endl;
balance -= money;
}
void show()
{
cout << "农业银行余额:" << balance << endl;
}
}
int main(){
ICBC::save(3000);
ICBC::draw(2000);
ICBC::show();
cout << "\n";
ABC::draw(1500);
ABC::save(800);
ABC::show();
return 0;
}
任何基本类型都可以隐式转换为布尔类型
非0即真,0即假
boolalpha bool类型使用字符输出
noboolalpha bool关闭字符输出,数值输出
同一作用域中,函数名相同,参数表不同的函数。不同作用域同名函数遵循标识符隐藏原则(临近隐藏原则)。
重载和返回值和参数名没有关系
重载和返回类型无关
函数作用域不是定义决定的,是声明决定的。
重载是编译器通过换名实现,
在linux下 用gcc -c 获取.o 使用nm .o文件查看
在windows下查看obj文件,或者不定义函数,只声明和使用
通过extern “C”可以要求C++编译器按照C方式处理函数接口
为函数指定缺省值,调用时若未指定实参,则对应的形参取缺省值。
只指定类型而不指定名称的函数参数,叫做哑元。
使用哑元1.兼容之前版本。二.形成函数重载
引用(reference)是c++对c语言的重要扩充。
引用就是某一变量(内存)的一个别名,对引用的操作与对变量直接操作完全一样。其格式为:
类型 &引用变量名 = 已定义过的变量名。
&符号:跟在类型后是引用,没有类型是取地址
*符号:跟在类型后是指针,没有类型是解引用
诸葛亮 诸葛孔明 卧龙 蜀国丞相
当引用做函数的返回值时: 函数可以放在赋值语句的左边(可以当左值)
使用new delete
预先分配好,放到进程空间的内存块,用户申请与释放内存其实都是在进程内进行,遇到小对象时就是基于内存池的。只有当内存池空间不够时,才会再从系统找一块很大的内存
两者都是地址的概念
指针指向一块内存,其内容为所指内存的地址;
引用是某块儿内存的别名。
用这个方法来创建二维数组,比较直观、易用,但它最大的限制在于:你必须在编译时确定b的大小。
低一级的数组是分开创建的,所以整个二维数组的内存不连续——类似‘array2D[i * width + j]’这样的访问就不要使用了,容易造成访问越界。
万物皆对象
底层对象能够从高层对象继承一些属性和行为。
例如小狗继承动物的一些属性和行为,动物继承生物的一些属性和行为。
属性和行为可以完整的描述对象。属性是状态,特征。行为是能做什么。
例如认识陌生人,先关注性别,外貌,再交谈
面向对象三大特性:继承,封装和多态
拥有相同属性和行为的一组对象统一描述成一个类
人类:男人,女人,泰国人妖 等等对象
属性:身高,体重,年龄,三围 等等
行为:吃饭,睡觉,打豆豆,么么哒 等等
类是对对象的抽象,对象是类的具体(实例)化
class People
{
public:
char name[20];
int age;
int weight;
void eat(char* food)
{
cout << "我吃了" << food << endl;
}
void play(char* game)
{
cout << "我玩了" << game << endl;
}
};
有一个狗类,属性有姓名,犬龄,品种,毛色(不可以外部直接设置)。行为有进食,奔跑,睡觉。
请分别在堆区和栈区实例化一个对象。依次进食,奔跑,睡觉
调用
创建一个对象数组,存放4个学生(学号,成绩)(学号和成绩不可外部直接设置,且设置学号时不可重复),设计一个函数max,找出这4个学生成绩最高的,并输出学号。
调用
第二种方式
构造函数为什么要放在public
在外部实例化对象时自动调用构造函数,如果是其他访问方式,没办法调用构造函数,也就不能实例化对象。
(特例,单例模式等。)
简化成员变量初始化。仅仅只是为了书写方便,没有效率上的提升
注意,参数初始化顺序与初始化表列出的变量的顺序无关,它只与成员变量在类中声明的顺序有关
为什么使用析构函数
创建对象时系统会自动调用构造函数进行初始化工作,对应的,销毁对象时系统也会自动调用一个函数来进行清理工作,例如释放分配的内存、关闭打开的文件等,这个函数就是析构函数。
什么是析构函数
析构函数也是一种特殊的成员函数,没有返回值,
构造函数的名字和类名相同,而析构函数的名字是在类名前面加一个~符号。
析构函数用在哪里
在销毁对象时自动执行。不能显示调用。
析构函数怎么用
析构函数没有参数,不能被重载,因此一个类只能有一个析构函数。
如果用户没有定义,编译器会自动生成一个默认的析构函数。这个析构函数的函数体是空的,也没有形参,也不执行任何操作。
this 是 C++ 中的一个关键字,也是一个 const 指针。指向当前对象,通过它可以访问当前对象的所有成员。
用->来访问成员变量或成员函数。
成员函数最终被编译成与对象无关的普通函数,除了成员变量,会丢失所有信息,所以编译时要在成员函数中添加一个额外的参数,把当前对象的首地址传入,以此来关联成员函数和成员变量。这个额外的参数,实际上就是 this,它是成员函数和成员变量关联的桥梁。它并不出现在代码中,而是在编译阶段由编译器默默地将它添加到参数列表中。
#include
using namespace std;
class Test{
char x;
public:
Test(char c='A')
{
cout << c;
}
};
int main() {
Test p1, *p2; // 只实例化了p1 p2只是一个指针
p2 = new Test('B');
delete p2;
return 0;
}
同构造函数一样,如果用户不提供拷贝构造函数,编译器提供默认拷贝构造函数。
注意:因为拷贝构造函数属于构造函数,有的编译器会在用户提供构造函数的时候也不再提供拷贝构造。
默认拷贝构造函数原型
复制地址
浅拷贝就是对内存地址的复制,让目标对象指针和源对象指向同一片内存空间,当内存销毁的时候,指向这片内存的几个指针需要重新定义才可以使用,要不然会成为野指针。
复制内存
深拷贝是指拷贝对象的具体内容,而内存地址是自主分配的。拷贝结束之后,两个对象虽然存的值是相同的,但是内存地址不一样,两个对象也互不影响,互不干涉。
如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。
该代码报错:报错原因是n1,n2,n3的m_pAge共用一块内存,delete n3的时候,内存已经释放,离开作用域调用析构时,m_pAge成为野指针。
类型 类名::变量名=值;
- 静态成员变量在类中仅仅是声明,没有定义,所以要在类的外面定义,实际上是给静态成员变量分配内存。如果不加定义就会报错,初始化是赋一个初始值,而定义是分配内存。
using namespace std;
class test2
{
public:
test2(int num) : y(num){}
~test2(){}//( 构造 析构 )函数
static void testStaticFun()//静态成员函数
{
cout << "y = " << y << endl; //Error:静态成员函数不能访问非静态成员
}
void testFun()//非静态成员函数
{
cout << "x = " << x << endl;
}
private:
static int x;//静态成员变量的引用性说明
int y;
};
int test2::x = 10;//静态成员变量的定义性说明
int main(void)
{
test2 t(100);
t.testFun();
return 0;
}
/*
* 静态成员属于类而不属于对象,生命周期进程级
* 相当于全局变量和全局函数,只是多了类作用域和访问控制属性限制
* 静态成员依然后类的作用域和访问控制限定符约束
*
* 静态成员变量的定义和初始化只能在类外部,不能在构造函数
* 静态成员变量为该类所有对象共享
* 访问静态成员可以通过对象也可以直接通过类
*
* 静态成员函数没有this指针,没有常属性
* 静态成员函数只能访问静态成员 (没有this指针)
*/
class Account
{
public:
Account(string const&name, int no, double balance) : m_name(name), m_no(no), m_balance(balance)
{
}
void save(double money)
{
m_balance += money;
}
void draw(double money)
{
if (money > m_balance)
cout << "余额不足" << endl;
m_balance -= money;
}
void query()const
{
cout << "户名:" << m_name << endl;
cout << "账号:" << m_no << endl;
cout << "余额:" << m_balance << endl;
}
void settle()
{
this->m_balance *= (1 + m_rate / 100);
}
static void adjust(double rate)
{
//this->m_name; 没有this 不能访问 静态成员函数只能访问静态成员
if (rate>0)
{
m_rate = rate;
}
}
private:
string m_name;
int m_no;
double m_balance;
static double m_rate;//类里声明
};
//定义静态成员变量
/*静态成员变量在类中仅仅是声明,没有定义,所以要在类的外面定义
*定义是给静态成员变量分配内存。
*只能在定义的时候初始化*/
double Account::m_rate =0.2 ;//类外定义 初始化
int main(int argc, char* argv[])
{
Account acc1("小风同学", 101, 4000);
acc1.draw(2000);
acc1.query();
Account acc2("海绵", 102, 40000);
acc2.save(50000);
acc2.query();
cout << "-----------------" << endl;
cout << sizeof(acc2) << endl;
acc1.settle();
acc1.query();
acc2.settle();
acc2.query();
getchar();
return 0;
}
//饿汉式 程序启动就创建 不管你用不用
class single
{
private:
int m_data;
single(){}
single(int data) :m_data(data){}
single(const single&){}
static single s_instance;
public:
static single& getinstance()
{
return s_instance;
}
};
single single::s_instance(100);
class singleton
{
private:
int m_data;
singleton(){}
singleton(int data) :m_data(data){}
singleton(const singleton&){}
static singleton* s_instance;
public:
static singleton& getinstance()
{
if (!s_instance)
{
s_instance = new singleton(200);
}
return *s_instance;
}
};
singleton* singleton::s_instance = NULL;
int main(int argc, char* argv[])
{
//1 隐藏所有构造函数
//2 内部调用构造函数来实例化静态成员对象
//2 提供一个静态函数来返回静态成员对象
single& s1 = single::getinstance();
single& s2 = single::getinstance();
cout << &s1 << " " << &s2 << endl;
singleton& st1 = singleton::getinstance();
singleton& st2 = singleton::getinstance();
cout << &st1 << " " << &st2 << endl;
getchar();
}
#if 0
class Point3D;
class Point2D
{
//友元函数
//声明print为友元函数,可以访问Point2D隐藏
friend void print(const Point2D& point);
//友元类
friend class Point3D;
public:
Point2D(int x = 0, int y = 0) :m_x(x), m_y(y){}
void print()
{
cout << "Point2D(" << m_x << "," << m_y << ")" << endl;
}
Point2D::~Point2D(){}
private:
int m_x;
int m_y;
};
class Point3D
{
public:
Point3D(int x = 0, int y = 0, int z = 0)
{
m_p.m_x = x;
m_p.m_y = y;
m_z = z;
}
void print()
{
cout << "Point3D(" << m_p.m_x << "," << m_p.m_y << "," << m_z << ")" << endl;
}
Point3D::~Point3D(){}
private:
Point2D m_p;
int m_z;
};
//在Point2D中将print声明为友元函数,print可以访问Point2D隐藏成员
void print(const Point2D& point)
{
cout << "我是外面的" << endl;
cout << "Point2D(" << point.m_x << "," << point.m_y << ")" << endl;
}
int main(int argc, char* argv[])
{
Point2D p21;
p21.print();
Point2D p22(10);
p22.print();
Point2D p23(20, 30);
p23.print();
print(p21);
print(p22);
print(p23);
cout << "-------------------------" << endl;
Point3D p31(10, 20, 30);
p31.print();
getchar();
}
#endif
只有一个构造函数,为什么能有三种方式?
友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
class Point3D
{
friend void print(Point3D& point);
public:
Point3D(int x = 0, int y = 0,int z=0)
{
m_p.m_x = x;
m_p.m_y = y;
m_z = z;
}
void print()
{
cout << "Point3D(" << m_p.m_x << "," << m_p.m_y << "," << m_z << ")" << endl;
}
private:
Point2D m_p;
int m_z;
};
友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。
只有友元可以访问,其他依然不能访问。若类B是类A的友元。则只有类B的成员函数可以访问类A中的隐藏信息
友元是单向的,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。
友元关系不具有传递性。若类B是类A的友元,类C是B的友元,类C不一定是类A的友元,同样要看类中是否有相应的申明
运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
重载运算符可以使用成员函数或非成员函数(一般是友元函数)两种方法
只能使用其中的一种方法
#if 0
//复数类
class Complex
{
friend Complex operator+(const Complex& cp1, const Complex& cp2);
//operator>>(cin, cp1)
//提起
friend istream& operator>>(istream& in, Complex& cp1);
//插入
friend ostream& operator<<(ostream& out, Complex&cp1);
//friend Complex& operator+=(Complex& cp1, const Complex& cp2);
public:
Complex(double real = 0,double vir=0):m_real(real),m_vir(vir){}
Complex& operator+=(Complex& cp2)
{
this->m_real += cp2.m_real;
this->m_vir += cp2.m_vir;
return *this;
}
//前++ 先自增再返回
Complex& operator++()
{
//先对象自增
this->m_real++;
this->m_vir++;
//再返回对象
return *this;
}
//后++ 先返回再自增
Complex operator++(int) //返回引用返回对象 不返回引用返回对象的值
{
Complex temp = *this;
++(*this); //对象自增
return temp; //返回自增之前的值
}
void print()
{
cout << m_real << "+" << m_vir << "i" << endl;
}
private:
double m_real; //实部
double m_vir; //虚部
};
Complex operator+(const Complex& cp1,const Complex& cp2)
{
Complex temp;
temp.m_real = cp1.m_real + cp2.m_real;
temp.m_vir = cp1.m_vir + cp2.m_vir;
return temp;
}
//Complex& operator+=(Complex& cp1, const Complex& cp2)
//{
// cp1.m_real += cp2.m_real;
// cp1.m_vir += cp2.m_vir;
// return cp1;
//}
//提起
istream& operator>>(istream& in, Complex& cp1)
{
in >> cp1.m_real >> cp1.m_vir;
return in;
}
//插入
ostream& operator<<(ostream& out, Complex&cp1)
{
out << cp1.m_real << "+" << cp1.m_vir << "i" << endl;
return out;
}
int main()
{
int a = 1, b = 2, c = 3;
a = b + 2;
Complex cp1(1, 2);
Complex cp2(3, 4);
cp1.print();
//单目运算符 ++ -- cp1++
//用友元函数形式重载 有一个参数 操作数就是参数 operator++(cp1)
//用成员函数形式重载 没有参数(隐藏的this) 操作数就是当前调用对象 cp1.operator++()
int i = 1;
cout << ++i << endl; //2 先自增再返回
cout << i << endl; //2
cout << i++ << endl; //2 先返回再自增
cout << i << endl; //3
//双目运算符 + - * / cp1 += cp2
//用友元函数形式重载 有两个参数 左操作数做第一个参数,右操作数做第二个参数 operator+=(cp1,cp2)
//用成员函数形式重载 只有一个参数(隐藏的this) 左操作数是当前调用对象 右操作数做第一个参数
//cp1.operator+=(cp2);
cp1 += cp2;
cp1.print();
cp1 = cp1 + cp2; //operator+(cp1,cp2)
cp1.print();
//三目运算符 ?:
//不能重载
//不能创建新运算符 operator……& 不允许
//不能改变优先级
// :: . .* ?: sizeof 不能重载
//= () [] -> ->*必须是成员函数
//单目运算符,建议成员函数
//双目运算符,建议友元重载
cin >> cp1; // operator>>(cin,cp1)
cout << cp1; // operator<<(cin,cp1)
//(cin >> a) >> a >> a;
//(cout << a) << a << a;
//getchar();
system("pause");
return 0;
}
#endif //运算符重载
#if 1
class A
{
int m_a;
public:
A(int a) :m_a(a){}
};
class B
{
int m_b;
public:
explicit B(int a) :m_b(a){}
};
int main()
{
A a1 = 10;
//B b1 = 10;
B b2(10);
getchar();
}
#endif //explicit关键字 了解
#if 0
class Integer
{
public:
Integer(int i) :m_i(i)
{
}
int m_i;
int m_a;
int m_c;
};
int main()
{
Integer i1(10), i2(20);
int* p1 = &i1.m_i;
int* p2 = &i2.m_i;
//成员指针 指向 成员变量 的指针
//从远倒近 从右到左 括号优先
//指向integer类中int类型的指针
int Integer::* p = &Integer::m_i;
i1.*p = 50;
int Integer::*pp = NULL;
cout << *(int*)&pp << endl;
pp = &Integer::m_i;
cout << *(int*)&pp << endl;
pp = &Integer::m_a;
cout << *(int*)&pp << endl;
pp = &Integer::m_c;
cout << *(int*)&pp << endl;
getchar();
return 0;
}
#endif //成员指针 了解
#if 0
class Array
{
friend ostream& operator<<(ostream& out, Array& arr);
public:
Array(size_t size)
{
cout << "构造函数" << endl;
m_array = new int[size];
m_size = size;
}
Array(const Array& that)
{
cout << "拷贝构造函数" << endl;
m_array = new int[that.m_size];
memcpy(m_array, that.m_array, that.m_size*sizeof(that.m_array[0]));
m_size = that.m_size;
}
//*p_array2 = *p_array1;//拷贝赋值
Array& operator=(const Array&that)
{
cout << "拷贝赋值函数" << endl;
#if 0
this->m_array = that.m_array;
this->m_size = that.m_size;
return *this;
#endif //编译器 浅拷贝
#if 0
m_array = new int[that.m_size];
memcpy(m_array, that.m_array, that.m_size*sizeof(that.m_array[0]));
m_size = that.m_size;
return *this;
//原来内存没管 构造函数new了一次,没有delete,赋值又new了一次
#endif //初级菜鸟
#if 0
//*p_array2 = *p_array2; *p_array2.operator=(*p_array2);
if (this->m_array)
{
delete this->m_array;
this->m_array = NULL;
}
this->m_array = new int[that.m_size];
memcpy(this->m_array, that.m_array, that.m_size*sizeof(that.m_array[0]));
this->m_size = that.m_size;
return *this;
//没有考虑自赋值
#endif //高级菜鸟
#if 0
//*p_array2 = *p_array2; *p_array2.operator=(*p_array3);
if (this != &that) //避免自赋值 this:p_array2 that: &(*p_array2) p_array3
{
if (m_array)//释放旧资源
{
delete m_array;
m_array = NULL;
}
m_array = new int[that.m_size];//分配新资源
//拷贝新资源
memcpy(m_array, that.m_array, that.m_size*sizeof(that.m_array[0]));
m_size = that.m_size;
}
//this:p_array2 that : &(*p_array2) p_array2
//返回自引用
return *this;
//*p_array2 = *p_array1;
//new 可能失败
#endif //小鸟
#if 0
if (this != &that) //避免自赋值
{
int* ptemp = new int[that.m_size];
memcpy(ptemp, that.m_array, that.m_size*sizeof(that.m_array[0]));
if (m_array)//释放旧资源
{
delete m_array;
m_array = NULL;
}
swap(ptemp, m_array);//ptemp NULL m_array 新资源
delete[] ptemp;
m_size = that.m_size;
}
//返回自引用
return *this;
#endif //大鸟
//老鸟
if (this != &that) //避免自赋值
{
Array temp(that); //利用拷贝构造 分配、拷贝新资源
swap(this->m_array, temp.m_array);//交换新旧资源
}//释放旧资源
//返回自引用
return *this;
}
int& at(int index)
{
return m_array[index];
}
size_t size()
{
return m_size;
}
~Array()
{
if (m_array)
{
delete[] m_array;
m_array = NULL;
}
}
private:
int* m_array;
size_t m_size;
};
ostream& operator<<(ostream& out, Array& arr)
{
for (int i = 0; i < arr.m_size; ++i)
{
out << arr.m_array[i] << "\t";
}
out << endl;
return out;
}
int main()
{
Array* p_array1 = new Array(4);
for (int i = 0; i < p_array1->size(); ++i)
{
p_array1->at(i) = i;
}
cout << *p_array1 << endl;
Array* p_array2 = p_array1; //指针赋值
p_array2 = p_array1; //指针赋值
p_array2 = new Array(*p_array1); //拷贝构造
Array array3 = *p_array1;//拷贝构造
*p_array2 = *p_array1;//拷贝赋值
getchar();
return 0;
}
#endif //拷贝赋值 重点
cout是输出类的对象,而cin是输入类的对象
这些操作符必须重载为全局函数。如果操作符重载为一个成员函数,则它必须是对象的成员,且出现在操作符的左侧。而左侧一般是cout或者cin对象
不可重载运算符,其余运算符都可以重载
编译器提供默认拷贝赋值操作符重载。同默认拷贝构造一样,默认拷贝赋值操作符重载同样是浅拷贝。
尽量复用拷贝构造函数和析构函数中的代码
拷贝构造:分配新资源,拷贝新内容
析构函数:释放旧资源
尽量避免使用指针型成员变量。尽量通过引用和指针像函数传递对象型参数。降低参数传递开销同时,减少拷贝构造和拷贝赋值机会。
如果不需要进行拷贝构造和拷贝赋值,可将它们私有化,防止误用
如果为一个类提供了拷贝构造函数,那么也要提供拷贝赋值运算符函数
class Clock
{
friend Clock operator+(const Clock&, const Clock&);
friend Clock operator-(const Clock&, const Clock&);
public:
Clock(unsigned int hour = 0, unsigned int minute = 0, unsigned int second = 0);
Clock& operator+=(const Clock&);
void show();
private:
int m_hour;
int m_minute;
int m_second;
};
//全局重载+
Clock operator+(const Clock& c1, const Clock& c2)
{
Clock cTemp;
cTemp.m_hour = c1.m_hour + c2.m_hour;
cTemp.m_minute = c1.m_minute + c2.m_minute;
cTemp.m_second = c1.m_second + c2.m_second;
while (cTemp.m_second>59)
{
cTemp.m_second %= 60;
++cTemp.m_minute;
}
while (cTemp.m_minute>59)
{
cTemp.m_minute %= 60;
++cTemp.m_hour;
}
while (cTemp.m_hour>23)
{
cTemp.m_hour %= 24;
}
return cTemp;
}
//全局重载-
Clock operator-(const Clock& c1, const Clock& c2)
{
Clock cTemp;
cTemp.m_hour = c1.m_hour - c2.m_hour;
cTemp.m_minute = c1.m_minute - c2.m_minute;
cTemp.m_second = c1.m_second - c2.m_second;
while (cTemp.m_second<0)
{
cTemp.m_second += 60;
--cTemp.m_minute;
}
while (cTemp.m_minute<0)
{
cTemp.m_minute += 60;
--cTemp.m_hour;
}
while (cTemp.m_hour<0)
{
cTemp.m_hour += 24;
}
return cTemp;
}
//成员重载+=
Clock& Clock::operator+=(const Clock& time)
{
//利用已经实现的+
*this = *this + time;
return *this; //+=最后返回左操作数
}
void Clock::show()
{
cout << setw(2) << setfill('0') << this->m_hour << ":" //格式化输出 需要#include
<< setw(2) << setfill('0') << this->m_minute << ":"
<< setw(2) << setfill('0') << this->m_second << endl;
}
C风格的显示类型转换
(目标类型)源类型变量
C++语言中新增了四个关键字static_cast、const_cast、reinterpret_cast和dynamic_cast。新类型的强制转换可以提供更好的控制强制转换过程,允许控制各种不同种类的强制转换。
const_cast<目标类型>(源类型变量)
去掉指针或引用上的const属性
reinterpret_cast<目标类型>(源类型变量)
改变指针或引用的类型
将指针或引用转换为一个足够长度的整形
将整型转换为指针或引用类型。
#if 0
class A
{
public:
A()
{
cout << this << endl;
cout << "A构造" << endl;
}
};
class Human :public A
{
public:
/*Human()
{
cout << this << endl;
cout << "Human构造" << endl;
}*/
Human(string name, int age, char gender = '0') :m_name(name), m_age(age), m_gender(gender)
{
cout << this << endl;
cout << "Human三参构造" << endl;
}
void eat()
{
cout << "吃吃吃" << endl;
}
void sleep()
{
cout << "睡睡睡" << endl;
}
protected:
string m_name;
char m_gender; //0 1
private:
int m_age;
};
class Student :public Human
{
public:
//通过初始化表显示选择基类构造函数
Student(string name) :Human(name, 18, '0')
{
cout << this << endl;
cout << "Student构造" << endl;
m_name = name;
}
void study()
{
Human::eat(); //可以访问公有成员
m_name = "橘子";//可以访问保护成员
//m_age=10;//私有成员存在但是不能访问
cout << "学习" << endl;
}
//int eat;//子类成员会隐藏基类同名成员
int a;
};
int main()
{
Student stu("努力");
//构造子类对象顺序 为整个对象分配内存 构造基类子对象 构造子类成员 执行构造代码
// |
// 为整个对象分配内存 构造基类子对象 构造子类成员 执行构造代码
//子类对象任何时候都可以被当成基类类型对象 ISA 皆然
Human* ph1 = &stu;
Human& rh1 = stu;
getchar();
return 0;
}
#endif //继承和子类构造析构顺序
Class 子类:继承方式1 基类1,继承方式2 基类2…
{
}
访问范围缩小在编译器看来是安全的
基类类型指针或者引用不能隐式转换成子类对象
访问范围扩大在编译器看来是危险的
强转可以,但是对于派生内容访问有危险。
基类指针或引用到底是基类对象还是子类对象,需要自己判断,不能依靠编译器判断
子类可以直接访问基类的所有公有和保护成员,其效果如同它们是在子类中声明一样。对于基类的私有成员,在子类中存在但不能访问。
在子类中定义基类中同名的公有成员或保护成员,子类中的成员会隐藏基类同名成员。想访问被隐藏的成员,可以借助作用域限定符“::”
使基类公有成员和保护成员进行保护化,只禁止外部通过该子类访问。子类指针或引用不能隐式转换成基类类型指针或引用
将基类公有和保护成员私有化,禁止外部通过该子类访问。也禁止该子类的子类访问。子类指针或引用不能隐式转换成基类类型指针或引用
访问控制限定符 | 访问控制属性 | 基类 | 子类 | 外部 | 友元 |
---|---|---|---|---|---|
public | 公有成员 | OK | OK | OK | OK |
protected | 保护成员 | OK | OK | NO | OK |
private | 私有成员 | OK | NO | NO | OK |
继承方式 | 基类public成员 | 基类protected成员 | 基类private成员 |
---|---|---|---|
public | public | protected | private |
protected | protected | protected | private |
private | private | private | private |
通过子类访问继承基类成员时,需要考虑继承方式与访问控制属性
子类构造函数会调用基类构造函数,构造子类对象中的基类子对象
子类构造函数没有显示指明基类构造方式,会选择基类的缺省构造函数
子类显示调用基类构造函数可以在初始化表中显示指明构造方式。
子类对象构造过程
构造基类子对象,构造成员变量,执行构造代码
基类构造函数定义为私有,子类无法实例化对象,可以阻断类的继承。
子类析构会调用基类析构。
子类对象析构过程
内存布局从低到高,析构顺序相反
C里面有两个Base,调用Base方法会调用歧义
派生多个中间子类的公共基类子对象,在继承自多个中间子类的汇聚子类对象中,存在多个实例。
在汇聚子类或通过汇聚子类对象访问基类成员,因继承路径不同而导致不一致。
防止继承中成员访问的二义性
在A B继承方式前加关键字virtual
将Base的数据保存在一个公共位置
普通成员函数前加关键字virtual,称为虚函数
#if 0
//基类 Animal
class Animal
{
public:
Animal(string name);
virtual void say(); //虚函数
protected:
string m_name;
};
Animal::Animal(string name) :m_name(name){}
void Animal::say()
{
cout << m_name << ":^(*^&@*(!" << endl;
}
//派生类 Dog
class Dog :public Animal
{
public:
Dog(string name);
void say();//也是虚函数 形成覆盖
};
Dog::Dog(string name) :Animal(name){}
void Dog::say()
{
cout << m_name << ":汪汪汪" << endl;
}
int main()
{
Animal* p = new Animal("动物");
p->say();
delete p;
//基类指针指向子类对象
p = new Dog("狗");
p->say();
Animal a = *p;
a.say();
getchar();
return 0;
}
/*
编译器按实际类型调用
将say()声明成虚函数
基类指针指向什么对象就调用相应对象的成员函数
*/
#endif //虚函数
子类成员函数和基类的虚函数具有相同函数原型,该成员函数也就是虚函数,无论其是否带有virtual关键字,都对基类虚函数构成覆盖
函数必须是成员函数(非全局和静态成员函数)
基类使用virtual声明
覆盖版本和基类版本函数原型必须严格相同
子类提供了对基类虚函数的有效覆盖,通过指向子类对象的基类指针,或者引用子类对象的基类引用,调用该虚函数,实际上调用的将是子类中的覆盖版本,而非基类中的原始版本
意义:一般情况下调用哪个类的成员函数由调用者指针或引用本身类型决定,当多态发生,调用哪个类成员函数完全由调用者指针或引用的实际目标对象的类型决定
条件 基类定义虚函数,借助指针和引用
调用虚函数的指针也可能是基类中的this指针,同样满足多态条件,但在构造和析构函数中除外
#if 0
//单态
int add(int x, int y)
{
return x + y;
}
int sub(int x, int y)
{
return x - y;
}
int mul(int x, int y)
{
return x * y;
}
int divi(int x, int y)
{
if (y != 0)
{
return x / y;
}
return 0;
}
//怎么用一个函数实现加减乘除?现有知识\
//多态函数
int calc(int x, int y, int(*fun)(int, int))
{
return fun(x, y);
}
int main()
{
cout << calc(1, 2, add) << endl;
cout << calc(5, 3, sub) << endl;
cout << calc(4, 5, mul) << endl;
cout << calc(12, 6, divi) << endl;
getchar();
return 0;
}
#endif //多态函数
形如Virtual 返回值 函数名(形参表)=0;的虚函数,成为纯虚函数或抽象方法
#if 0
class A
{
public:
A()
{
cout << "A构造" << endl;
}
virtual ~A()
{
cout << "A析构" << endl;
}
};
class B :public A
{
public:
B()
{
p = new int(10);
cout << "B构造" << endl;
}
~B()
{
cout << "B析构" << endl;
if (p)
{
delete p;
}
}
private:
int* p;
};
int main()
{
//基类指针指向子类对象
A* p = new B;
delete p;
getchar();
return 0;
}
#endif //虚析构
至少拥有一个纯虚函数的类成为抽象类
抽象类不能实例化为对象
抽象类子类不对基类中全部纯虚函数提供有效覆盖,子类也是抽象类
#if 0
//单态
int add(int x, int y)
{
return x + y;
}
int sub(int x, int y)
{
return x - y;
}
int mul(int x, int y)
{
return x * y;
}
int divi(int x, int y)
{
if (y != 0)
{
return x / y;
}
return 0;
}
//怎么用一个函数实现加减乘除?现有知识\
//多态函数
int calc(int x, int y, int(*fun)(int, int))
{
return fun(x, y);
}
int main()
{
cout << calc(1, 2, add) << endl;
cout << calc(5, 3, sub) << endl;
cout << calc(4, 5, mul) << endl;
cout << calc(12, 6, divi) << endl;
getchar();
return 0;
}
#endif //多态函数
全部由纯虚函数构成的抽象类成为纯抽象类或接口
#if 0
//纯抽象类
class A
{
public:
virtual void display(int) = 0; //纯虚函数 抽象方法
virtual void foo(int) = 0;
};
class B:public A
{
public:
void display(int)
{
cout << "B" << endl;
}
void foo(int){}
};
class C :public A
{
public:
void display(int)
{
cout << "C" << endl;
}
};
int main()
{
A* p = new B;
p->display(10);
delete p;
//p = new C; //抽象类子类不对基类中全部纯虚函数提供有效覆盖,子类也是抽象类
//p->display(10);
delete p;
//A a;//抽象类不能实例对象 类中至少有一个抽象方法
getchar();
}
#endif //纯虚函数和抽象类
#if 1
class N
{
public:
void foo()
{
cout << "N::foo" << endl;
}
void bar()
{
cout << "N::bar" << endl;
}
int m_a;
int m_b;
};
class A
{
public:
virtual void foo()
{
cout << "A::foo" << endl;
}
virtual void bar()
{
cout << "A::bar" << endl;
}
int m_a;
int m_b;
};
class B :public A
{
public:
void foo()
{
cout << "B::foo" << endl;
}
void bar()
{
cout << "B::bar" << endl;
}
};
int main()
{
N n;
A a;
B b;
A* pa = &b;
cout << "sizeof(N):" << sizeof(N) << ",m_a:" << offsetof(N, m_a) << ",m_b:" << offsetof(N, m_b) <<endl;
cout << "sizeof(A):" << sizeof(A) << ",m_a:" << offsetof(A, m_a) << ",m_b:" << offsetof(A, m_b) << endl;
void* vf_ptr = *(void**)&a;
cout << vf_ptr << endl;
typedef void(*VFUN) (void*); //VFUN void(*)(void*) //函数指针
typedef VFUN* VPTR; //VPTR void(**) (void*) //指像函数指针类型的指针 虚函数表
VPTR _vfptr = *(VPTR*)&a;
cout << _vfptr << endl;
a.foo();
_vfptr[0](&a);
_vfptr[1](&a);
VPTR _vfptr1 = *(VPTR*)&b;
cout << _vfptr1 << endl;
b.foo();
_vfptr1[0](&b);
_vfptr1[1](&b);
getchar();
return 0;
}
#endif //虚函数表
某小型公司,主要有四类员工(Employee):经理(Manager)、技术人员(Technician)、销售经理(SalesManager)和推销员(SalesMan)。现在,需要存储这些人员的姓名(name)、编号(id)、当月薪水(salary)。计算月薪总额并显示全部信息。人员编号基数为 1000,每输入一个人员工信息编号顺序加 1。
月薪算法:
经理拿固定月薪 8000 元;
技术人员按每小时 100 元领取月薪;
推销员的月薪按该推销员当月销售额的 4%提成;
销售经理既拿固定月薪也领取销售提成,固定月薪为 5000 元,销售提成为所管辖部门当月销售总额的5%。
class Employee
{
public:
Employee():m_name(""),m_salary(0.0f) {
m_num++; //每有一个员工m_num++
m_id = m_num;
}
virtual void getSalary() = 0;//员工的工资计算没有具体方法 使用纯虚函数
void show() {
cout << "姓名:" << m_name << " 员工ID:" << m_id << " 工资:" << m_salary << endl;
}
virtual ~Employee() {}
protected:
string m_name;
int m_id;
float m_salary;
static int m_num; //属于全体员工 不在属于某个员工对象
};
int Employee::m_num = 0;
//经理拿固定月薪 8000 元;
class Manager :virtual public Employee
{
public:
Manager(string name) {
m_name = name;
m_baseSalary = 8000.0f;
}
void getSalary() {
m_salary = m_baseSalary;
}
~Manager() {}
protected:
float m_baseSalary; //基本工资
};
//技术人员按每小时 100 元领取月薪;
class Technician : public Employee
{
public:
Technician(string name, int hour) {
m_name = name;
m_hour = hour;
}
void getSalary() {
m_salary = m_hour * 70;
}
~Technician() {}
private:
int m_hour;
};
//推销员的月薪按该推销员当月销售额的 4%提成;
class SalesMan : virtual public Employee
{
public:
SalesMan(string name, float Count) {
m_name = name;
m_Count = Count;
m_partCount += m_Count;
}
void getSalary() {
m_salary = m_Count * 0.04;
}
~SalesMan() {}
private:
float m_Count;
protected:
static float m_partCount; //属于全体销售 不在属于某个销售对象
};
float SalesMan::m_partCount = 0.0f;
//销售经理既拿固定月薪也领取销售提成,固定月薪为 5000 元,销售提成为所管辖部门当月销售总额的5%。
class SalesManager : public Manager, public SalesMan
{
public:
SalesManager(string name):Manager(name),SalesMan(name,0) {
m_name = name;
m_baseSalary = 5000;
}
void getSalary() {
m_salary = m_baseSalary + m_partCount*0.05;
}
~SalesManager() {}
};
int main()
{
Employee* emp[5] = { 0, };
emp[0] = new Manager("关羽");
emp[0]->getSalary();
emp[0]->show();
emp[1] = new Technician("张飞", 99);
emp[1]->getSalary();
emp[1]->show();
emp[2] = new SalesMan("赵云", 60000);
emp[2]->getSalary();
emp[2]->show();
emp[3] = new SalesMan("马超", 900000);
emp[3]->getSalary();
emp[3]->show();
emp[4] = new SalesManager("黄忠");
emp[4]->getSalary();
emp[4]->show();
for (int i = 0; i < 5; ++i)
{
delete emp[i];
}
return 0;
}
当输出流是cout,则输出到显示器
put( ) 输出单个字符
write(buf, len) 输出指定长度
get( )操作:读取单个字符
getline( )读取一行
read(buf, len) 对空白字符(包括’\n’)照读不误
peek():查看而不读取
使用流的setf成员来设置,使用unsetf来取消
#include
struct fileinfo
{
int fileNameSize;
int fileOff;
int fileSize;
char fileName[20];
};
int main(int argc, char* argv[])
{
fstream file("new.pack", ios::in | ios::binary);//打开文件 此处的文件是new.pak
//1.读取索引表大小 +索引表个数
int listSize, listNum;
file.read((char*)&listSize, 4);//需要强转 &取地址
file.read((char*)&listNum, 4);
cout << listSize << "\t" << listNum << endl;
//2.读取索引表 创建文件
fstream *srcfile = new fstream[listNum];//文件
fileinfo *src = new fileinfo[listNum];//存放索引表
for (int i = 0; i < listNum; i++)
{
file.read((char*)&src[i].fileSize, 4);//读取文件大小
file.read((char*)&src[i].fileOff, 4);//读取文件偏移量
file.read((char*)&src[i].fileNameSize, 4);//读取文件名大小
file.read(src[i].fileName, src[i].fileNameSize);//读取文件名
srcfile[i].open(src[i].fileName, ios::out | ios::binary);//创建文件
//cout << "new file" << src[i].fileName << endl;
}
//3.读取文件 从pack文件中读取到新文件中
for (int i = 0; i < listNum; i++)
{
for (int j = 0; j < src[i].fileSize; j++)//读取一个文件
{
srcfile[i].put(file.get());//从文件中读取
}
}
//4 关闭文件
for (int i = 0; i < listNum; i++)
{
srcfile[i].close();
}
delete[] srcfile;//释放内存
delete[]src;
return 0;
}
语法错误,逻辑错误,功能错误,设计错误,需求不符,环境异常,操作错误。。。
异常处理主要处理运行环境中发生,但是在设计,编码和测试时无法预料得潜在得异常
try{
可能引发异常的语句;
}
catch(异常类型1& ex){
对异常类型1的异常处理
}
catch(异常类型2& ex){
对异常类型2的异常处理
}
。。。
根据异常对象类型从上到下匹配,而非最优匹配,因此对子类类型异常的捕获不要放在对基类类型异常的捕获后面
建议使用引用接收异常对象,避免因为拷贝构造带来性能损失或者新的异常。
为每一种异常定义相对应的异常类型,异常对象必须允许被拷贝构造和析构,最好从标准库异常派生异常。
what():返回一个C风格的字符串,目的是为抛出的异常提供文本描述
logic_error派生
class domain_error; 违反了前置条件
class invalid_argument; 指出函数的一个无效参数
class length_error; 指出有一个超过类型size_t的最大可表现值长度的对象的企图
class out_of_range; 参数越界
runtime_error派生
class range_error; 违反后置条件
class overflow_error; 报告一个算术溢出
class underflow_error; 报告算术下溢错误。
class bad_cast; 在运行时类型识别中有一个无效的dynamic_cast表达式
class bad_typeid; 报告在表达试typeid(*p)中有一个空指针p
class bad_alloc; 存储分配错误
实现C++中内置基本数据类型之间的相互转换.(隐式类型转换的逆转换)
把空指针转换成目标类型的空指针
把任何类型的表达式转换为void类型
用于类层次结构中基类和派生类之间指针或引用的转换
进行上行转换(把派生类的指针或引用转换成基类表示)是安全的
进行下行转换(把基类的指针或引用转换为派生类表示),由于没有动态类型检查,所以是不安全的
static_cast不能转换掉const属性
dynamic_cast<目标类型>(源类型变量)
将基类类型指针或者引用转换为子类类型的指针或引用。前提是子类必须从基类多态继承,即基类至少有一个虚函数。(从虚函数表找类型信息)
目标为期望得到的子类类型对象,转换成功,否则转换失败
针对指针的动态类型转换,失败返回NULL,针对引用的动态类型转换,失败抛出bad_cast异常
64字节整形
空指针,以前NULL只是用0代替,不能完整表示空的意思
给类型取别名 using dtype = int;
根据初始化代码的内容自动判断变量的类型,而不是显式的指定
类内初始化之后,构造函数只需要负责和缺省值不同的部分
使用条件:支持begin和end。
STL容器都支持,基本类型数组也有类似机制。
原来放返回值类型的位置写auto,在函数声明结束以后接一个’->'再跟着写函数的返回值类型。和普通声明效果一样。
添加了其他有参数的构造函数,编译器就不再生成缺省的构造函数了。C++11允许我们使用=default来要求编译器生成一个默认构造函数
A()=default;
假如上面的几个函数中,不想使用其中某个,可以将其定义为private,或者使用=delete。
A()=delete;
真正的构造工作由最后一个构造函数完成,而其他的构造函数都是委托最后一个构造函数完成各自的构造工作
一个表达式可以放在赋值语句的左侧,就称之为左值,如果不能放到表达式的左侧,就称之为右值
C++11使用&&来声明右值引用
int &&r=1;
没有函数名,有参数和函数体
将基本类型指针封装为类对象指针(这个类肯定是个模板,以适应不同基本类型的需求),并在析构函数里编写delete语句删除指针指向的内存空间。
auto_ptr(危险,被弃用)
unique_ptr 独占指针,离开作用域后释放指针
实现了独占式拥有概念,意味着它可确保一个对象和其相应资源同一时间只被一个指针拥有。一旦拥有者被销毁或变成空,或开始拥有另一个对象,先前拥有的那个对象就会被销毁,其任何相应资源也会被释放。
shared_ptr共享指针,增加计数器,计数器为0时释放指针
多个shared_ptr可以共享(或说拥有)同一对象。对象的最末一个拥有者有责任销毁对象,并清理与该对象相关的所有资源。
shared_ptr的目标就是,在其所指向的对象不再被使用之后,自动释放与对象相关的资源。
如果派生类在虚函数声明时使用了override描述符,那么该函数必须重写其基类中的同名函数,否则代码将无法通过编译。
类被final修饰,不能被继承
虚函数被final修饰,不能被override
#pragma once
template<class T, typename T1>
int bar(T a, T1 b);
template<class T, typename T1>
int bar(T a, T1 b)
{
cout << typeid(a).name() << " " << typeid(b).name() << endl;
return a > b ? a : b;
}
int add(int a, int b);
泛型是程序设计语言的一种特性。允许程序员在强类型程序设计语言中编写代码时定义一些可变部分,那些部分在使用前必须作出指明。各种程序设计语言和其编译器、运行环境对泛型的支持均不一样。将类型参数化以达到代码复用提高软件开发工作效率的一种数据类型。
数据的值可以通过函数参数传递,在函数定义时数据的值是未知的,只有等到函数调用时接收了实参才能确定其值。这就是值的参数化。
int max(int a, int b)
{
return a > b ? a : b;
}
在C++中,数据的类型也可以通过参数来传递,在函数定义时可以不指明具体的数据类型,当发生函数调用时,编译器可以根据传入的实参自动推断数据类型。这就是类型的参数化。
template<typename T>
T max(T a, T b)
{
return a > b ? a : b;
}
max<int>(12, 34)
函数模板代表一个函数族,它所用到的数据的类型(包括返回值类型、形参类型、局部变量类型)可以不具体指定,而是用一个标识符来代替。函数调用时根据实参逆推出真正类型,生成具体函数的二进制代码。
template <typename 类型参数1 , typename 类型参数2 , ...>
返回值类型 函数名(形参列表){
//模板函数体
}
类型参数用<> 值参数用()
typename可以用class替换.此处class不是表示类,因为早期C++没有typename而是用class表示类型参数名字。
函数名<参数类型1,参数类型2>(形参表);
编译器根据调用函数模板提供的模板实参,将所调用的函数模板编译成具体函数的过程,称为函数模板的实例化。
用于实例化函数模板的类型必须满足模板内部基于该类型的操作。
函数模板二次编译。编译器第一次看到函数定义,检查语法,生成内部结构。第二次,将提供的具体类型实参结合之前内部结构生成具体函数二进制指令。
对于函数模板一定要让编译器看到调用语句的同时,也能看到定义代码,否则无法完成第二次编译。因此,将函数模板定义放到头文件,需要使用该函数模板的源文件都包含此头文件。
max(12, 34)隐式推断成max
隐式推断的同时不允许隐式转换
实现一个模板函数cmp,该函数比较两个元素的任何类型。并使它适合于比较任何两个int、double、float、char*、string和任何两个相同类型的指针。如果第一个参数等于第二个参数,则返回true,否则返回false。
1.当比较两个int、double、float、char*、string时,应该比较它们的值
2 在比较两个指针时,应该比较它们指向的值。
#include "string"
#include "cstring"
template<typename T>
bool cmp(T a, T b) {
return a == b;
}
template<typename T>
bool cmp(T* a, T* b) {
return *a == *b;
}
bool cmp(char a[], char b[]) {
return !strcmp(a, b);
}
将类中成员变量的类型,成员函数的类型,成员类的类型,基类的类型等参数化。
一但声明了类模板,就可以将类型参数用于类的成员函数和成员变量了。换句话说,原来使用 int、float、char 等内置类型的地方,都可以用类型参数来代替。
template<typename 类型参数1 , typename 类型参数2 , …> class 类名{
//TODO:
};
成员函数如果在类外定义,同样要带上模板头
template<typename 类型参数1 , typename 类型参数2 , …>
返回值类型 类名<类型参数1 , 类型参数2, ...>::函数名(形参列表){
//T
使用类模板,必须显示指定模板参数。不支持隐式推断
类模板可以带有缺省值
template<typename T = int>
class Stack
{}
Stack<> si;
类模板中,只有那些被调用的成员函数才被实例化,即编译二进制代码。某些类型虽然没有提供某些功能,照样可以实例化该类模板,只要不直接或间接调用那些未提供功能的成员函数即可。
类模板的静态成员变量,在该类模板的每个实例化类中,都有一份独立的拷贝。
C++中的模板特化不同于模板的实例化,模板参数在某种特定类型下的具体实现称为模板的特化。模板特化有时也称之为模板的具体化,分别有函数模板特化和类模板特化
将泛型的东东搞得具体化一些
全特化,就是模板中模板参数全被指定为确定的类型。定义了一个全新的类型
偏特化,就是模板中的模板参数没有被全部确定,需要编译器在编译时进行确定。类型上加上const、&、( cosnt int、int&、int、等等)并没有产生新的类型