C++笔记-创新互联
- 四区
- 函数的分文件编写
- 函数默认参数
- class结构
- class和struct的区别
- 构造函数和析构函数
- 拷贝函数
- 创建对象的三种方法
- 深拷贝和浅拷贝
- 初始化列表
- 静态成员
- this指针
- 常成员函数和常对象
- 友元函数
- 友元类
- 友元成员函数
- 运算符重载
- 重载+(加号运算符)
- 重载<<(左移运算符)
- 重载自增运算符(递增运算符)
- 重载=(赋值运算符)
- 重载关系运算符
- 重载括号运算符
- 继承
- 多继承
- 同名变量函数
- 多个父类拥有同名变量函数
- 菱形继承
- 多态
- 开闭原则
- 纯虚函数
- 函数模板
- 举例
- 调用规则
- 利用具体化模板解决自定义类型的比较问题
- 全局区
静态变量、全局变量、常量 - 代码区
存储编写的代码,本质就是把代码编译形成的二进制文件放在内存的代码区 - 栈区
形参、临时变量(由操作系统负责分配与回收) - 堆区
new的变量(由程序员负责分配与回收)
- 把函数声明放在.h的头文件中
- 把函数定义写在.cpp的函数文件中
- 在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 类名 {访问权限:
变量
访问权限:
方法
}
访问权限(默认是私有):
- public
类内类外都可以访问 - protected
类内可以访问,类外不可以访问 - private
类内可以访问,类外不可以访问
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) {}
};
静态成员静态成员变量
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化(必须)
静态成员函数
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指针的用途
- 当形参和成员变量同名时,可用this指针来区分
- 在类的非静态成员函数中返回对象本身,可使用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修饰就变成了常成员函数,常成员函数无法修改成员变量,通过两个规则保证
- 常成员函数不能更新对象的数据成员,也不能调用该类中没有用const修饰的成员函数。这保证了在常成员函数中绝对不会更新数据成员的值。
- 如果将一个对象说明为常对象(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);
}
调用规则调用规则如下:
- 如果函数模板和普通函数都可以实现,优先调用普通函数
- 可以通过空模板参数列表来强制调用函数模板
- 函数模板也可以发生重载
- 如果函数模板可以产性更好的匹配,优先调用函数模板
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://myzitong.com/article/pcgoj.html