c++学习

这是一份记录一下学习c++的笔记,比较杂乱。
随机数种子
#include
//用时间制作的随机数种子
srand((using int)time(MULL));
rand()%61;//0~60取值
内存分区模型:代码区、全局区
代码区:程序执行前,存放cpu机器指令,共享,只读
全局区:程序执行前,全局变量,静态变量(static),常量(字符串常量,const修饰的全局变量)
栈区:程序执行后,局部变量,形参数据(由编译器开辟,函数执行后自动释放,要注意不能返回局部变量的地址)
堆区:程序执行后,由程序员开辟和释放,new出来的数据返回该数据类型的指针,指针的局部变量放在栈区,数据开辟在堆区存放。注意怎么开辟数组类型new int [10];释放 delete [] arry;
引用
给变量取别名:数据类型 &别名 = 原名
引用要初始化,不可以更改,操作的一直都要是同一块内存
引用传递,实参可以修饰形参与地址传递有一样的效果
不要返回局部变量的引用,返回引用的函数可以作为左值 int& test(); test()=1000;
引用本质是指针常量,值可以改,指向不能改 int * const ref= &a等价与 int& ref = a;
常量引用,const int& a = 10;等价于 int temp = 10; int & a = temp;使用在于修饰形参,防止误修改
函数提高
函数的默认参数:1.参数可以有默认值,但是往后的参数都要有2.函数声明和实现只能有一个有默认值
函数的占位参数:void func(int a;int),也可以有默认值void func(int a;int = 10)
函数的重载:1.作用域相同2.参数的个数,顺序,类型不同3.返回值不能作为条件。引用可以作为重载的条件void test(int &a);void test(const int &a);遇到默认参数如果是void test(int a);和如果是void test(int a,int b=10);调用可能会出现二义性,如果是void test(int a);和void test(const int &a);也会出现二义性。二义性:编译器不知道用哪个函数
12.23今天先到这,明天学一下类和对象--------------------------------------------------------------------------
c++三大特性:封装,继承,多肽
封装:设计类的时候,属性和行为写在一起表现事物。实例化是通过一个类创建具体的对象。属性(成员属性,成员变量)、行为(成员函数,成员方法)
关于权限:public(类内类外都可以访问)protected(类内可以,类外不行,子类可以)private(类内可以,类外不行,子类也不行)
struct的默认权限是public,class的默认权限是private

struct C1
{
	int m_age;//公共权限
};
class C1
{
	int m_age;//私有权限
};

成员属性私有化,可以用方法对其控制读写权限

class Person
{
	private:
		string m_name;
		int m_age;
	public:
		void setName(string name)//通过set方法类外设置
		{
			m_name = name;
		}
		void getName()//通过get方法类外得到
		{
			 return m_name;
		}
};

构造函数和析构函数
特点:没有返回值和类同名,构造函数可重载析构函数不行,会自动调用一次,没有写程序也还会自己增加。析构前加~
构造函数分类:有参和无参(默认构造)、普通和拷贝(拷贝的话要用const 类命 & 变量名)
调用构造函数:
括号法

Person p1;//无参构造
Person p2(10);//有参构造
Person p3(p1);//拷贝构造
//注意无参构造不能加(),会被误会是函数 Person p();是错误的

显示法

Person p1;//无参构造
Person p2 = Person(10);//有参构造
Person p3= Person(p2);//拷贝构造
//匿名对象会马上被回收无法使用 Person(10);
//注意不要用匿名对象调用拷贝构造会被误会重定义 Person(p3) = Person p3;

隐式转换法

Person p1;//无参构造
Person p2 = 10;//有参构造
Person p3= p2;//拷贝构造

拷贝构造函数的调用:
复制已有的对象信息

Person p2(10);
Person p3(p1);//把p2的值都拷贝一份

传值的方式调用

void test(Person p)//拷贝一份
{
}
void copy()
{
	Person p1;
	test(p1);
}

返回值的方式调用

Person test()
{
	Person p1;//局部变量函数结束被销毁
	return p1;//重新拷贝一份
}
void copy()
{
	Person p = test();//p和函数里面的p1地址不一样
}

