快上网专注成都网站设计 成都网站制作 成都网站建设
成都网站建设公司服务热线:028-86922220

网站建设知识

十年网站开发经验 + 多家企业客户 + 靠谱的建站团队

量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决

C++笔记-创新互联

文章目录
    • 四区
    • 函数的分文件编写
    • 函数默认参数
    • class结构
    • class和struct的区别
    • 构造函数和析构函数
    • 拷贝函数
    • 创建对象的三种方法
    • 深拷贝和浅拷贝
    • 初始化列表
    • 静态成员
    • this指针
    • 常成员函数和常对象
    • 友元函数
    • 友元类
    • 友元成员函数
    • 运算符重载
      • 重载+(加号运算符)
      • 重载<<(左移运算符)
      • 重载自增运算符(递增运算符)
      • 重载=(赋值运算符)
      • 重载关系运算符
      • 重载括号运算符
    • 继承
      • 多继承
      • 同名变量函数
      • 多个父类拥有同名变量函数
      • 菱形继承
    • 多态
      • 开闭原则
      • 纯虚函数
    • 函数模板
      • 举例
      • 调用规则
      • 利用具体化模板解决自定义类型的比较问题

成都创新互联-专业网站定制、快速模板网站建设、高性价比凉州网站开发、企业建站全套包干低至880元,成熟完善的模板库,直接使用。一站式凉州网站制作公司更省心,省钱,快速模板网站建设找我们,业务覆盖凉州地区。费用合理售后完善,10年实体公司更值得信赖。四区
  1. 全局区
    静态变量、全局变量、常量
  2. 代码区
    存储编写的代码,本质就是把代码编译形成的二进制文件放在内存的代码区
  3. 栈区
    形参、临时变量(由操作系统负责分配与回收)
  4. 堆区
    new的变量(由程序员负责分配与回收)
函数的分文件编写
  1. 把函数声明放在.h的头文件中
  2. 把函数定义写在.cpp的函数文件中
  3. 在main.cpp中包含了函数声明头文件即可直接使用此函数
swap.h
#includeusing namespace std;

void swap(int *a, int *b);

swap.cpp
#include "swap.h"

void swap(int *a, int *b) {int temp = *a;
	*a = *b;
	*b = temp;
}

main.cpp
#include "swap.h"

int main()
{int a = 1, b = 2;
	swap(&a, &b);
	cout<< a<< " "<< b<< endl;
	return 0;
}
函数默认参数

若一个形参有默认参数,则此形参的右边所有参数都必须有默认参数,这是防止二义性的出现

int getRadius(int a, int b = 2, int c = 3) {//valid
	return 0;
}
int getRadius(int a, int b = 2, int c) {//invalid
	return 0;
}
class结构
class 类名 {访问权限:
		变量
	访问权限:
		方法
}

访问权限(默认是私有):

  1. public
    类内类外都可以访问
  2. protected
    类内可以访问,类外不可以访问
  3. private
    类内可以访问,类外不可以访问
class和struct的区别

c++的struct除了保留c的所有特性外,还增加了class的所有特性,两者只有一点不同,struct里面的属性默认是public,而class默认是private

构造函数和析构函数

构造函数:创建对象时会自动调用构造函数,构造函数(与函数名一致)若不定义则系统会默认创建一个空实现的构造函数
析构函数:释放对象内存时会自动调用析构函数,在构造函数前面加上“~”即成为析构函数,若不定义则系统会默认创建一个空实现的析构函数

析构函数通常用来释放类对象在堆区开辟的空间

class cicle {public:
		cicle() {	cout<< "这里调用了构造函数"<	cout<< "这里调用了析构函数"<
拷贝函数
class circle {public:
		int radius;
		circle(const circle &c) {//参数形式是固定的,只能这样写const类名 &变量名
			radius = c.radius;
			cout<< "这里调用了拷贝函数"<
创建对象的三种方法

如果创建了有参构造函数则不提供默认构造函数
如果创建了拷贝构造函数则不提供其他构造函数

circle c(2);	//括号法 
circle c = circle(2);	//显示法(circle(2)创建了一个匿名对象),匿名对象会立刻被系统回收掉
circle c = 2;	//隐式转化法(编译器将此语句变成circle c = circle(2))
深拷贝和浅拷贝

浅拷贝:
简单的变量赋值操作
深拷贝:
在堆区重新申请内存,用指针接受地址

浅拷贝存在的问题:如果在类中函数有申请堆区内存操作,假设创建了类c1,类中指针p接收了new出来的内存地址,又调用拷贝函数创建了c2,c2的p指针内容是由c1的p指针内容拷贝过来的,因此两者指针指向同一块内存区域,若此时c2调用了析构函数,将p指针指向的区域delete掉了,由于c1的p也指向此区域,所以当c1调用析构函数时,就会因为p1指针指向区域已经被释放掉而进行非法操作。解决办法是自定义拷贝函数,重新申请一块新区域,用p指针指向此区域,即深拷贝

class circle {public:
		int *p;
	public:
		circle(int a) {	p = new int(a);
		}
		circle(const circle &c) {//拷贝函数如果有new操作特别注意要深拷贝,浅拷贝会出问题
			p = new int(*c.p);
		}
		~circle() {//释放掉类中new的内存
			if(p) {		delete p;
				cout<< "调用成功"<< endl;
			}
		}
};
初始化列表
class circle {public:
		int aa, bb, cc;
	public:
		circle(int a, int b, int c): aa(a), bb(b), cc(c) {}
};
静态成员

静态成员变量

  1. 所有对象共享同一份数据
  2. 在编译阶段分配内存
  3. 类内声明,类外初始化(必须)

静态成员函数
4. 所有对象共享同一个函数
5. 静态成员函数只能访问静态成员变量

class circle {public:
		static int a;	//类内声明
		int b;
		static int getB() {//invalid,因为b是非静态变量
			return b;
		}
};
int circle::a = 1; 	//类外初始化
void test() {circle c;
	circle c2;
	c2.a = 3;	//c2和c共享一份a静态变量
	cout<< c.a<< endl; 
}
this指针

this指针指向被调用的成员函数所指向的对象
this指针不需要定义,直接使用即可
this指针的用途

  1. 当形参和成员变量同名时,可用this指针来区分
  2. 在类的非静态成员函数中返回对象本身,可使用return *this(链式编程)
class person {public:
		int age;
		person(int age) {	this->age = age;	//区分形参和成员变量
			//person::age = age;//这种形式也可以
		}
		person& addAge(int age) {//注意返回引用才可以链式编程
			this->age += age;
			return *this;	//返回调用该函数的对象c
		}
};
void test() {person c;
	c.age = 10;
	c.addAge(10).addAge(10).addAge(10);	//链式编程
	cout<< c.age<< endl;
}
常成员函数和常对象

特例:mutable修饰的变量可以被常函数和常对象修改

在普通成员函数后面括号后面加上const修饰就变成了常成员函数,常成员函数无法修改成员变量,通过两个规则保证

  1. 常成员函数不能更新对象的数据成员,也不能调用该类中没有用const修饰的成员函数。这保证了在常成员函数中绝对不会更新数据成员的值。
  2. 如果将一个对象说明为常对象(const对象),则通过该常对象只能调用它的常成员函数,而不能调用其他成员函数。这是C++从语法机制上对 const对象 的保护,也是 const对象 唯一的对外接口方式。
class person {public:
		int age;
		void setAge(int age) const {//常成员函数 ,const本质上是在修饰this指针,更改成员变量本质上就是通过对this指针解引用实现 
//			this->age = age //invalid,不允许修改成员变量 
		}
};

定义对象语句前面加上const修饰即为常对象。
常对象只能调用常函数,无法修改常对象的成员变量和调用常对象的普通成员函数。

const person p();	//const修饰要初始化加上一个括号
友元函数

作用:让全局函数可以访问到类对象的私有变量和函数
用法:在类中加上一条全局函数声明,再在前面加上friend修饰

class person {friend void func(person &p);	//友元函数声明
	private:
		int age;
		string name;
		string getName() {	return name;
		}
	public:
		person(string name, int age) {	this->name = name;
			this->age = age;
		}
};
void func(person &p) {//友元函数可以访问私有成员
	cout<< "正在访问"<< p.name<< " "<< p.age<< endl;
	cout<< p.getName()<< endl;
}
void test() {string name;
	int age;
	cin >>name >>age;
	person p1 = person(name, age);
	func(p1);	
}
int main()
{test();
	return 0;
}
友元类

作用:和友元函数一样,都是让好朋友可以访问自己的私有成员
用法:在类中加上友元类声明,再在前面加上friend修饰

class rooms;	//事先声明rooms类,防止location报错找不到rooms类
class person {private:
		int age;
		string name;
	public:
		person(string name, int age) {	this->name = name;
			this->age = age;
		}
		void location(rooms &r);
};

class rooms {friend class person;	//友元类
	private:
		string bedRoom;
	public:
		string sittingRoom;
		rooms(string a, string b) {	bedRoom = a;
			sittingRoom = b;
		}
};

void person::location(rooms &r) {//在类外实现成员函数的定义,这是因为location用到了rooms的成员变量,而person类定义在rooms之前,所以如果location在类内定义的话,编译器不认识bedRoom这个变量,因为此时rooms只是声明了一下,并没有定义内部变量(如果不想这么写,可以直接把rooms定义写在person类定义前面)
	cout<< name<< "现在正在"<< r.bedRoom<person p("李四", 21);
	rooms r("卧室", "客厅");
	p.location(r);
}
友元成员函数
class rooms;
class person {private:
		string name;
		rooms *p; 
	public:
		person(string name);
		void location();
		
};

class rooms {friend void person::location();	//变得只有这里
	private:
		string bedRoom;
	public:
		string sittingRoom;
		rooms() {	bedRoom = "卧室";
			sittingRoom = "客厅";
		}
};

person::person(string name) {this->name = name;
	p = new rooms;
}

void person::location() {cout<< name<< "现在正在"<< p->bedRoom<person p("李四");
	p.location();
}
运算符重载

将成员函数名字换成operator"运算符",即为运算符重载,本质还是编写函数

重载+(加号运算符)

让两个同类对象对应属性相加

class person {public:
		int age;
		person(int age) {	this->age = age;
		}
//		person operator+(person p) {	//成员函数重载
//			person temp(0);
//			temp.age = age + p.age;
//			return temp;
//		}

};
person operator+(person a, person b) {//全局函数重载
	person temp(0);
	temp.age = a.age + b.age;
	return temp;
}
void test() {person p1(10);
	person p2(20);
//	person p3 = p1.person(p2);	//成员函数重载,第一种写法
//	person p3 = operator+(p1, p2);	//全局函数重载,第二种写法
	person p3 = p1 + p2;	//以上两种写法的简化,等价于上面两种
	cout<< p3.age<< endl;
}
重载<<(左移运算符)

输出对象的所有属性值,由于cout在左移运算符左边,所以无法通过成员函数实现,成员函数cout只能在右边

class person {friend ostream& operator<<(ostream &out, person &p);	//友元
	private:
		int a, b;
	public:
		person(int a, int b) {	this->a = a;
			this->b = b;
		}
};
ostream& operator<<(ostream &out, person &p) {//链式编程,只有这样cout<person p(1,2);
	cout<< p<< endl;
}
重载自增运算符(递增运算符)
class myInteger {friend ostream& operator<<(ostream &cout, myInteger &o);
	private:
		int num;
	public:
		myInteger() {	num = 0;
		}
		myInteger& operator++() {	++ num;
			return *this;
		}
		myInteger& operator++(int) {//占位区分前置和后置
			myInteger temp = *this;
			num ++;
			return temp;
		}
};
ostream& operator<<(ostream &cout, myInteger &o) {cout<< o.num;
	return cout;
}
void test() {myInteger a;
	cout<< ++a<< endl;
	cout<< a++<
重载=(赋值运算符)
class person {public:
		int *p;
	public:
		person(int t) {	p = new int(t);
		}
		person(const person &o) {//重写拷贝函数
			p = new int(*o.p);
		}
		~person() {//析构函数释放申请空间
			if(p) {		delete p;
				p = NULL;
			}
			cout<< "success"<< endl;
		}
		person& operator=(person &o) {//重载=,注意返回当前对象,链式法则
			if(p) {		delete p;
				p = NULL;
			}
			p = new int(*o.p);
			return *this;
		}
};

void test() {person p1(10);
	person p2(0);
	person p3(0);
	person p4 = p3;	//这里不是=运算符重载,而是调用拷贝函数,因此依旧是浅拷贝,需要自定义拷贝函数,改成深拷贝 
	p3 = p2 = p1;
	cout<< p1.p<< " "<< p2.p<< " "<< p3.p<< " "<< p4.p<< endl;	//查看申请的内存地址
}
重载关系运算符
class person {public:
		string name;
		int age;
	public:
		person(string name, int age) {	this->name = name;
			this->age = age;
		}
//		bool operator==(person o) {//			if(name == o.name && age == o.age) return true;
//			return false;
//		}
		bool operator<(person o) {	if(age< o.age) return true;
			return false;
		}
		bool operator>(person o) {	if(age >o.age) return true;
			return false;
		}
};
bool operator==(person a, person b) {if(a.name == b.name && a.age == b.age) return true;
	return false;
}
void test() {person p1("tom", 16);
	person p2("tom", 15);
	if(p1 == p2) cout<< "same"<< endl;
	else if(p1 >p2) cout<< "bigger"<< endl;
	else cout<< "smaller"<< endl;
	
}
重载括号运算符
class person {public:
		string name;
		int age;
	public:
		person(string name, int age) {	this->name = name;
			this->age = age;
		}
		void operator()() {//重载()运算符
			cout<< name<< endl;
		}
};

void test() {person p1("tom", 16);	
	p1();	//由于写法类似函数,又名仿函数
}
继承 多继承

考虑以下情况:
狗是一个类,但狗又可以细分为很多品种,例如边牧、二哈、柯基等,这些细分的品种有狗的共性,但也有自己的特性,这时就体现出继承。
继承方式有三种:(子类会继承所有父类属性,但父类private的属性子类无法访问)
public:
不改变从父类继承过来的属性访问权限
protected:
把所有父类非private的属性访问权限设为protected
private:
把所有父类非private的属性访问权限设为private

class basicClass {//父类,公共内容
	public:
		void header() {	cout<< "公共头部"<< endl;
		}
		void footer() {	cout<< "公共底部"<< endl;
		}
		void left() {	cout<< "公共左部"<< endl;
		}
};
class JAVA : public basicClass {//继承父类的子类
	public:
		void content() {	cout<< "JAVA课程"<< endl; 
		}
};
class CPP : public basicClass {public:
		void content() {	cout<< "CPP课程"<< endl; 
		}
};
class Python : public basicClass {public:
		void content() {	cout<< "Python课程"<< endl; 
		}
};
void test() {JAVA a;
	Python b;
	CPP c;
	a.header(); a.content(); a.footer(); a.left(); 
	cout<< "----------------------------------------"<< endl;
	b.header(); b.content(); b.footer(); b.left();
	cout<< "----------------------------------------"<< endl;
	c.header(); c.content(); c.footer(); c.left();
}
同名变量函数

如果子类和父类有同名变量或者同名函数,则子类会隐藏父类的同名变量和同名函数,想要访问到父类的同名变量或者函数,则必须加上父类的作用域,另外,即使子类和父类的同名函数参数不同,但是子类仍然会隐藏父类的函数

class basicClass {public:
		int a;
		void out() {	cout<< "basicClass"<< endl;
		}
		void out(int k) {	cout<< "basicClass "<< k<< endl;
		}
};
class JAVA : public basicClass {public:
		JAVA() {	a = 100;
		}
		int a;
		void out() {	cout<< "JAVA"<< endl; 
		}
};
void test() {JAVA a;
	cout<< a.a<< endl;
	a.out();
	cout<< a.basicClass::a<< endl;
	a.basicClass::out();
	a.basicClass::out(2);	//即使子类同名函数和父类同名函数参数不同,仍然会隐藏父类同名函数 
}
多个父类拥有同名变量函数

子类可以继承多个父类,如果多个父类拥有同名变量或者函数,也需要通过作用域来区分

class basicClass {public:
		int a;
		basicClass() {	a = 100;
		}
};
class basicClass2 {public:
		int a;
		basicClass2() {	a = 200;
		}
};
class JAVA : public basicClass, public basicClass2 {};
void test() {JAVA a;
	cout<< a.basicClass::a<< " "<< a.basicClass2::a<< endl;
}
菱形继承

考虑下面情况:
有基类A,B继承于A,C继承于A,D继承于B和C,A中有一份数据,此时D就会同时继承两份一样的数据,要访问这份数据,还要加上作用域。如何解决呢?
虚继承可以解决此问题

class annimal {public:
		int age;
		annimal() {	age = 18;
		}
};
class sleep : virtual public annimal {};
class tuo : virtual public annimal {};
class sleepTuo : public sleep, public tuo {};
void test() {sleepTuo a;
	cout<< sizeof a<< endl;	//8->24消耗了内存
	cout<< a.age<< endl;
}

虚继承本质是存储一个指向虚基类的指针,指针指向虚基类表,表中存储一个偏移量,指针地址加上偏移量就是数据存放地址,因此此操作实质上增加了内存消耗,换来的是不用区分作用域。

多态

详解

class annimal {public:
		virtual void speak() {//定义为虚函数
			cout<< "动物在说话"<< endl;
		}
};
class cat : public annimal {public:
		void speak() {//重写虚函数
			cout<< "猫在说话"<< endl;
		}
};
void test() {cat c;
	annimal &a = c;	//多态
	a.speak();
}
开闭原则

开发项目时最忌讳代码一整个一口气写完,一是可读性差,二是扩展性差
开闭原则即为扩展开放,修改封闭
多态即可实现这样的目标,下面计算器是一个实例,如果把计算器实现的所有操作都封装在一个类里面,会显得代码都集中在一块,没有体现模块化编程思想,以后想添加新的操作,就要修改计算器类的代码。
如果使用多态,就可以创造一个空实现的基类,要添加操作时,就可以创造一个新类继承此基类,重写虚函数,实现新功能,不需要更改旧代码,只需要添加新代码即可。

class calculator {public:
		int num1, num2;
		virtual int calc() {	return 0;
		}
};

class subCalculator : public calculator {public:
		int calc() {	return num1 + num2;
		}
};

void test() {calculator *c = new subCalculator;	//加法器 
	c->num1 = 1;
	c->num2 = 2;
	cout<< c->calc()<< endl;
}
纯虚函数

以上案例中基类的虚函数函数体其实根本不会用到,写它只是为了过编译,这种情况就可以使用纯虚函数,纯虚函数可以不写函数体,拥有纯虚函数的类称为虚类,而让子类重写此函数,且子类必须重写纯虚函数,否则子类就也为虚类。

虚类无法实例化,即无法创建对象。

只能创建指针或者引用来实现多态。

class calculator {public:
		int num1, num2;
		virtual int calc() = 0;	//纯虚函数
};
函数模板 举例
template//将T作为一种数据类型
//template效果等价于上一句
void out(T &a) {cout<< a;
}

以上代码调用out函数时会根据传入的参数判断出T的类型,我们也可以在调用时直接指定出类型

out(s);	//直接指定T为string类型

以下是快速排序自写的一个模板

templatevoid sort(T a[], int l, int r) {if(l >= r) return ;
	T bas = a[l];
	int i = l, j = r;
	while(i< j) {while(i< j && a[j] >= bas) j --;
		while(i< j && a[i]<= bas) i ++;
		swap(a[i], a[j]);
	}
	swap(a[l], a[i]);
	sort(a, l, i-1);
	sort(a, i+1, r);
}
调用规则

调用规则如下:

  1. 如果函数模板和普通函数都可以实现,优先调用普通函数
  2. 可以通过空模板参数列表来强制调用函数模板
  3. 函数模板也可以发生重载
  4. 如果函数模板可以产性更好的匹配,优先调用函数模板
利用具体化模板解决自定义类型的比较问题

template<>打头,且参数类型具体指名即为具体化模板

class person {public:
		int age;
		string name;
		person(int age, string name) {	this->age = age;
			this->name = name;
		}
};

templatebool compare(T &a, T &b) {if(a == b) return true;
	return false;
}
template<>bool compare(person &a, person &b) {//具体化模板
	if(a.age == b.age && a.name == b.name) return true;	//如果函数参数和具体化模板参数相同,则会优先调用具体化模板
	return false;
}
void test() {person p1(16, "tom");
	person p2(16, "tom");
	cout<< compare(p1, p2)<< endl;	//person类型编译器无法比较
}

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


新闻名称:C++笔记-创新互联
文章路径:http://6mz.cn/article/pcgoj.html