构造函数的调用规则
默认提供无参构造函数、拷贝构造函数(值拷贝)、析构函数
如果通过有参构造,不提供无参构造只通过拷贝构造;如果通过拷贝构造,有参和无参构造都不提供
深拷贝与浅拷贝
主要是拷贝构造函数的问题,浅拷贝只是简单的值拷贝,如果有指针开辟内存会出现析构重复释放同一个内存地址的问题,要用深拷贝来解决这个问题。深拷贝:在拷贝构造函数里也要重新开辟一个内存

Person(const Person& p)
{
	m_height = new int(*p.m_height);//重新开辟内存,深拷贝
}
~Person()
{
	if(m_height!=NULL)
		delete m_height;//析构函数要释放内存
}

构造函数初始化列表

Person(int a,int b,int c):m_A(a),m_B(b),m_C(c)
{
}

类中有类对象构造和析构的顺序是什么样的,先构造的后析构

class A
{
};
class B
{
	A a;//类中对象先构造,自身的类先析构
};

12.24今天先到封装,明天学一下继承吧!--------------------------------------------------------------------------
静态成员
静态成员变量:所有数据都共享一份内存;编译阶段就分配内存(全局区);类内声明类外初始化,有访问权限。可以通过对象调用,也可以通过类名调用

class Person 
{
	static int m_A;//类内声明
private:
	static int m_B;//私有变量类外无法访问
}
int Person::m_A = 100;//类外初始化
void test01()
{
	Person p;
	p.m_A;//对象调用
	Person::m_A;//类名调用
}

静态成员函数:所有对象都共用一个函数,只能使用静态成员变量(因为非静态成员变量对象不一致,无法在类内使用),有访问权限。可以通过对象调用,也可以通过类名调用

class Person 
{
	static int m_A;//类内声明
	int m_C;
	static void func()
	{
		m_A = 100;//可以修改因为都是一份数据
		m_C = 200;//不可以修改因为对象无法确定
	}
private:
	static void func1();//私有函数类外无法访问	
};
void test01()
{
	Person p;
	p.func();//对象调用
	Person::func();//类名调用
}

成员变量和成员函数分开存储,只有非静态成员变量才是属于类的对象上,空对象占一个字节(区分空对象占内存的位置)

class Person 
{
	int m_A;//非静态成员变量,属于类的对象
	static int m_B;//静态成员变量,不属于类的对象
	void funC();//非静态成员函数,不属于类的对象
	static void funA();//静态成员函数,不属于类的对象
};

this指针的用途:解决命名冲突;返回*this,可以实现链式编程

class Person 
{
	int age;
	void setAge(int age)
	{
		age = age;//形参与成员变量同名,无法进行区分
		this->age = age;//可以解决命名冲突,指向被调用成员函数所属对象
	}
	Person addAge(Person p)//返回值会拷贝一份已经不是本身了
	{
		this->age = p.age;//年龄追加
		return *this;
	}
	Person& addAge(Person p)//返回引用一直都是自己那一份
	{
		this->age = p.age;//年龄追加
		return *this;
	}
};
void test()
{
	Person p1(10);
	Person p2(10);
	p2.addAge(p1).addAge(p1).addAge(p1);//可以实现链式编程
}

注意:this空指针无法访问成员变量

void test()
{
	Person *p = NULL;
	p.getAge();
}
class Person 
{
	int age;
	void getAge()
	{
		if(this==NULL)//指针如果为空无法访问变量,提高健壮性
		{
			return;
		}
		cout<<age;
	}
};

const修饰成员函数:this指针的本质是指针常量,值可以改这种指向不能修改,再再函数本身加上const限定的话称为常函数,值也会不能修改。如果非要修改的话,在变量前加上mutable才可以修改。const修饰对象称为常对象,常对象只能调用常函数限定只读,因为要保持成员变量值也不能修改的特性,常对象中只能修改mutable限定的特殊变量。

class Person 
{
	int age;
	mutable int year;
	void setAge() const//加上const限定 const Person *const this;//指针指向和值都不能修改
	{
		year = 2024//可以修改,因为有mutable
	}
	void getAge();
};
void test()
{
	const Person p;
	p.getAge();//无法调用,常对象只能调用常函数
	p.setAge();//可以调用常函数
	p.age = 100;//无法修改
	p.year= 100;//可以修改,因为有mutable
}

友元函数:全局函数做友元,类做友元,类内的成员函数做友元,都是加friend,可以访问本类中的私有变量
全局函数

class Bulid 
{
	friend void visit();//告诉编译器这是好朋友可以访问私有变量
	void Bulid()
	{
		m_SettingRoom = "客厅"
		m_BedRoom= "卧室"
	}
public:
	string m_SettingRoom;
private:
	string m_BedRoom;
};
void visit()
{
	Bulid b;
	cout<<b.m_SettingRoom <<","<<b.m_BedRoom<<endl;
}

class Bulid 
{
	friend class GoodFriend;//告诉编译器这是好朋友类可以访问私有变量
	void Bulid()
	{
		m_SettingRoom = "客厅"
		m_BedRoom= "卧室"
	}
public:
	string m_SettingRoom;
private:
	string m_BedRoom;
};
class GoodFriend
{
	GoodFriend()
	{
		b = new Bulid;//new出来一个类
	}
	void visit()
	{
		cout<<b.m_SettingRoom <<","<<b.m_BedRoom<<endl;
	}
	Bulid b;
};

类的成员函数

class Bulid 
{
	friend void GoodFriend::visit();//告诉编译器这是类下的好朋友函数可以访问私有变量
	void Bulid()
	{
		m_SettingRoom = "客厅"
		m_BedRoom= "卧室"
	}
public:
	string m_SettingRoom;
private:
	string m_BedRoom;
};
class GoodFriend
{
	GoodFriend()
	{
		b = new Bulid;//new出来一个类
	}
	void visit()
	{
		cout<<b.m_SettingRoom <<","<<b.m_BedRoom<<endl;
	}
	Bulid b;
};

运算符重载:operate+(),operate<<(),operate++(),operate++(int),operate=(),operate==(),operate!=(),operate()(),运算符重载可以在类内通过成员函数,也可以通过全局函数。对于已有的数据类型不能随意重载,也不要更改原有的运算规则
operate+()

class Person 
{
	int age;
	Person operate+(Person &p)//通过成员函数重载+,要不要返回引用只是要看要不要对自身数据进行下一步操作
	{
		Person temp;
		temp.age = this->age+p.age;
		return temp;
	}
};
Person operate+(Person &p1,Person &p2)//通过全局函数重载+
{
	Person temp;
	temp.age = p1.age+p2.age;
	return temp;
}
Person operate+(Person &p1,int num)//函数重载的版本
{
	Person temp;
	temp.age = p1.age+num;
	return temp;
}
void test()
{
	Person p1;
	p1.age = 10;
	Person p2;
	p2.age = 10;
	Person p3 = p1+p2;//成员函数本质Person p3 = p1.operate+(p2);
	Person p3 = p1+p2;//全局函数本质Person p3 = operate+(p1,p2);
	Person p3 = p1+10;
}

operate<<(),因为成员函数重载会出现p.operate<<(cout)简化是p<<cout的结构,所以这里使用全局函数重载,输出自定义类型

class Person 
{
	friend ostream& operate<<(ostream &cout,Person &p1);//全局友元可以访问私有变量
private:
	int age;
	Person(int age)
	{
		this->age = age;
	}
	Person operate+(Person &p)//通过成员函数重载+,要不要返回引用只是要看要不要对自身数据进行下一步操作
	{
		Person temp;
		temp.age = this->age+p.age;
		return temp;
	}
};
ostream& operate<<(ostream &cout,Person &p1)//通过全局函数重载+,本质operat<<(cout,p)简化cout<<p
{
	cout<<p1.age;
	return cout;
}
void test()
{
	Person p1(10);
	cout<<p1<<endl;
}

operate++(),operate++(int),自增运算符重载

class MyInterer
{
friend ostream& operate<<(ostream &cout,Person &p1);//全局友元可以访问私有变量
public:
	MyInterer()
	{
		num = 0;
	}
	//重载前置++运算符,返回引用是为了一直对一个数据进行操作
	MyInterer& operate++()
	{
		num++;
		return *this;//返回自身
	}
	//重载后置++运算符,int是占位参数表示区分前置和后置,返回值因为是局部变量要不会非法操作
	MyInterer operate++(int)
	{
		MyInterer my =  *this;//先记录原本的
		num++;//再增加
		return my;//再返回
	}
private:
	int num;
}
ostream& operate<<(ostream &cout,Person &p1)//通过全局函数重载+,本质operat<<(cout,p)简化cout<<p
{
	cout<<p1.age;
	return cout;
}
void test01()
{
	MyInterer my1;
	cout<<++(++my1)<<endl;//返回引用一直对自己操作 2
	cout<my1<<endl;//已经加上 2
}
void test02()
{
	MyInterer my2;
	cout<<my2++<<endl;//0 不会(my2++)++这种写法因为是返回值不可修改
	cout<my2<<endl;//1
}

operate=() c++编译器还会给类增加第四个函数是operate=,进行值拷贝。所以还是会出现浅拷贝的问题,重复释放同一个堆区的内容,要用深拷贝解决

class Person
{
	int *m_age;
	Person(int age)
	{
		m_age = new int(age);//重新开辟内存,深拷贝
	}
	~Person()
	{
		if(m_age !=NULL)
		{
			delete m_age ;//析构函数要释放内存,这里就会暴露浅拷贝出现的问题所以要重载operate=使用深拷贝
			m_age = NULL;
		}
	}
	Person& operate=(Person &p)
	{
		m_age = p.age;//编译器本身是值拷贝
		if(m_age !=NULL)//先判断原本有没有数据,有的话先清空
		{
			delete m_age ;
			m_age = NULL;
		}
		m_age = new int(*p.age);//重新开辟内存,深拷贝
		return *this;//返回自身的引用才能连续操作
	}
};
void test()
{
	Person p1(10);
	Person p2(20);
	Person p3(30);
	p3 = p2 = p1;
}

operate==(),operate!=()关系运算符重载,自定义数据类型判断

class Person 
{
	int age;
	bool operate ==(Person &p)
	{
		if(this->age == p.age)
		{
			return true;
		}
		return false;
	}
	bool operate !=(Person &p)
	{
		if(this->age == p.age)
		{
			return false;
		}
		return true;
	}
};
void test()
{
	Person p1(10);
	Person p2(20);
	if(p1==p2)
	{
		cout<<"相等"<<endl;
	}
	else
	{
		cout<<"不相等"<<endl;
	}
	if(p1!=p2)
	{
		cout<<"不相等"<<endl;
	}
	else
	{
		cout<<"相等"<<endl;
	}

operate()(),很像函数调用所以叫仿函数,比较灵活没有固定写法

class MyPrint
{
	void operate()(string s)
	{
		cout<<s<<endl;
	}
};
class MyAdd
{
	int operate()(int a,int b)
	{
		return a+b;
	}
};
void test01()
{
	MyPrint myprint;
	myprint("test");//特别像函数调用
}
void test02()
{
	MyAdd myadd;
	myadd(100,100);
	cout<<MyAdd()(100,100)<<endl;//匿名函数对象
}

12.26重载有好多东西啊,只能明天再学继承和多肽了!--------------------------------------------------------------------------
继承:好处是减少重复代码,格式是 class 子类(派生类):继承方式 父类(基类),总结就是继承共性派生个性
继承方式:父类中是私有的成员子类的任何继承方式都无法访问,public继承保持成员原本的继承方式,protected继承把非private继承的都更改为protected,private继承把非private都更改为private在这里插入图片描述

class A
{
public:
	int a;
protected:
	int b;
private:
	int c;
};
class B:public A
{
	void func()
	{
		a = 100;//可以访问,权限还是public
		b = 100;//可以访问,权限还是private
		c = 100;//私有无法访问
	}
};
class C:protected A
{
	void func()
	{
		a = 100;//可以访问,权限变成protected
		b = 100;//可以访问,权限变成protected
		c = 100;//私有无法访问
	}
};
class D:public A
{
	void func()
	{
		a = 100;//可以访问,权限还是private
		b = 100;//可以访问,权限还是private
		c = 100;//私有无法访问
	}
};
class E:public D
{
	void func()
	{
		a = 100;//私有无法访问
		b = 100;//私有无法访问
		c = 100;//私有无法访问
	}
};
void test()
{
	B b;
	b.a = 10;//公共,可以访问
	b.b = 10;//保护,无法访问
	b.c = 10;//私有,无法访问
	C c;
	c.a = 10;//保护,无法访问
	c.b = 10;//保护,无法访问
	c.c = 10;//私有,无法访问
	D d;
	d.a = 10;//私有,无法访问
	d.b = 10;//私有,无法访问
	d.c = 10;//私有,无法访问
}

子类继承父类:所有的权限下成员都会被继承,private权限下的成员只是会被编译器隐藏
在这里插入图片描述
在这里插入图片描述

class Base
{
public:
	int m_a;
protected:
	int m_b;
private:
	int m_c;
};
class Son:public Base
{
	int m_d;//默认私有权限
};
void test()
{
	sizeof(Son);//答案是16,父类3个int加上子类自己1个int
}

继承中构造和析构的顺序:先构造的后析构,先构造父类再构造子类
关于继承中出现同名成员:子类对象可以直接访问子类成员,子类对象要通过加作用域才可以访问父类成员,出现成员函数同名时,子类会屏蔽掉父类所有的同名函数,此时要加作用域才可以访问

class Base
{
public:
	int m_a;
	Base()
	{
		m_a = 100;
	}
	void func()
	{
		cout<<"父类的"<<endl;
	}
	void func(int a)
	{
		cout<<"有参父类的"<<endl;
	}
};
class Son:public Base
{
public:
	int m_a;
	Son()
	{
		m_a = 200;
	}
	void func()
	{
		cout<<"子类的"<<endl;
	}
};
void test()
{
	Son son;
	son.m_a;//200
	son.Base::m_a;//100要加作用域才可以
	son.func();//子类的
	son.Base::func();//父类的无参,被屏蔽要加作用域
	son.Base::func(10);//父类的有参,被屏蔽要加作用域
}

关于继承中出现静态同名成员:子类对象可以直接访问子类成员,子类对象要通过加作用域才可以访问父类成员,出现成员函数同名时,子类会屏蔽掉父类所有的同名函数,此时要加作用域才可以访问,可以通过对象进行访问,也可以通过类名访问

class Base
{
public:
	static int m_a;//类内声明
	static void func()
	{
		cout<<"父类的"<<endl;
	}
	static void func(int a)
	{
		cout<<"有参父类的"<<endl;
	}
};
int Base::m_a = 100;//类外初始化,要加作用域
class Son:public Base
{
public:
	int m_a;//类内声明
	static void func()
	{
		cout<<"子类的"<<endl;
	}
};
int Son::m_a = 200;//类外初始化,要加作用域
void test()
{
	//通过对象访问
	Son son;
	son.m_a;//200
	son.Base::m_a;//100要加作用域才可以
	son.func();//子类的
	son.Base::func();//父类的无参,被屏蔽要加作用域
	son.Base::func(10);//父类的有参,被屏蔽要加作用域
	//通过类名访问
	Son::m_a;//200
	Son::Base::m_a;//100要加作用域才可以
	Son::func();//子类的
	Son::Base::func();//父类的无参,被屏蔽要加作用域
	Son::Base::func(10);//父类的有参,被屏蔽要加作用域
}

多继承:语法是calss 子类:继承方式 父类1,继承方式 父类2,如果父类1和父类2出现同名成员,会有二义性,要加作用域区分,开发中不建议使用多继承,问题比较大

class Base1
{
public:
	int m_a;
	Son()
	{
		m_a = 100;
	}
};
class Base2
{
public:
	int m_a;
	Son()
	{
		m_a = 200;
	}
};
class Son:public Base1,Base2
{
public:
	int m_b;
};
void test()
{
	sizeof(Son);//答案是12,两个父类1个int加上子类自己1个int
	Son s;
	s.Base1::m_a;//100,Base1中的数据
	s.Base2::m_a;//200,Base2中的数据
}

菱形继承:会出现两份数据造成资源浪费,可以用虚继承解决,两个子类会有对应的虚基类指针指向对应的虚基类函数表,子类父类全部共用一份数据,解决菱形继承最后一个对象数据不一致的问题
在这里插入图片描述

class Animal//虚继承
{
public:
	int m_a;
};
class Sheep:virtual public Animal//虚继承解决菱形继承的问题
{
};
class Tuo:virtual public Animal
{
};
class SheepTuo:public Sheep,Tuo
{

};
void test()
{
	SheepTuo sheeptuo;
	sheeptuo.Sheep::m_a = 18;//羊类的数据
	sheeptuo.Tuo::m_a = 28;//陀类的数据
	sheeptuo.Base1::m_a;//增加作用域,两个父类有相同数据
	sheeptuo.Base2::m_a;//增加作用域,两个父类有相同数据
	sheeptuo.m_a;//以上三份数据都是一样的
}

多态:静态多态(编译阶段已经绑定地址),像函数重载和运算符重载;动态多态(运行阶段才绑定地址),像虚函数和派生类。动态多态的条件有继承关系,子类重写(返回类型,函数名,参数列表完全一样)父类的虚函数。多态的使用条件父类的指针或者引用指向子类对象

class Animal
{
public:
	virtual void speak()
	{
		cout<<"动物在说话"<<endl;
	}
};
class Cat:public Animal
{
public:
	void speak()
	{
		cout<<"猫在说话"<<endl;
	}
};
class Dog:public Animal
{
public:
	void speak()
	{
		cout<<"狗在说话"<<endl;
	}
};
//需要地址晚绑定
void doWork(Animal &animal) //父类引用指向子类对象Animal &animal = cat
{
	animal.speak();
}
void test()
{
	Cat cat;
	doWork(cat);//如果没有虚函数就是动物在说话
	Dog dog;
	doWork(dog);//有了虚函数就是狗在说话
}

多态的底层是虚函数指针指向虚函数表,重写虚函数之后,函数会被覆盖
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
多态的优点:结构清晰,可读性强,利于维护(争取不修改源码,只扩展新功能)
纯虚函数:加上=0,有寸虚函数的类称为抽象类,抽象类无法实例化对象,栈上(局部变量)和堆区(new)都无法实例化对象。子类必须重写虚函数,要不也是抽象类

class Base
{
public:
	virtual void func() = 0;//父类的纯虚函数
};
class Son:public Base
{
public:
	void func() //重写父类的纯虚函数,要不也是抽象类
	{
		cout<<"son的函数"<<endl;
	}
};
void test()
{
	Base base;//抽象类,无法实例化
	new Base;//抽象类,无法实例化
	Base * base = new Son;//父类指针指向子类对象
	base->func();//执行子类函数
}

父类指针在析构的时候不会调用子类的析构,如果子类有堆区的数据就会无法释放出现内存泄漏。要将父类的析构函数更改为虚析构函数才能解决。纯虚析构也能解决这个问题,要注意虚析构和纯虚析构都要有具体的实现,有纯虚析构的类也是抽象类

class Animal
{
public:
	Animal()
	{
		cout<<"动物的构造"<<endl;
	}
	virtual ~Animal()//虚析构可以解决父类指针释放子类对象的问题
	{
		cout<<"动物的虚析构"<<endl;
	}
	virtual ~Animal() = 0;//纯虚析构,要声明也要实现,这个类变成抽象类
	virtual void speak() = 0;//纯虚函数
};
//要实现纯虚析构
Animal::~Animal()
{
	cout<<"动物的纯虚析构"<<endl;
}
class Cat:public Animal
{
public:
	Cat(string name)
	{
		cout<<"猫的构造"<<endl;
		m_Name = new string(name);
	}
	void speak()
	{
		cout<<"猫在说话"<<endl;
	}
	~Cat()
	{
		cout<<"猫的析构"<<endl;//如果没有纯虚析构函数不会调用到这
		if(m_Name!=NULL)
		{
			delete m_Name;
			m_Name= NULL;
		}
	}
	string *m_Name;
};
void test()
{
	Animal animal = new Cat("Tom");
	animal->speak();//执行子类函数
	delete animal;//父类指针在析构的时候不会调用子类的析构,如果子类有堆区的数据就会无法释放出现内存泄漏
}

12.28写了两天继承和多态,太久了呀!明天看看模板--------------------------------------------------------------------------

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值