C++面向对象的三大特性为:封装、继承、多态
封装性的存在主要有两大意义:①将属性和行为作为一个整体,表现生活中的事物;②将属性和行为加以权限控制
权限控制
访问权限有三种:public(公共权限 )、protected(保护权限)、private(私有权限)
公共权限public: 类内可以访问,类外可以访问
保护权限protected:类内可以访问,类外除子类不可以访问
私有权限private: 类内可以访问,类外不可以访问
友元的目的就是让一个函数或者类访问另一个类中私有成员。友元的关键字为friend
class A
{
public:
//成员变量和函数,在类的其他成员函数和类的对象可以直接访问
private:
//成员变量和函数,在类的其他成员函数中可以直接访问,但是不可以通过类的对象直接访问,在派生类中不可以直接访问
protected:
//成员变量和函数,在类的其他成员函数中可以直接访问,但是不可以通过类的对象直接访问,在派生类中可以直接访问
};
class Person {
public:
//姓名设置可读可写
void setName(string name) {
m_Name = name;
}
string getName()
{
return m_Name;
}
//获取年龄
int getAge() {
return m_Age;
}
//设置年龄
void setAge(int age) {
if (age < 0 || age > 150) {
cout << "你个老妖精!" << endl;
return;
}
m_Age = age;
}
//情人设置为只写
void setLover(string lover) {
m_Lover = lover;
}
private:
string m_Name; //可读可写 姓名
int m_Age; //只读可写检测 年龄
string m_Lover; //只写 情人
};
int main() {
Person p;
//姓名设置
p.setName("张三");
cout << "姓名: " << p.getName() << endl;
//年龄设置
p.setAge(50);
cout << "年龄: " << p.getAge() << endl;
//情人设置
p.setLover("苍井");
//cout << "情人: " << p.m_Lover << endl; //只写属性,不可以读取
system("pause");
return 0;
}
父类指针指向子类对象
使用父类指针指向子类对象时,可以实现多态性,即在运行时根据对象的实际类型来调用相应的函数。这样可以方便地实现代码的扩展和维护,同时也可以提高代码的可读性和可维护性
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void speak()
{
cout << "Animal speaks" << endl;
}
string name;
};
class Cat : public Animal
{
public:
void speak()//扩展
{
cout << "Cat speaks" << endl;
}
string name;
};
class Dog : public Animal
{
public:
void speak()//扩展
{
cout << "Dog speaks" << endl;
}
};
int main()
{
Animal *p1 = new Cat(); //维护
Animal *p2 = new Dog();
p1->speak(); // 输出 Cat speaks
p2->speak(); // 输出 Dog speaks
delete p1;
delete p2;
return 0;
}
继承
分别是公共继承、保护继承、私有继承
class Base1
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
//公共继承
class Son1 :public Base1
{
public:
void func()
{
m_A; //可访问 public权限
m_B; //可访问 protected权限
//m_C; //不可访问
}
};
void myClass()
{
Son1 s1;
s1.m_A; //类外只能访问到公共权限
}
//保护继承
class Base2
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son2:protected Base2
{
public:
void func()
{
m_A; //可访问 protected权限
m_B; //可访问 protected权限
//m_C; //不可访问
}
};
void myClass2()
{
Son2 s;
//s.m_A; //不可访问
}
//私有继承
class Base3
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son3:private Base3
{
public:
void func()
{
m_A; //可访问 private权限
m_B; //可访问 private权限
//m_C; //不可访问
}
};
class GrandSon3 :public Son3
{
public:
void func()
{
//Son3是私有继承,所以继承Son3的属性在GrandSon3中都无法访问到
//m_A;
//m_B;
//m_C;
}
};
利用【开发人员命令提示符工具】查看:打开工具窗口后,定位到当前C++文件的盘符,输入
cl /d1 reportSingleClassLayout查看的类名 所属文件名
同名属性参数相同
-
当子类定义了与父类同名的属性以后,子类会将父类的同名属性进行隐藏。此时直接访问同名属性时,调用的是子类定义的同名属性
-
在子类内部,可以通过属性名或子类::属性名的方式对只属于子类的同名属性进行调用;可以通过父类::属性名的方式对从父类继承下来的属于子类的同名属性进行调用
-
在子类外部,可以可以通过子类对象.属性名或子类对象.子类::属性名的方式对只属于子类的同名属性进行调用;可以通过子类对象.父类::属性名的方式对从父类继承下来的属于子类的同名属性进行调用
同名属性参数不同
- 在子类内部,由于子类的同名函数把父类所有名称相同的函数,都进行了隐藏,因此,无法通过函数名(参数)的方式对子类的继承的不同参数的同名函数进行调用;可以通过父类::函数名(参数)的方式对从父类继承下来的属于子类的同名函数进行调用
- 在子类外部,也同样无法通过子类对象.函数名(参数)的方式对子类的继承的不同参数的同名函数进行调用,只能通过子类对象.父类::函数名(参数)的方式进行调用
静态成员和非静态成员出现同名,处理方式一致,只不过有两种访问的方式:通过对象和通过类名
class Base
{
public:
int m_A = 10;
};
//公共继承
class Son :public Base
{
public:
int m_A = 100;
public:
void print_info() {
//子类内
cout << Base::m_A << endl;
cout << Son::m_A << endl;
cout << m_A << endl;
}
};
void test01()
{
Son s;
s.print_info();
//子类外
cout << s.Base::m_A << endl;
cout << s.Son::m_A << endl;
cout << s.m_A << endl;
}
int main() {
test01();
system("pause");
return 0;
}
在C++中,struct和class唯一的区别就在于默认的访问权限不同:struct默认权限为公共,class默认权限为私有
继承中构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数。构造函数的调用顺序为:
先调用父类构造函数,
再调用子类构造函数。
析构函数的调用顺序为:
先调用子类析构函数,
再调用父类析构函数。
多继承语法
多继承可能会引发父类中有同名成员出现,此时子类在使用时,需要加作用域区分。C++实际开发中不建议用多继承
静态多态: 函数重载和运算符重载属于静态多态,复用函数名。
动态多态: 派生类和虚函数实现运行时多态
静态多态的函数地址早绑定:编译阶段确定函数地址
动态多态的函数地址晚绑定:运行阶段确定函数地址
多态的介绍
实现多态,必须要满足以下两个条件:
两个类有继承关系
子类重写父类中的虚函数
重写:函数返回值类型、函数名、参数列表完全一致称为重写
重载:函数的参数名相同,而参数列表不同
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数
virtual 返回值类型 函数名 (参数列表)= 0 ;
抽象类
类中有了纯虚函数,这个类也称为抽象类。抽象类具有以下特点:
无法实例化对象。
子类必须重写抽象类中的纯虚函数,否则也属于抽象类
class Base
{
public:
//纯虚函数
//类中只要有一个纯虚函数就称为抽象类
//抽象类无法实例化对象
//子类必须重写父类中的纯虚函数,否则也属于抽象类
virtual void func() = 0;
};
class Son :public Base
{
public:
virtual void func()
{
cout << "func调用" << endl;
};
};
void test01()
{
Base * base = NULL;
//base = new Base; // 错误,抽象类无法实例化对象
base = new Son;
base->func();
delete base;//记得销毁
}
int main() {
test01();
system("pause");
return 0;
}
虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码,此时,就需要将父类中的析构函数改为虚析构或者纯虚析构 virtual ~类名(){}
virtual ~类名() = 0;
类名::~类名(){}//纯虚析构需要实现
虚析构和纯虚析构异同
可以解决父类指针释放子类对象
都需要有具体的函数实现
如果是纯虚析构,该类属于抽象类,无法实例化对象
class Animal {
public:
Animal()
{
cout << "Animal 构造函数调用!" << endl;
}
virtual void Speak() = 0;
//析构函数加上virtual关键字,变成虚析构函数
//virtual ~Animal()
//{
// cout << "Animal虚析构函数调用!" << endl;
//}
virtual ~Animal() = 0;
};
Animal::~Animal()
{
cout << "Animal 纯虚析构函数调用!" << endl;
}
//和包含普通纯虚函数的类一样,包含了纯虚析构函数的类也是一个抽象类。不能够被实例化。
class Cat : public Animal {
public:
Cat(string name)
{
cout << "Cat构造函数调用!" << endl;
m_Name = new string(name);
}
virtual void Speak()
{
cout << *m_Name << "小猫在说话!" << endl;
}
~Cat()
{
cout << "Cat析构函数调用!" << endl;
if (this->m_Name != NULL) {
delete m_Name;
m_Name = NULL;
}
}
public:
string *m_Name;
};
void test01()
{
Animal *animal = new Cat("Tom");
animal->Speak();
//通过父类指针去释放,会导致子类对象可能清理不干净,造成内存泄漏
//怎么解决?给基类增加一个虚析构函数
//虚析构函数就是用来解决通过父类指针释放子类对象
delete animal;
}
int main() {
test01();
system("pause");
return 0;
}
C++中使用抽象基类和纯虚函数实现接口功能
以达到多态和封装的目的。通过定义接口,实现了回调函数、共享行为、解耦和复用等功能
#include <iostream>
using namespace std;
/* 接口抽象类的定义 */
class Interface {
public:
virtual void method1() = 0;
virtual void method2() = 0;
};
class Device1 : public Interface
{
public:
virtual void method1(void)
{
cout<<"Device1 method1"<<endl;
}
virtual void method2(void)
{
cout<<"Device1 method2"<<endl;
}
};
class Device2 : public Interface
{
public:
virtual void method1(void)
{
cout<<"Device2 method1"<<endl;
}
virtual void method2(void)
{
cout<<"Device2 method2"<<endl;
}
};
class Process {
public:
void Parse(Interface *api) {
api->method1();
api->method2();
}
};
int main(void)
{
Process process;
Device1 dev1;
Device2 dev2;
process.Parse(&dev1);
process.Parse(&dev2);
return 0;
}
//********************************************
// Interface.h
//主要是宏定义一些关键词,可以形成接口类
//********************************************
#ifndef INTERFACE_H
#define INTERFACE_H
#define Interface class
#define DeclareInterface(name) Interface name { \
public: \
virtual ~name() {}
#define DeclareBasedInterface(name, base) class name : \
public base { \
public: \
virtual ~name() {}
#define EndInterface };
#define implements public
#endif
//***********************************************
// IBar.h
//通过宏定义生成我们的接口类,写一些纯虚函数
//***********************************************
#ifndef IBAR_H
#define IBAR_H
#include "Interface.h"
DeclareInterface(IBar)
virtual int GetBarData() const = 0;
virtual void SetBarData(int nData) = 0;
EndInterface
#endif
//***********************************************
// BasicBar.h
//Bar类的父亲类,用来继承测试
//***********************************************
#ifndef BASICBAR_H
#define BASICBAR_H
#include <iostream>
class BasicBar
{
public:
BasicBar(int x)
{
}
~BasicBar(){}
virtual int getBarData1() const
{
std::cout <<"Get BasicBar!";
return 0;
}
virtual void setBarData1(int nData)
{
}
};
#endif
//***********************************************
#ifndef Bar_H
#define Bar_H
#include "Interface.h"
#include "BasicBar.h"
#include "IBar.h"
#include <iostream>
class Bar :public BasicBar,implements IBar
{
public:
Bar(int x=0) : BasicBar(x)
{
}
~Bar(){}
virtual int getBarData() const
{
std::cout <<"Get Bar!";
return 0;
}
virtual void setBarData(int nData)
{
}
};
#endif
//***********************************************
// main.h
//主函数
//***********************************************
#include <QtCore/QCoreApplication>
#include "IBar.h"
#include "DataAccess.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
//IBar *bar = new Bar();
IBar *bar = DataAccess::createBar();
bar->getBarData();
delete bar;
return a.exec();
}
调用接口的主要作用
是解耦,对用户隐藏实现,用户只需要关心接口的形式,不用关心具体的实现,只要保持接口的兼容性,实现上的修改或者升级对用户无感知。解耦之后也方便多人合作开发,设计好接口之后,各模块只通过接口进行交互,各自完成各自的模块即可
回调接口
系统定义接口,由用户实现,注册到系统中,系统有异步事件需要通知用户时,回调用户注册的接口实现。系统定义接口的形式,但无需关心接口的实现,而是接受用户的注册,并在适当的时机调用。这类由系统定义,用户实现,被系统调用的接口,称为回调接口
回调接口的主要作用是异步通知,系统定义好通知的接口,并在适当的时机发出通知,用户接收通知,并执行相应的动作,用户动作执行完后控制权交还给系统,用户动作可以给系统返回一些数据,以决定系统后续的行为
class Network
{//弊端:虚函数调用需要使用虚函数表指针间接调用,运行时才能决定调用哪个函数,无法在编译链接期间内联优化。实际上调用接口在编译期间就能确定调用哪个函数,无需虚函数的动态特性
//由于虚函数是按照索引查询虚函数表来调用,增加虚函数会造成索引变化,新接口不能在二进制层面兼容老接口,而且由于用户可能继承了Network接口类,在末尾增加虚函数也有风险,因此虚函数接口一经发布,难以修改
public:
virtual bool send(const char* host,
uint16_t port,
const std::string& message) = 0;
static Network* New();
static void Delete(Network* network);
}
//指向实现的指针
//指向实现的指针是C++比较推荐的定义接口的方式,使用指向实现的指针定义Network接口类如下
/*Network的实现通过impl指针转发给NetworkImpl,
NetworkImpl使用前置声明,实现对用户隐藏。使用指向实现的指针的方式定义接口,接口类对象的创建和销毁可以由用户负责,
因此用户可以选择将Network类的对象创建在栈上,生命周期自动管理
使用指向实现的指针定义接口具有良好的通用性,用户能够直接创建和销毁接口对象,并且增加新的接口函数不影响二进制兼容性,便于系统的演进
指向实现的指针增加了一层调用,尽管对性能的影响几乎可以忽略不计,但不太符合C++的零开销原则,那么问题来了,C++能否实现零开销的接口呢?当然可以,即下面要介绍的隐藏的子类*/
class NetworkImpl;
class Network
{
public:
bool send(const char* host,
uint16_t port,
const std::string& message);
Network();
~Network();
private:
NetworkImpl* impl;
}
/*隐藏的子类可以实现零开销的接口,思想非常简单。调用接口要实现的目标是解耦
主要就是隐藏实现,也即隐藏接口类的成员变量,如果能将接口类的成员变量都移到另一个隐藏的实现类中,接口类就不需要任何成员变量,也就实现了隐藏实现的目的。隐藏的子类就是这个隐藏的实现类,使用隐藏的子类定义Network接口类
*/
class Network
{
public:
bool send(const char* host,
uint16_t port,
const std::string& message);
static Network* New();
static void Delete(Network* network);
protected:
Network();
~Network();
}
/*Network接口类只有成员函数(非虚函数),没有成员变量,并且构造函数和析构函数都申明为protected。提供静态方法New创建对象,静态方法Delete销毁对象。New方法的实现中创建隐藏的子类NetworkImpl的对象,并以父类Network指针的形式返回。NetworkImpl类中存放Network类的成员变量,并将Network类声明为friend
*/
class NetworkImpl : public Network
{
friend class Network;
private:
//Network类的成员变量
}
/*Network的实现中,创建隐藏的子类NetworkImpl的对象,并以父类Network指针的形式返回,通过将this强制转换为NetworkImpl的指针,访问成员变量*/
bool Network::send(const char* host,
uint16_t port,
const std::string& message)
{
NetworkImpl* impl = (NetworkImpl*)this;
//通过impl访问成员变量,实现Network
}
static Network* New()
{
return new NetworkImpl();
}
static void Delete(Network* network)
{
delete (NetworkImpl*)network;
}
/*使用隐藏的子类定义接口同样具有良好的通用性和二进制兼容性,同时没有增加任何开销,符合C++的零开销原则**/
回调接口
class Network
{
public:
class Listener
{
public://onReceive定义为纯虚函数,由用户继承实现,由于多态的存在,回调的是实现类的方法
virtual void onReceive(const std::string& message) = 0; //当有消息到达时,回调Listener的onReceive方法
}
bool send(const char* host,
uint16_t port,
const std::string& message);
void registerListener(Listener* listener);
}
/*函数指针
函数指针是C语言的方式,使用函数指针定义Network接口类如下*/
class Network
{
public:
typedef void (*OnReceive)(const std::string& message, void* arg);
bool send(const char* host,
uint16_t port,
const std::string& message);
void registerListener(OnReceive listener, void* arg);
}
/*使用函数指针定义C++回调接口简单高效,但只适用于回调接口中只有一个回调函数的情形,如果Listener接口类中要增加onConnect,onDisconnect等回调方法,单个函数指针无法实现。另外函数指针不太符合面向对象的思想,可以换成下面要介绍的std::function*/
//std::function提供对可调用对象的抽象,可封装签名相符的任意的可调用对象
class Network
{
public:
typedef std::function<void(const std::string& message)> OnReceive;
bool send(const char* host,
uint16_t port,
const std::string& message);
void registerListener(const OnReceive& listener);
}
/*std::function可以很好的取代函数指针,配合std::bind,具有很好的通用性,因而被广受推崇。但std::function同样只适用于回调接口中只有一个回调方法的情形。另外,std::function比较重量级,使用上面的便利却会带来了性能上的损失,有人做过性能对比测试,std::function大概比普通函数慢6倍以上,比虚函数还慢*/
类成员函数指针
//类成员函数指针
class Network
{
public:
class Listener
{
public:
void onReceive(const std::string& message);
}
typedef void (Listener::* OnReceive)(const std::string& message);
bool send(const char* host,
uint16_t port,
const std::string& message);
void registerListener(Listener* listener, OnReceive method);
template<typename Class>
void registerListener(Class* listener,
void (Class::* method)(const std::string& message)
{
registerListener((Listener*)listener, (OnReceive)method);
}
}
使用类成员函数指针定义C++回调接口灵活高效,可实现与接口类解耦,并且不破坏面向对象特性,可很好的取代传统的函数指针的方式
名字空间(namespace)的引入和使用
名字空间域是随标准C++而引入的。它相当于一个更加灵活的文件域(全局域),可以用花括号把文件的一部分括起来,并以关键字namespace开头给它起一个名字
//TestMain.cpp
//1. 普通的命名空间
namespace sun
{
int max = 10;
int min = 0;
int my_add(int a,int b){ return a+b;}
}
// 2.名字空间域可分层嵌套,同样有分层屏蔽作用
namespace Primer
{
double pi = 3.1415926535898;
double my_add(double a,double b) { return a + b;}
namespace Matrix
{
char my_max(char a,char b) { return a>b? a:b;}
}
}
//3. 同一个工程中允许存在多个相同名称的命名空间
// 编译器最后会合成同一个命名空间中。
namespace sun
{
float pi = 3.14;
int my_sub(int a,int b)
{
min = a - b;
return min;
}
}
int main()
{
int a = sun::my_add(12,23);
printf("%lf \n",Primer::pi);
printf("%f \n", sun::pi);
Primer::Matrix::my_max('a','b');
return 0;
}
花括号括起来的部分称声明块。声明块中可以包括:类、变量(带有初始化)、函数(带有定义)等。最外层的名字空间域称为全局名字空间域(global namespace scope),即文件域
名字空间域的引入,主要是为了解决全局名字空间污染(global namespace pollution)问题,即防止程序中的全局实体名与其他程序中的全局实体名,命名冲突
使用using namespace 名字空间名称引入
using sun::pi;
using Primer::Matrix::my_max;
//名字空间类成员matrix的using声明
//以后在程序中使用matrix时,就可以直接使用成员名,而不必使用限定修饰名。
int main()
{
printf("%lf \n",Primer::pi);
printf("%f \n", pi); // sun::
my_max('a','b');
return 0;
}
using namespace sun;
int main()
{
printf("%lf \n",Primer::pi);
printf("%f n", pi); // sun::
my_add(12,23); // sun::
return 0;
}
使用using指示符:
标准C++库中的所有组件都是在一个被称为std的名字空间中声明和定义的。在采用标准C++的平台上使用标准C++库中的组件,只要写一个using指示符:using namespace std;
就可以直接使用标准C++库中的所有成员
C++如何申请动态内存
变量申请
Type* pointer = new Type;
//…
delete pointer;
数组申请
Type* pointer = new Type[N];
//…
delete[] pointer;
在C++中,你可以使用new
和delete
关键字来申请和释放动态内存。这对于创建非静态存储期的对象或数组非常有用。下面是一些基本的例子:
**单个对象:**
int* ptr = new int; // 申请一个整数的内存
*ptr = 5; // 在分配的内存中存储值5
delete ptr; // 释放内存
**对象数组:**
int* ptr = new int[10]; // 申请一个10个整数的数组
for (int i = 0; i < 10; ++i) {
ptr[i] = i; // 初始化数组
}
delete[] ptr; // 释放数组内存
new有三种典型的使用方法:plain new,nothrow new和placement new
void* operator new(std::size_t) throw(std::bad_alloc);
void operator delete(void ) throw();
nothrow new在空间分配失败的情况下是不抛出异常,而是返回NULL
void * operator new(std::size_t,const std::nothrow_t&) throw();
void operator delete(void) throw();
#include <iostream>
#include <string>
using namespace std;
class ADT{
int i;
int j;
public:
ADT(){
i = 10;
j = 100;
cout << "ADT construct i=" << i << "j="<<j <<endl;
}
~ADT(){
cout << "ADT destruct" << endl;
}
};
int main()
{
char *p = new(nothrow) char[sizeof ADT + 1];
if (p == NULL) {
cout << "alloc failed" << endl;
}
ADT *q = new(p) ADT; //placement new:不必担心失败,只要p所指对象的的空间足够ADT创建即
可
//delete q;//错误!不能在此处调用delete q;
q->ADT::~ADT();//显示调用析构函数
delete[] p;
return 0;
}
//输出结果:
//ADT construct i=10j=100
//ADT destruct
#include <iostream>
#include <string>
using namespace std;
int main()
{
char *p = new(nothrow) char[10e11];
if (p == NULL)
{
cout << "alloc failed" << endl;
}
delete p;
return 0;
}
//运行结果:alloc failed
构造函数特性
名称与类名相同:构造函数的名称必须与类名完全相同,并且不能有返回值类型(包括void)。
自动调用:构造函数在对象实例化时自动调用,不需要手动调用。
初始化成员变量:构造函数的主要作用是初始化对象的成员变量。
重载:一个类可以有多个构造函数,只要它们的参数列表不同(即构造函数可以被重载)。
无返回值:构造函数不能有返回值类型。
初始化列表:构造函数可以使用初始化列表来初始化成员变量,特别是当成员变量是常量或引用类型时
#include <iostream>
using namespace std;
class Point {
public:
// 默认构造函数
Point() {
x = 0;
y = 0;
cout << "Default constructor called" << endl;
}
// 带参数的构造函数
Point(int xVal, int yVal) {
x = xVal;
y = yVal;
cout << "Parameterized constructor called" << endl;
}
void print() {
cout << "Point(" << x << ", " << y << ")" << endl;
}
private:
int x, y;
};
int main() {
Point p1; // 调用默认构造函数
Point p2(10, 20); // 调用带参数的构造函数
p1.print();
p2.print();
return 0;
}
继承中的调用顺序:在继承中,基类的构造函数会在派生类的构造函数之前被调用
默认构造函数: 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成
无参(默认)构造函数
有参构造函数
委托构造函数
复制(拷贝)构造函数
移动构造函数
有参,无参,拷贝三种构造函数对应三种调用方式示例
#include<iostream>
using namespace std;
class Person
{
public:
//默认构造函数
Person();
Person()
{
cout << "调用无参(默认)构造函数" << endl;
}
Person(int a)
{
age = a;
cout << "调用有参构造函数" << endl;
}
//拷贝构造函数用于拷贝传入的类到自身类上
//除了拷贝构造函数外都是普通构造函数
Person(const Person &p)//传入的类不希望被改变所以加const 传入引用用p指向该类
{
age = p.age;
cout << "调用拷贝构造函数" << endl;
}
~Person()
{
cout << "析构函数的调用" << endl;
}
int age;
};
void test()
{
//调用
//1.括号法
//注意:调用无参构造时不要输入()
//Person p();会被编译器认为是函数的声明
Person p;//调用无参构造函数
Person p1(10);//调用有参函数构造
Person p2(p1);//调用拷贝构造函数
cout <<"p1的年龄"<< p1.age << endl;
cout <<"p2的年龄"<< p2.age << endl;
//2.显式法
Person p3;//调用无参构造函数
Person p4=Person (10);//调用有参函数构造
Person p5=Person (p1);//调用拷贝构造函数
//Person(10)为匿名对象 等号左侧就是它的名
//特点:当前行结束时,系统会立即回收掉匿名对象 即它的析构函数会在该行结束后就调用而不是test函数结束
//3.隐式转换法
Person p6 = 10; //调用有参函数构造 相当于Person p6=Person(10); 假如有两个参数就是 Person p6 = (10,9);
Person p7 = p1;//调用拷贝构造函数 相当于Person p7=Person(p1);
}
int main()
{
test();
system("pause");
return 0;
}
#include<iostream>
using namespace std;
class Person
{
public:
//默认构造函数
Person();
//有参构造
Person(int age, int height);
int m_Age;
int* m_Height;
};
//默认构造
Person::Person()
{
cout << "默认构造函数的调用!" << endl;
this->m_Age = 0;
this->m_Height = new int(0);
}
//有参构造,把age赋值给m_Age,身高用m_Height指向
Person::Person(int age,int height)
{
cout << "有参构造函数的调用!" << endl;
this->m_Age = age;
this->m_Height = new int(height);
}
int main()
{
Person p(18,175);
cout << "此人的年龄是: " << p.m_Age << endl;
cout << "此人的身高是: " << *(p.m_Height) << endl;
return 0;
}
委托构造函数
委托构造函数就是把自己构造的事情,交给其他的构造函数顺带完成
#include<iostream>
using namespace std;
class Person
{
public:
//默认构造函数
Person();
//有参构造
Person(int age, int height);
//拷贝构造
Person(const Person& p);
int m_Age;
int* m_Height;
};
//默认构造
Person::Person() :Person(0, 0)
{
cout << "委托构造函数的调用!" << endl;
}
//有参构造,把age赋值给m_Age,身高用m_Height指向
Person::Person(int age, int height)
{
cout << "有参构造函数的调用!" << endl;
this->m_Age = age;
this->m_Height = new int(height);
}
//拷贝构造函数调用
Person::Person(const Person& p)
{
cout << "拷贝构造函数的调用!" << endl;
this->m_Age = p.m_Age;
this->m_Height = new int(*p.m_Height);
}
int main()
{
Person p;
cout << "p的年龄是: " << p.m_Age << endl;
cout << "p的身高是: " << *(p.m_Height) << endl;
return 0;
}
#include<iostream>
using namespace std;
class Person
{
public:
//默认构造函数
Person();
//有参构造
Person(int age, int height);
//拷贝构造
Person(const Person& p);
int m_Age;
int* m_Height;
};
//默认构造
Person::Person()
{
cout << "默认构造函数的调用!" << endl;
this->m_Age = 0;
this->m_Height = new int(0);
}
//有参构造,把age赋值给m_Age,身高用m_Height指向
Person::Person(int age, int height)
{
cout << "有参构造函数的调用!" << endl;
this->m_Age = age;
this->m_Height = new int(height);
}
//拷贝构造函数调用
Person::Person(const Person& p)
{
cout << "拷贝构造函数的调用!" << endl;
this->m_Age = p.m_Age;
this->m_Height = new int(*p.m_Height);
}
int main()
{
Person p1(18, 175);
cout << "p1的年龄是: " << p1.m_Age << endl;
cout << "p1的身高是: " << *(p1.m_Height) << endl;
Person p2(p1);//将对象p1复制给p2。注意复制和赋值的概念不同
cout << "p2的年龄是: " << p2.m_Age << endl;
cout << "p2的身高是: " << *(p2.m_Height) << endl;
return 0;
}
#include<iostream>
#include<string>
using namespace std;
class Point {
public:
Point(int a,int b,int c)
{
this->a = a;
this->b = b;
this->c = c;
cout << "这是Pointd的有3个默认参数的构造函数! "<<this->a<<" "<<this->b<<" "<<this->c<<endl;
}
Point(int a, int b)
{
this-> a= a;
this->b = b;
Point(3, 4, 5);//产生一个匿名对象,纸条语句执行结束,匿名对象会被析构。
cout << "这是Pointd的有2个默认参数的构造函数! " << this->a << " " << this->b << endl;
}
~Point()
{
cout << "Point对象被析构了! "<<this->a << " " << this->b << " " << this->c << endl;
}
int getC()
{
return c;
}
private:
int a;
int b;
int c;
};
int run()
{
Point aa(1, 2);
cout << aa.getC() << endl; //c的值为垃圾值,因为匿名对象被创建有立即析构了
//就算用不析构的方式,也是垃圾值,因为c是不同对象中的元素
//在2个参数的构造函数中,没有显式初始化c,不能通过构造其他对象而在本构造对象中访问未初始化的数据
return 0;
}
int main()
{
run();
cout << "hello world!\n";
return 0;
}
C++之构造函数的初始化参数表
初始化列表是成员变量定义的地方
不管有没有显示写初始化参数列表,编译器在调用构造函数时都会先走初始化参数列表
在C++11新特性中,允许在类中声明变量时给上缺省值,这里的缺省值实际上是给初始化参数列表使用的
初始化参数列表初始化的顺序与成员函数的声明顺序一致
class A{
public:
//在函数的括号后使用 : 成员变量(参数) 使用逗号进行分割
//A(int a,int b):x(a),y(b){}
A(int a,int b)
: x(a)
, y(b)
{}
private:
int x;
int y;
};
struct Test1
{
Test1() // 无参构造函数
{
cout << "Construct Test1" << endl ;
}
Test1(const Test1& t1) // 拷贝构造函数
{
cout << "Copy constructor for Test1" << endl ;
this->a = t1.a ;
}
Test1& operator = (const Test1& t1) // 赋值运算符
{
cout << "assignment for Test1" << endl ;
this->a = t1.a ;
return *this;
}
int a ;
};
struct Test2
{
Test1 test1 ;
Test2(Test1 &t1)
{
test1 = t1 ;
}
};
常用构造:
Test1() // 无参构造函数
A(int a,int b):x(a),y(b){}//构造函数初始化参数表
Test1(const Test1& t1) // 拷贝构造函数
Test1& operator = (const Test1& t1) // 赋值运算符
初始化列表实例
class Stack {
public:
Stack(int capacity = 10): _a((int*)malloc(sizeof(int))), _top(0), _capacity(capacity)
{
if (nullptr == _a)
{
perror("fail malloc");
exit(-1);
}
memset(_a, 0, sizeof(int) * capacity);
}
private:
int* _a;
int _top;
int _capacity;
};
class A {
public:
A():_x(1),_a1(3),_a2(1),_z(_a1)
{
_a1++;
_a2--;
}
private:
int _a1 = 1; //声明
int _a2 = 2;
const int _x;
int& _z;
B _bb;
};
析构函数
清理资源:析构函数通常用于释放对象在其生命周期内分配的资源,例如动态分配的内存、打开的文件句柄等
#include <iostream>
using namespace std;
class Example {
public:
// 构造函数
Example() {
cout << "Constructor called" << endl;
data = new int[10]; // 动态分配内存
}
// 析构函数
~Example() {
cout << "Destructor called" << endl;
delete[] data; // 释放动态分配的内存
}
private:
int* data;
};
int main() {
Example ex; // 创建对象,调用构造函数
// 对象生命周期结束时,自动调用析构函数
return 0;
}
#include <iostream>
using namespace std;
//构造函数的实现
class Student
{
public: //:flag(Flag)解释:类内变量(参数)
Student(int Flag,char Sex, string Name, const char *File); //类外参数列表函数声明
//类内使用方式,不需要声明
// Student(int Flag,char Sex, string Name) :flag(Flag),sex(Sex),name(Name) //构造函数
// {
// // //错误形式:
// // int a=10;
// // int &b;
// // b=a; //大错特错
// cout<<"类内:flag="<<this->flag<<endl;
// cout<<"类内:sex="<<this->sex<<endl;
// cout<<"类内:name="<<this->name<<endl;
// }
private:
int &flag; //必须用参数列表方式
char sex;
string name;
const char *file = NULL; //必须使用参数列表方式
//引用变量定义法则->构造函数名(参数):引用变量名(参数)
};
//类外构造函数
Student::Student(int Flag,char Sex, string Name,const char *File):flag(Flag),sex(Sex),name(Name),file(File) //构造函数
{
// //错误形式:
// this->flag=Flag;相当于以下:
// int a=10;
// int &b;
// b=a; //大错特错
cout<<"类内:flag="<<this->flag<<endl;
cout<<"类内:sex="<<this->sex<<endl;
cout<<"类内:name="<<this->name<<endl;
cout<<"类内:file="<<this->file<<endl;
}
int main(int argc, char const *argv[])
{
Student st(100,'M',"JKJK","/usr/include/linux/fb.h"); //先创建了对象,后执行构造函数
return 0;
}
struct foo
{
int i ;
int j ;
foo(int x):i(x), j(i){}; // ok, 先初始化i,后初始化j
};
浅拷贝
int* shallowCopy(int* original) {
return original; // 只是复制了指针,没有复制指针指向的内存
}
浅拷贝只是复制了对象的引用或指针,而不是复制对象所指向的资源
深拷贝
int* deepCopy(int* original, size_t size) {
int* copy = new int[size];
std::copy(original, original + size, copy);
return copy; // 复制了指针指向的内存
}
class MyArray {
public:
MyArray(size_t size) : size_(size), data_(new int[size]) {}
~MyArray() { delete[] data_; }
// 禁用拷贝构造函数和赋值操作符以防止浅拷贝
MyArray(const MyArray&) = delete;
MyArray& operator=(const MyArray&) = delete;
// 实现深拷贝的拷贝构造函数
MyArray(const MyArray& other) : size_(other.size_), data_(new int[other.size_]) {
std::copy(other.data_, other.data_ + size_, data_);
}
// 实现深拷贝的赋值操作符
MyArray& operator=(MyArray other) {
std::swap(size_, other.size_);
std::swap(data_, other.data_);
return *this;
}
private:
size_t size_;
int* data_;
};
C++ string使用介绍
string类初始化s5 s6
string s5 = string(“value”);
string s6(string(“value”));
区别初始化和赋值操作
string st1, st2(2,‘b’);
st1 = st2; //st1此时已近占据了一块内存
string对象的操作
string s1;
cin >> s1;
cout << s1 << endl;
用getline读取一整行
string s1;
getline(cin, s1);
cout << s1 << endl;
比较string的大小
string str = "Hello";
string phrase = "Hello ";
尽管两者的前面对应的字符都一样,但是phrase长度长(多一个空格),所以phrase>str
string str2 = "Hello";
string phrase2 = "Hi ";
这种情况比较的是第一个相异字符,根据字符值比较大小,因为i的字符值>e的字符值,所以phrase2> str2
一般使用时相等
两个string对象相加
string str = "Hello,";
string phrase = "world ";
string s = str + phrase;
str += phrase;//相当于str = str + phrase
cout << s << endl;
cout << str << endl;
string对象加上一个字符(或字符串)字面值
string str = "Hello";
string phrase = "world";
string s = str + ','+ phrase+ '\n';
cout << s ;
元素访问
使用下标运算符[]
string s = "Hello world!";
cout << s[0] << endl;
cout << s[s.size()-1] << endl;
cout << s << endl;
s[0] = 'h';
cout << s << endl;
int main()
{
// []重载使string可以像字符数组一样访问
string s1("hello world");
cout << s1[0] << endl;
cout << s1[1] << endl;
// at 于[] 功能相同,只不过[]的越界是由assert来限制,而at则是抛异常
cout << s1.at(0) << endl;
cout << s1.at(1) << endl;
// front访问string中第一个字符
cout << s1.front() << endl;
// back访问string中最后一个字符
cout << s1.back() << endl;
return 0;
}
遍历整个string对象
string s = "Hello world!";
for (decltype(s.size()) index = 0; index != s.size(); index++){
cout << s[index] << ",";
}
cout<<endl;
使用迭代器
string s = "Hello world!";
for (auto i = s.begin(); i != s.end(); i++){
cout << *i << ",";
}
cout << endl;
int main()
{
string s1("hello world");
// 三种遍历方式
// 1、通过[]来访问每一个字符
for (int i = 0; i < s1.size(); i++)
{
cout << s1[i] << " ";
}
cout << endl;
// 2、通过迭代来来访问每一个字符
string::iterator it = s1.begin();
while (it != s1.end())
{
cout << *it << " ";
it++;
}
cout << endl;
// 3、通过范围for(其实范围for就是编译器替换成了迭代器遍历的方法)
for (auto ch : s1)
{
cout << ch << " ";
}
cout << endl;
return 0;
}
通过反向迭代器,对其逆向遍历;反向迭代器的类型为 string::reverse_iterator
int main()
{
string s1("hello world");
string::reverse_iterator rit = s1.rbegin();
while (rit != s1.rend())
{
cout << *rit << " ";
rit++;
}
}
void Print(const string& s)
{
// 形参用const对象的引用接收,此时只能用const迭代器系列函数
string::const_iterator cit = s.cbegin();
while (cit != s.cend())
{
cout << *cit << " ";
cit++;
}
cout << endl;
}
使用基于范围的for语句
string str("some string");
for (auto c : str)
cout << c << ",";
cout << endl;//通过auto关键字让编译器推断c的类型,c这里的类型实际上推断出的是char类型,c每次的值都是str中的一个字符
拷贝数组
const char *cp = "hello world";//最后有一个空字符
char cp2[] = "hello world";//最后有一个空字符
char cp3[] = { 'h', 'e' };//最后没有空字符
(1) string s1(cp);//s1为”hello world”,长度为11
(2) string s2(cp2);//s2为”hello world”,长度为11
(3) string s3(cp3);//因为cp3不以空字符结尾,所以这是未定义行为
(4) string s4(cp,5);//s4为”hello”,长度为5。将cp改为cp2一样
(5) string s5(cp,13);//s5为”hello world ”,长度为13,后面有两个空字符。将cp改为cp2一样
(6) string s6(cp3,2);//s6为”he”,长度为2
拷贝string对象
string s(s1,pos)
string s(s1,pos,len)
第一个将s1从下标pos开始拷贝到结尾。当pos>s1.size()时,为未定义行为;当pos=s1.size(),拷贝一个空字符
第二个将s1从下标pos开始拷贝,拷贝len个字符。当pos>s1.size()时,为未定义行为;当pos=s2.size(),拷贝一个空字符
string s1("value");
(1) string s2(s1, 1);//s2为” alue”,长度为4
(2) string s3(s1, 5);//s3为””,长度为0
(3) string s8(s1, 6);// 错误,未定义的行为,抛出异常
(4) string s4(s1, 1,3);// s4为” alu”,长度为3
(5) string s5(s1, 1,8);// 正确,s5为” alue”,长度为4
(6) string s6(s1, 5,8);// s6为””,长度为0
(7) string s7(s1, 6,1);// 错误,未定义的行为,抛出异常
使用substr成员函数
s.substr(pos,n)
返回一个string对象,返回的对象包含s从pos下标开始的n个字符。pos和n均为可选参数。pos默认为下标0;n默认为s.size()-pos
string s ("value");
(1)string s2 = s.substr();//s2为”value”,大小为5
(2)string s3 = s.substr(2);//s3为”lue”,大小为3
(3)string s4 = s.substr(5);//s3为””,大小为0
(4)string s5 = s.substr(6);//错误,s5的大小为pos = 5,小于s.size()
(5)string s6 = s.substr(1,2);// s6为”al”,大小为2
(6)string s7 = s.substr(1,7);// s7为”alue”,大小为4
(7)string s8 = s.substr(5,7);// s8为””,大小为0
(8)string s9 = s.substr(6,7);// 错误,s9的大小为pos = 5,小于s.size()
string对象的insert()
iterator insert( iterator pos, CharT ch )
void insert( iterator pos, size_type count, CharT ch )
void insert( iterator pos, InputIt first, InputIt last )
插入初始化列表
string s1("value");
s1.insert(s1.begin(), 's');//执行后,s1为"svalue"
s1.insert(s1.begin(), 1, 's');//执行后,s1为"ssvalue"
s1.insert(s1.begin(), s1.begin(), ++s1.begin());//执行后,s1为"sssvalue"
s1.insert(s1.end(), {'1','2'});//执行后,s1为"sssvalue12"
string s1("value");
s1.insert(0,2,’s’); //执行后,s1为” ssvalue”
s1.insert(5,2,’s’); //执行后,s1为” valuess”
string s1("value");
string s3 = "value";
const char* cp = "value";
s1.insert(0,s3);//执行完后,s1为" valuevalue"
s1.insert(0,cp); //执行完后,s1为" valuevalue"
string对象的erase()
basic_string & erase(size_type pos=0, size_type n=npos)
解释:如果string对象s调用,它删除s从pos下标开始的n个字符,并返回删除后的s。当pos > s.size()时,报错
iterator erase(const_iterator position)
解释:如果string对象s调用,它删除s迭代器position位置的字符,并返回下一个字符的迭代器。
iterator erase(const_iterator first, const_iterator last)
解释:如果string对象s调用,它删除s迭代器[first,last)区间的字符,并返回last字符的迭代器。
string s1("value");
string s2("value");
string s3("value");
string s4("value");
s1.erase();//执行后,s1为空
s2.erase(0,2); //执行后,s2为”lue”
s3.erase(s3.begin());//执行后,s3为”alue”
s4.erase(s4.begin(),++s4.begin());//执行后,s4为”alue”
string对象的append()和replace()
string s("i love China!");
s.append("forever");//执行完后,s=” i love China! forever”
string s("i very love China!");
const char* cp1 = "truly";
const char* cp2 = "truly!!!";
string str1 = "really";
string str2 = "really!!!";
//1.将s从下标2开始删除4个字符,删除后在下标2处插入cp1
s.replace(2,4,cp1);//s=” i truly love China!”
//2.将s从下标2开始删除5个字符,删除后在下标2插入cp2的前5个字符
s.replace(2, 5, cp2,5); //s=” i truly love China!”
//3.将s从下标2开始删除5个字符,删除后在下标2插入str1
s.replace(2, 5, str1);//s=”i really love China!”
//4.将s从下标2开始删除6个字符,删除后在下标2插入str2从下标0开始的6个字符
s.replace(2, 6, str2,0,6);//s=”i really love China!”
//5.将s从下标2开始删除6个字符,删除后在下标2插入4个’*’字符
s.replace(2, 6, 4, '*');//s=”i **** love China!”
string s1("bad phrase");
const char* cp3 = "sample";
const char* cp4 = "sample!!!";
string str3 = "useful";
string str4 = "useful!!!";
//1.删除[s1.begin(),s1. begin()+3)区间字符,删除后插入cp3
s1.replace(s1.begin(),s1.begin()+3,cp3);//s1="sample phrase"
//2.删除[s1.begin(),s1. begin()+6)区间字符,删除后插入cp4的前6个字符
s1.replace(s1.begin(),s1.begin()+6,cp4,6);//s1="sample phrase"
//3.删除[s1.begin(),s1. begin()+6)区间字符,删除后插入str3
s1.replace(s1.begin(),s1.begin()+6, str3);//s1="useful phrase"
//4.删除[s1.begin(),s1. begin()+6)区间字符,删除后插入str4[str4.begin(),str4. begin()+6)区间字符
s1.replace(s1.begin(),s1.begin()+6, str4.begin(),str4.begin() + 6);//s1="useful phrase"
//5. 删除[s1.begin(),s1. begin()+6)区间字符,删除后插入4个’*’字符
s1.replace(s1.begin(),s1.begin()+6, 4, '*');//s1="**** phrase"
//6. 删除[s1.begin(),s1. begin()+4)区间字符,删除后插入初始化列表
s1.replace(s1.begin(), s1.begin() + 4, {'3','4','5'});//s1="345 phrase"
string对象的assign()
assign方法可以理解为先将原字符串清空,然后赋予新的值作替换
std::string str;
std::string base = "The quick brown fox jumps over a lazy dog.";
//1.参数形式1
str.assign(base);
std::cout << str << '\n';
//2.参数形式2:将base从下标10开始的9个字符赋值给str
str.assign(base, 10, 9);
std::cout << str << '\n'; // "brown fox"
//3.参数形式4:将"pangrams are cool"的前7个字符赋值给str
str.assign("pangrams are cool", 7);
std::cout << str << '\n'; // "pangram"
//4.参数形式3:将"c-string"赋值给str
str.assign("c-string");
std::cout << str << '\n'; // "c-string"
//5.参数形式5:将10个字符'*'赋值给str
str.assign(10, '*');
std::cout << str << '\n'; // "**********"
//6.参数形式6:将[base.begin() + 16, base.end() - 12)赋值给str
str.assign(base.begin() + 16, base.end() - 12);
std::cout << str << '\n'; // "fox jumps over"
//7.参数形式7:将初始化列表{'l','o','v','e'}赋值给str
str.assign({ 'l', 'o', 'v', 'e' });
std::cout << str << '\n'; // "love"
string对象的搜索操作
string搜索函数 描述
s.find(args) 在s中查找第一次出现args的下标
s.rfind(args) 在s中查找最后一次出现args的下标
s.find_first_of(args) 在s中查找第一个在args中出现的字符,返回其下标
s.find_first_not_of(args) 在s中查找第一个不在args中出现的字符,返回其下标
s.find_last_of(args) 在s中查找最后一个在args中出现的字符,返回其下标
s.find_last_not_of(args) 在s中查找最后一个不在args中出现的字符,返回其下标
args参数格式如下
c,pos 搜索单个字符。从s中位置pos开始查找字符c。pos可省略,默认值为0
s2,pos 搜索字符串。从s中位置pos开始查找字符串string对象s2。pos可省略,默认值为0
cp,pos 搜索字符串。从s中位置pos开始查找指针cp指向的以空字符结尾的C风格字符串。pos可省略,默认值为0
cp,pos,n 从s中位置pos开始查找指针cp指向的数组的前n个字符。
std::string str("There are two needles in this haystack with needles.");
std::string str2("needle");
//1.对应参数args为s2,pos
std::size_t found = str.find(str2);//返回第一个"needles"n的下标
if (found != std::string::npos)
std::cout << "first 'needle' found at: " << found << '\n';
//2.对应参数args为cp,pos, n
found = str.find("needles are small", found + 1, 6);
if (found != std::string::npos)
std::cout << "second 'needle' found at: " << found << '\n';
//3.对应参数args为cp,pos
found = str.find("haystack");
if (found != std::string::npos)
std::cout << "'haystack' also found at: " << found << '\n';
//4.对应参数args为c,pos
found = str.find('.');
if (found != std::string::npos)
std::cout << "Period found at: " << found << '\n';
// 替换第一个needle:
str.replace(str.find(str2), str2.length(), "preposition");
std::cout << str << '\n';
cout << " rfind()函数:" << endl;
std::string str("The sixth sick sheik's sixth sheep's sick.");
std::string key("sixth");
std::size_t found = str.rfind(key);//找到最后一个sixth的下标
if (found != std::string::npos)
str.replace(found, key.length(), "seventh");//替换找到的sixth
std::cout << str << '\n';
std::string str("Please, replace the vowels in this sentence by asterisks.");
std::size_t found = str.find_first_of("aeiou");
while (found != std::string::npos)
{
str[found] = '*';
found = str.find_first_of("aeiou", found + 1);
}
std::cout << str << '\n';
std::string str("look for non-alphabetic characters...");
std::size_t found = str.find_first_not_of("abcdefghijklmnopqrstuvwxyz ");
if (found != std::string::npos)
{
std::cout << "The first non-alphabetic character is " << str[found];
std::cout << " at position " << found << '\n';
}
#include <iostream> // std::cout
#include <string> // std::string
#include <cstddef> // std::size_t
void SplitFilename (const std::string& str)
{
std::cout << "Splitting: " << str << '\n';
std::size_t found = str.find_last_of("/\\");
std::cout << " path: " << str.substr(0,found) << '\n';
std::cout << " file: " << str.substr(found+1) << '\n';
}
int main ()
{
std::string str1 ("/usr/bin/man");
std::string str2 ("c:\\windows\\winhelp.exe");
SplitFilename (str1);
SplitFilename (str2);
system("pause");
return 0;
}
std::string str("Please, erase trailing white-spaces \n");
std::string whitespaces(" \t\f\v\n\r");
std::size_t found = str.find_last_not_of(whitespaces);
if (found != std::string::npos)
str.erase(found + 1);
else
str.clear(); // str is all whitespace
std::cout << '[' << str << "]\n";
string对象的compare操作
compare的参数形式
s2 比较s和s2
pos1, n1, s2 将s中从pos1开始的n1个字符与s2比较
pos1, n1, s2, pos2, n2 将s中从pos1开始的n1个字符与s2中从pos2开始的n2个字符比较
cp 比较s与cp指向的以空字符结尾的数组
pos1, n1, cp 将s中从pos1开始的n1个字符与cp指向的以空字符结尾的数组比较
pos1, n1, cp,n2 将s中从pos1开始的n1个字符与cp指向的以空字符结尾的数组前n个字符比较
std::string str1("green apple");
std::string str2("red apple");
//1.str1和str2比较:参数形式1
if (str1.compare(str2) != 0)
std::cout << str1 << " is not " << str2 << '\n';
//2.str1的下标6开始的5个字符和"apple"比较:参数形式5
if (str1.compare(6, 5, "apple") == 0)
std::cout << "still, " << str1 << " is an apple\n";
//3.str2的下标str2.size() - 5(就是下标6)开始的5个字符和"apple"比较:参数形式5
if (str2.compare(str2.size() - 5, 5, "apple") == 0)
std::cout << "and " << str2 << " is also an apple\n";
//4.str1的下标6开始的5个字符和str2的下标4开始的5个字符比较:参数形式3
if (str1.compare(6, 5, str2, 4, 5) == 0)
std::cout << "therefore, both are apples\n";
//5.str1的下标6开始的5个字符和"apple pie"的前5个字符比较:参数形式6
if (str1.compare(6, 5, "apple pie",5) == 0)
std::cout << "apple pie is maked by apple\n";
//6.str1和"poisonous apple"比较:参数形式4
if (str1.compare("poisonous apple") < 0)
std::cout << "poisonous apple is not a apple\n";
to_string
std::string pi = "pi is " + std::to_string(3.1415926);
std::string perfect = std::to_string(1 + 2 + 4 + 7 + 14) + " is a perfect number";
std::cout << pi << '\n';
std::cout << perfect << '\n';
stoi(s,p,b) 例子(stol stoll stoul stoull类似,不再举例)
std::string str_dec = "2001, A Space Odyssey";
std::string str_hex = "40c3";
std::string str_bin = "-10010110001";
std::string str_auto = "0x7f";
std::string::size_type sz; // alias of size_t
//1.转换基数为10进制,sz保存','下标,i_dec = 2001
int i_dec = std::stoi(str_dec, &sz);
//2.转换基数为16进制。所以i_hex = 0x40c3,十进制为16579
int i_hex = std::stoi(str_hex, nullptr, 16);
//3.转换基数为2进制。所以i_bin = -10010110001B,十进制为-1201
int i_bin = std::stoi(str_bin, nullptr, 2);
//4.自动确定 转换基数
int i_auto = std::stoi(str_auto, nullptr, 0);
std::cout << str_dec << ": " << i_dec << " and [" << str_dec.substr(sz) << "]\n";
std::cout << str_hex << ": " << i_hex << '\n';
std::cout << str_bin << ": " << i_bin << '\n';
std::cout << str_auto << ": " << i_auto << '\n';
cout << "stof示例" << endl;
std::string orbits("686.97 365.24");
std::string::size_type sz; // alias of size_t
//1.mars = 686.97,sz保存空格下标
float mars = std::stof(orbits, &sz);
//1.将" 365.24"转换为float类型,earth = 686.97
float earth = std::stof(orbits.substr(sz));
std::cout << "One martian year takes " << (mars / earth) << " Earth years.\n";
六个简单一些的接口
int main()
{
string s1("hello world");
// string中储存的字符个数(不包括\0)
cout << s1.length() << endl;
// 与length功能相同
cout << s1.size() << endl;
// 可以最多储存多少个字符(理论值,实际上并没有那么多)
cout << s1.max_size() << endl;
// string的当前容量
cout << s1.capacity() << endl;
// 当前string对象是否为空
cout << s1.empty() << endl;
// 清空s1中所有字符
s1.clear();
return 0;
}
修改
int main()
{
string tmp("xxxx");
string s1;
// 尾加字符
// void push_back (char c);
s1.push_back('c');
// 尾加string类
// string& append (const string& str);
s1.append(tmp);
// 尾加string从subpos位置开始的sublen个字符
//string& append (const string& str, size_t subpos, size_t sublen);
s1.append(tmp, 2, 3);
// 用字符指针指向的字符串/字符尾加
// string& append (const char* s);
s1.append("hello world");
// 用字符指针指向的字符串的前n个字符尾加
// string& append (const char* s, size_t n);
s1.append("hello world", 6);
// 尾加n个c字符
// string& append (size_t n, char c);
s1.append(5, 'x');
// 迭代器区间追加
// template <class InputIterator>
// string& append(InputIterator first, InputIterator last);
s1.append(tmp.begin(), tmp.end());
cout << s1 << endl;
return 0;
}
int main()
{
string tmp("xxxx");
string s1;
// 尾加字符
// void push_back (char c);
s1.push_back('c');
// 尾加string类
// string& append (const string& str);
s1.append(tmp);
// 尾加string从subpos位置开始的sublen个字符
//string& append (const string& str, size_t subpos, size_t sublen);
s1.append(tmp, 2, 3);
// 用字符指针指向的字符串/字符尾加
// string& append (const char* s);
s1.append("hello world");
// 用字符指针指向的字符串的前n个字符尾加
// string& append (const char* s, size_t n);
s1.append("hello world", 6);
// 尾加n个c字符
// string& append (size_t n, char c);
s1.append(5, 'x');
// 迭代器区间追加
// template <class InputIterator>
// string& append(InputIterator first, InputIterator last);
s1.append(tmp.begin(), tmp.end());
cout << s1 << endl;
return 0;
int main()
{
string tmp("hello world");
string s1;
// 使用string类对其赋值
// string& assign (const string& str);
s1.assign(tmp);
cout << s1 << endl;
// 使用string类中从subpos位置开始的sublen个串来赋值
// string& assign (const string& str, size_t subpos, size_t sublen);
s1.assign(tmp, 2, 5);
cout << s1 << endl;
// 使用字符指针所指向的字符串对其赋值
// string& assign (const char* s);
s1.assign("hello naiths");
cout << s1 << endl;
// 使用字符指针所指向的字符串的前n个对其赋值
// string& assign (const char* s, size_t n);
s1.assign("hello naiths", 7);
cout << s1 << endl;
// 使用n个c字符对其赋值
// string& assign (size_t n, char c);
s1.assign(10, 'x');
cout << s1 << endl;
// 使用迭代器对其赋值
// template <class InputIterator>
// string& assign(InputIterator first, InputIterator last);
s1.assign(tmp.begin(), tmp.end());
cout << s1 << endl;
return 0;
}
}
int main()
{
string tmp("hello world");
string s1;
// 在pos位置插入string类字符串
// string& insert (size_t pos, const string& str);
s1.insert(0, tmp);
cout << s1 << endl;
// 在pos位置插入str的子串(subpos位置开始的sublen个字符)
// string& insert (size_t pos, const string& str, size_t subpos, size_t sublen);
s1.insert(7, tmp, 0, 6);
cout << s1 << endl;
// 在pos位置插入字符指针指向的字符串
// string& insert (size_t pos, constchar* s);
s1.insert(2, "xxx");
cout << s1 << endl;
// 在pos位置插入字符指针指向的字符串的前n个字符
// string& insert (size_t pos, const char* s, size_t n);
s1.insert(7, "hello naiths", 8);
cout << s1 << endl;
// 在pos位置插入n个c字符
// string& insert (size_t pos, size_t n, char c);
s1.insert(0, 5, 'y');
cout << s1 << endl;
// 指定迭代器的位置插入n个字符c
// void insert (iterator p, size_t n, char c);
string::iterator it = s1.begin() + 10;
s1.insert(it, 10, 'z');
cout << s1 << endl;
// 指定迭代器的位置插入字符c
// iterator insert (iterator p, char c);
s1.insert(s1.begin(), 'A');
cout << s1 << endl;
// 指定p位置插入迭代器区间的字符
// template <class InputIterator>
// void insert(iterator p, InputIterator first, InputIterator last);
s1.insert(s1.begin(), tmp.begin() + 3, tmp.begin() + 8);
cout << s1 << endl;
// 删除pos位置开始的len个字符
// string& erase (size_t pos = 0, size_t len = npos);
s1.erase(2, 5);
cout << s1 << endl;
// 删除迭代器位置的那个字符
// iterator erase (iterator p);
s1.erase(s1.begin());
cout << s1 << endl;
// 删除迭代器区间的字符
// iterator erase (iterator first, iterator last);
s1.erase(s1.begin() + 2, s1.begin() + 5);
cout << s1 << endl;
return 0;
}
int main()
{
string s1("hello world");
cout << s1.c_str() << endl;
cout << s1.data() << endl;
return 0;
}
int main()
{
// 从string拷贝给字符数组
// size_t copy (char* s, size_t len, size_t pos = 0) const;
char arr[] = "hello world";
string s1("xxxxxxxxxxxxxxxx");
s1.copy(arr, 6, 2);
cout << s1 << endl;
// 寻找某个字符串的起始位置
// size_t find (const string& str, size_t pos = 0) const;
string tmp("abc");
string s2("abbadabcdeabcd");
size_t pos1 = s2.find(tmp, 0);
// 从后往前找
//size_t rfind (const string& str, size_t pos = npos) const;
size_t pos2 = s2.rfind(tmp, s2.size() - 1);
cout << pos1 << endl;
cout << pos2 << endl;
return 0;
}
int main()
{
string tmp("acm");
string s1("This is a program");
// 从前往后寻找tmp中任意一个字符的位置(参数二不填则默认从第一个位置开始寻找)
size_t pos1 = s1.find_first_of(tmp, 0);
// 从后往前寻找tmp中任意一个字符的位置(参数二不填则默认从最后一个位置开始寻找)
size_t pos2 = s1.find_last_of(tmp, s1.size() - 1);
// 从前往后寻找任意一个非tmp中的字符的位置(参数二不填则默认从第一个位置开始寻找)
size_t pos3 = s1.find_first_not_of(tmp, 0);
// 从后往前寻找任意一个非tmp中的字符的位置(参数二不填则默认从最后一个位置开始寻找)
size_t pos4 = s1.find_last_not_of(tmp, s1.size() - 1);
cout << pos1 << endl;
cout << pos2 << endl;
cout << pos3 << endl;
cout << pos4 << endl;
return 0;
}
int main()
{
//string substr(size_t pos = 0, size_t len = npos) const;
string tmp("hello world");
string s1 = tmp.substr(6, 5);
cout << s1 << endl;
string s2("abc");
string s3("abcc");
string s4("aac");
string s5("abc");
cout << s2.compare(s3) << endl;
cout << s2.compare(s4) << endl;
cout << s2.compare(s5) << endl;
return 0;
}
i
C++ 友元函数与友元类
友元函数
友元函数(Friend Function)是一个不是类成员但可以访问类的私有或保护成员的函数。通过在类定义中声明一个函数为友元函数,该函数可以直接操作类的私有数据
定义与用法
在类内部声明友元函数时,需要使用关键字friend
#include <iostream>
using namespace std;
class MyClass {
private:
int data;
public:
MyClass(int value) : data(value) {}
// 声明友元函数
friend void displayData(const MyClass& obj);
};
// 友元函数的定义
void displayData(const MyClass& obj) {
cout << "Data: " << obj.data << endl;
}
int main() {
MyClass obj(42);
displayData(obj); // 友元函数访问私有成员
return 0;
}
友元类
友元类(Friend Class)是指一个类允许另一个类访问它的所有私有和保护成员。在这种情况下,友元类的所有成员函数都可以访问被友元类声明的类的私有成员
定义与用法 关键字friend后面跟的是类名
#include <iostream>
using namespace std;
class B;
class A {
private:
int data;
public:
A(int value) : data(value) {}
// 声明B类为友元类
friend class B;
};
class B {
public:
void showData(const A& obj) {
// B类可以访问A类的私有成员
cout << "A::data = " << obj.data << endl;
}
};
int main() {
A objA(100);
B objB;
objB.showData(objA); // 通过B类访问A类的私有成员
return 0;
}
c++ operator隐式类型转换
#include <iostream>
#include <sstream>
using namespace std;
class FuncObj
{
public:
FuncObj(int n): _n(n) {
cout << "constructor" << endl;
}
bool operator()(int v) {//操作符重载,重载()使得该对象成为一个函数对象
cout << "operator overload" << endl;
return v > _n; //FuncObj用过操作符重载可以判断传入的参数是否大于一个预先设定好的值(在构造函数里指定)
}
operator string() {//定义表名FuncObj对象可以隐身转换成string
cout << "type convert" << endl;
stringstream sstr;
sstr << _n;
return sstr.str();
}
int _n;
};
int main()
{
FuncObj obj(10);
if (obj(11))
cout << "11 greater than 10" << endl;
string str(obj);//注意在函数声明时,operator关键词出现在返回类型的前面,区别与操作符重载时的用法
cout << str << endl;
}
#include <iostream>
using namespace std;
class point {
public:
int x,y;
point(int x=0, int y=0):x(x),y(y) {}
}
void displayPoint(const point &p)
{
cout << "( " << p.x << "," << p.y << " )";
}
int main()
{
displayPoint(1);//函数displayPoint需要的是point类型的参数,而我们传入一个int数据,这个程序能够成功运行,并且成功输出(1,0)说明隐式调用了构造函数
point p = 1;//在对象刚刚定义的时候,使用赋值操作符=,调用的是构造函数,而不是operator=运算符
//这种悄然发生的事情,有时可以带来便利,让你的程序可以继续运行,而更多的会带来很多意想不到的结果,explict关键字用来避免这样的情况发生
}
//c++11中对explicit的解释为指定构造函数或转换函数(operator type())为显示,即它不能用于隐式转换和复制初始化
#include <iostream>
#include <string>
using namespace std;
class point {
public:
point(int x, int y):x(x),y(y) {
cout << "constructor" << endl;
}
//operator主要有两种作用,一是操作符重载,二是自定义对象类型的隐式转换
bool operator()(int x, int y) {
cout << "opearator overload" << endl;
return x>this->x && y>this->y;
}
operator string() {
cout << "type convert" << endl;
string str = "(" + x;
str += "," + y;
str += ")";
return str;
}
private:
int x,y;
};
int main()
{
// part1
point p1(5,6);
if(p1(10,9)){
cout << "point(10,9) is larger than point(5,6)" << endl;
}
// part2
string str(p1);
cout << str;
}
//在C++中,explicit:禁止通过构造函数进行的隐式转换,关键字用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换,只能以显示的方式进行类型转换
//在C++中,explicit关键字主要用于修饰只有一个参数的类构造函数,以防止不期望的隐式类型转换。当一个构造函数被声明为explicit时,它不能被用于隐式类型转换。
class MyString{
public:
explicit MyString(int n){
cout << "MyString(int n)!" << endl;
}
MyString(const char* str){
cout << "MyString(const char* str)" << endl;
}
};
int main(){
//给字符串赋值?还是初始化?
//MyString str1 = 1;
MyString str2(10);
//寓意非常明确,给字符串赋值
MyString str3 = "abcd";
MyString str4("abcd");
return EXIT_SUCCESS;
}
class CxString // 使用关键字explicit的类声明, 显示转换
{
public:
char *_pstr;
int _size;
explicit CxString(int size)
{
_size = size;
// 代码同上, 省略...
}
CxString(const char *p)
{
// 代码同上, 省略...
}
};
// 下面是调用:
CxString string1(24); // 这样是OK的
CxString string2 = 10; // 这样是不行的, 因为explicit关键字取消了隐式转换
CxString string3; // 这样是不行的, 因为没有默认构造函数
CxString string4("aaaa"); // 这样是OK的
CxString string5 = "bbb"; // 这样也是OK的, 调用的是CxString(const char *p)
CxString string6 = 'c'; // 这样是不行的, 其实调用的是CxString(int size), 且size等于'c'的ascii码, 但explicit关键字取消了隐式转换
string1 = 2; // 这样也是不行的, 因为取消了隐式转换
string2 = 3; // 这样也是不行的, 因为取消了隐式转换
string3 = string1; // 这样也是不行的, 因为取消了隐式转换, 除非类实现操作符"="的重载
class MyClass {
public:
explicit operator int() const {
// 转换操作的实现
return 42;
}
};
int main() {
MyClass obj;
int value1 = static_cast<int>(obj); // 正确,显式转换
int value2 = obj; // 错误,不能进行隐式转换
return 0;
}
class MyClass {
public:
explicit MyClass(int value) {
// 构造函数的实现
}
};
int main() {
MyClass obj1(42); // 正确,显式调用构造函数
MyClass obj2 = 42; // 错误,不能进行隐式转换
return 0;
}
#include<iostream>
using namespace std;
class Fraction{
public:
Fraction(int numerator, int denominator = 1): m_numerator(numerator), m_denominator(denominator){}
explicit operator double() const
{
return (double)m_numerator/m_denominator;
}
private:
int m_numerator;
int m_denominator;
}
int main(void)
{
Fraction fraction(3, 5);
double d = 3.5 + static_cast<double>(f);
cout << d << endl;
return 0;
}
explicit 关键字只能用于类内部的构造函数声明上。
explicit 关键字作用于单个参数的构造函数。
在C++中,explicit关键字用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换
C++ 运算符重载
运算符重载的实质就是函数重载或函数多态。运算符重载是一种形式的C++多态。目的在于让人能够用同名的函数来完成不同的基本操作。要重载运算符,需要使用被称为运算符函数的特殊函数形式,运算符函数形式:operatorp(argument-list)//operator 后面的’p’为要重载的运算符符号
<返回类型说明符> operator <运算符符号>(<参数表>)
{
<函数体>
}
#include <iostream>
using namespace std;
class Rect
{
private:
double width;
double height;
public:
Rect(double a, double b)
{
width = a;
height = b;
}
double area() {
return width * height;
}
// 重载小于运算符 ( < ), 按照面积比大小
bool operator<(Rect& that)
{
return this->area() < that.area();
}
friend std::ostream &operator<<(std::ostream &output, Rect &r)
{
output << "width: " << r.width << ", ";
output << "height: " << r.height << ", ";
output << "area: " << r.area();
return output;
}
friend std::istream &operator>>(std::istream &input, Rect &r)
{
input >> r.width >> r.height;
return input;
}
};
int main()
{
Rect r1(3.0, 5.0), r2(3.5, 4.5);
cout << "Area of r1 = " << r1.area() << endl;
cout << "Area of r2 = " << r2.area() << endl;
if ( r1 < r2 )
cout << "r1 is less than r2" << endl;
else
cout << "r1 is large than r2" << endl;
Rect r1(3.0, 4.0), r2(6.0, 8.0), r3;
cout << "Enter the value of object: \n";
cin >> r3;
cout << "r1: " << r1 << endl;
cout << "r2: " << r2 << endl;
cout << "r3: " << r3 << endl;
return 0;
}
#include <iostream>
using namespace std;
class Time
{
private:
int minute;
int second;
public:
Time () {
minute = 0;
second = 0;
}
Time (int m, int s) {
minute = m;
second = s;
}
void display() {
cout << minute << " : " << second << endl;
}
// 重载前缀递增运算符 ( ++ )
Time operator++() {
second++;
if (second >= 60) {
minute++;
second = 0;
}
return Time(minute, second);
}
// 重载后缀递增运算符( ++ )
Time operator++(int)
{
Time t(minute, second); // 保存原始值
second++; // 对象加 1
if (second >= 60) {
minute++;
second = 0;
}
return t; // 返回旧的原始值
}
};
int main()
{
Time t1(12, 58), t2(0,45);
t1.display();
(++t1).display();
(++t1).display();
t2.display();
(t2++).display();
(t2++).display();
return 0;
}
#include <iostream>
using namespace std;
class Rect
{
private:
double width;
double height;
public:
Rect() {
width = 0;
height = 0;
}
Rect(double a, double b) {
width = a;
height = b;
}
void display() {
cout << " width: " << width;
cout << " height: " << height;
}
void operator= (const Rect &r)
{
width = r.width + 1;
height = r.height + 1;
}
void operator()() {
cout << "Area of myself is:" << width * height << endl;
};
int main()
{
Rect r1(3.0, 4.0), r2;
r2 = r1;
cout << "r1: ";
r1.display();
cout << endl;
cout << "r2: ";
r2.display();
cout << endl;
Rect r1(3, 4), r2(6, 8);
cout << "r1: ";
r1();
cout << "r2: ";
r2();
return 0;
}
#include <iostream>
using namespace std;
const int SIZE = 10;
class Fibo
{
private:
// 偷懒,防止把 SIZE 设置的过小
int arr[SIZE+3];
public:
Fibo() {
arr[0] = 0;
arr[1] = 1;
for(int i=2; i<SIZE; i++) {
arr[i] = arr[i-2] + arr[i-1];
}
}
int& operator[](unsigned int i) {
if (i >= SIZE) {
std::cout << "(索引超过最大值) ";
return arr[0]; // 返回第一个元素
}
return arr[i];
}
};
int main()
{
Fibo fb;
for (int i=0; i<SIZE+1; i++) {
cout << fb[i] << " ";
}
cout << endl;
return 0;
}
#include <iostream>
#include <vector>
using namespace std;
// 假设一个实际的类
class Obj
{
static int i, j;
public:
void f() const { cout << i++ << endl; }
void g() const { cout << j++ << endl; }
};
// 静态成员定义
int Obj::i = 10;
int Obj::j = 12;
// 为上面的类实现一个容器
class ObjContainer
{
std::vector<Obj*> a;
public:
void add(Obj* obj) {
a.push_back(obj); // 调用向量的标准方法
}
friend class SmartPointer;
};
// 实现智能指针,用于访问类 Obj 的成员
class SmartPointer {
ObjContainer oc;
int index;
public:
SmartPointer(ObjContainer& objc)
{
oc = objc;
index = 0;
}
// 前缀版本
// 返回值表示列表结束
bool operator++()
{
if(index >= oc.a.size())
return false;
if(oc.a[++index] == 0)
return false;
return true;
}
// 后缀版本
bool operator++(int)
{
return operator++();
}
// 重载运算符 ->
Obj* operator->() const
{
if(!oc.a[index]) {
std::cout << "Zero value";
return (Obj*)0;
}
return oc.a[index];
}
};
int main()
{
const int sz = 6;
Obj o[sz];
ObjContainer oc;
for(int i=0; i<sz; i++) {
oc.add(&o[i]);
}
SmartPointer sp(oc);
do {
sp->f();
sp->g();
} while(sp++);
return 0;
}
#include <iostream>
using namespace std;
class Rect
{
private:
int width;
int height;
public:
Rect() {
width = 0;
height = 0;
}
Rect( int a, int b ) {
width = a;
height = b;
}
int area () {
return width * height;
}
// 当 width 或者 height 有一个小于 0 则返回 true
bool operator!() {
if ( width <= 0 || height <= 0 ) {
return true;
}
return false;
}
};
int main()
{
Rect r1(3, 4), r2(-3, 4);
if (!r1) cout << "r1 is not a rectangle" << endl;
else cout << "r1 is a rectangle" << endl;
if (!r2) cout << "r2 is not a rectangle" << endl;
else cout << "r2 is a rectangle" << endl;
return 0;
}
//下标操作符重载
#include <iostream>
using namespace std;
class String
{
public:
String(char const* chars = "");
char& operator[](std::size_t index) throw(String);
char operator[](std::size_t index) const throw(String);
void print();
private:
char* ptrChars;
static String erroMessage;
};
String String::erroMessage("Subscript out of range");
char& String::operator[](std::size_t index) throw(String)//下标操作符重载
{
if (index >= std::strlen(ptrChars))
throw erroMessage;
return ptrChars[index];
}
char String::operator[](std::size_t index) const throw(String) //不可变的字符串,常量
{
if (index >= std::strlen(ptrChars))
throw erroMessage;
return ptrChars[index];
}
String::String(char const* chars)
{
chars = chars ? chars : ""; //判断字符串是否为空,将空字符串分配为空串
ptrChars = new char[std::strlen(chars) + 1];//动态分配字符串大小
std::strcpy(ptrChars, chars);//复制字符串到成员指针变量
}
void String::print()
{
cout << ptrChars << endl;
}
int main()
{
String s("hello");
s.print();
cout << s[0] << endl;
s[0] = 'A';
s.print();
String const s2("dog"); //将会调用const操作符重载
cout << s2[2] << endl;
return 0;
}
//成员访问操作符重载
#include <iostream>
using namespace std;
class String
{
public:
String(char const *chars = "");
String(String const& str);
~String();
void display() const; private:
char* ptrChars;
};
String::String(char const* chars)
{
chars = chars ? chars : "";
ptrChars = new char[std::strlen(chars) + 1];
std::strcpy(ptrChars, chars);
}
String::String(String const& str)
{
ptrChars = new char[std::strlen(str.ptrChars) + 1];
std::strcpy(ptrChars, str.ptrChars);
}
String::~String()
{
delete[] ptrChars;
}
void String::display() const
{
std::cout << ptrChars << std::endl;
}
class Pointer
{
public:
Pointer();
Pointer(String const& n);
~Pointer();
String& operator*();
String* operator->() const;
private:
String* ptr; //ptr为String类的指针
static String errorMessage;
};
Pointer::Pointer():ptr(0){}
Pointer::Pointer(String const& n)//将字符串n转化为String类
{
ptr = new String(n); //将ptr指向传入的String类
}
Pointer:: ~Pointer()
{
delete ptr;
}
String Pointer::errorMessage("Uninitialized pointer");
String& Pointer::operator*()
{
if (!ptr)
throw errorMessage;
return *ptr;
}
String* Pointer::operator->() const
{
if (!ptr)
throw errorMessage;
return ptr;
}
int main()
{
String s("Hello String");
s.display();
String* ps = &s;
ps->display();
try
{
Pointer p1("C++");
p1->display(); //->操作符重载,p1->相当于一个指向String类的指针,即Pointer里的ptr
String s = *p1; //*p1相当于一个指向String类的指针,即Pointer里的ptr
s.display();
Pointer p2;
p2->display();
}
catch (String const& error)
{
error.display();
}
return 0;
}
//自增自减操作符重载
String& operator(); //前加加,返回引用
String const operator++(int);//后加加,有参数,返回拷贝
String& String::operator++() //前加加
{
for(std::size_t i=0;i<std::strlen(ptrChars);++i) //对字符串中的每一位进行加一操作
{
++ptrChars[i];
}
return *this;
}
String const String::operator++(int)
{
String copy(*this); //先复制返回当前对象的copy
++(*this); //然后对当前对象加加
return copy;
}
//类型转换操作符重载
#include <iostream>
using namespace std;
//转换操作符
class Dog{
public:
Dog(string n,int a,double w):name(n),age(a),weight(w){}
operator int() const //转换操作符函数,不改变操作的对象,不能指定返回类型,不能有形参
{
return age; //显式返回int型的数据
}
private:
int age;
double weight;
string name;
};
int main()
{
int a,b;
a=10;
b=a;
cout<<b<<endl;
Dog d("Bill",6,15.0);
b=d;
cout<<b<<endl;
}
this指针详解 每个类都有this指针,this指针指向this指针指向的是类的对象本身
类定义好后我们就可以通过类来创建多个实例对象,每个对象都有各自的实例属性(实例变量),但是非内联成员函数(non-inline member function)只会诞生一份函数实例(换句话说每个对象需要共用同一个方法来操作实例属性)。在有多个实例对象访问同一个函数时,函数如何知道该操作哪个对象的属性?此时就需要 this 指针
const成员函数
const成员函数就是在类的成员函数的形参列表后面加上const关键字,作用是将this指针指向的对象置为const,所以,调用成员函数时,不能在const成员函数中修改类的对象。因为是修饰this,而普通函数和static成员函数没有this,所以不能在static成员函数和普通函数后面加const修饰
#include <iostream>
using std::cout;
class Hello {
public:
int a;
int b;
int c;
Hello(int _a, int _b, int _c) {
a = _a;
b = _b;
c = _c;
}
Hello & getObject() {//在一个对象的某个成员函数中需要返回对象本身时可以 return *this; 来将对象返回
return *this;
}
//为了保证方法 setValue 能够修改对象对应的属性,编译器隐式的向方法内传递 this 指针,this 指针指向的是当前对象的地址(注意 this 是指针类型,在 C/C++ 中要用 -> 运算符来访问成员)
void setValue(int _a, int _b, int _c) {
this->a = _a;
this->b = _b;
this->c = _c;
}
int getSum() {
return (a + b + c);
}
};
int main()
{
Hello object1(10, 20, 30);
Hello object2(20, 20, 10);
Hello object3(10, 10, 10);
cout << "Hello";
return 0;
}
class A
{
public:
A() {}
~A() {}
A constfunc() const;
private:
int a;
};
A A::constfunc() const
{
cout<<__func__<<endl;
//a=10;
return *this;
}
int main(int argc, char const *argv[])
{
A t1;
A t2= t1.constfunc();
return 0;
}
class Box {
public:
double width;
double height;
// 构造函数
Box(double w, double h) : width(w), height(h) {}
// 成员函数,用于计算盒子的体积
double getVolume() {
return width * height;
}
// 成员函数,使用 this 指针返回指向当前对象的指针
Box* getBiggerBox(double extraWidth, double extraHeight) {
this->width += extraWidth; // 使用 this 指针访问成员变量
this->height += extraHeight;
return this; // 返回当前对象的指针
}
};
int main() {
Box myBox(3.0, 4.0);
std::cout << "Original Volume: " << myBox.getVolume() << std::endl;
// 使用 getBiggerBox 成员函数改变 myBox 的尺寸,并返回 myBox 的引用
Box* biggerBox = myBox.getBiggerBox(1.0, 1.0);
// 输出改变后的体积
std::cout << "Bigger Volume: " << biggerBox->getVolume() << std::endl;
return 0;
}
智能指针
常规指针的缺点:
当一个常规指针离开它的作用域时,只有该指针所占用的空间会被释放,而它指向的内存空间能否被释放就不一定了,在一些特殊情况下(人为、业务逻辑的特殊)free或delete没有执行,就会形成内存泄漏。
1
智能指针的优点:
智能指是一个封装了常规指针的类类型对象,当它离开作用域时,它的析构函数会负责释放常规指针所指向的动态内存(以正确方式创建的智能指针,它的析构函数才会正确执行)。
1
智能指针和常规指针的相同点:
都支持*和->运算
1
不同点:
任何时候,一个对象只能使用一个智能指针来指向,而常规指针可以指向多次。
智能指针的赋值操作需要经过拷贝构造和赋值构造特殊处理(深拷贝)
class Int
{
int val;
public:
Int(int val=0):val(val){}
Int& operator=(const int val)
{
this->val = val;
return *this;
}
~Int(void)
{
cout << "Int的析构函数" << endl;
}
friend ostream& operator<<(ostream& os,Int& n);
};
ostream& operator<<(ostream& os,Int& n)
{
return os << n.val;
}
class IntPointer
{
Int* ptr;
public:
IntPointer(Int* ptr):ptr(ptr){}
Int& operator*(void)
{
return *ptr;
}
~IntPointer(void)
{
delete ptr;
}
};
int main()
{
Int* num = new Int(100);
IntPointer p = num;
cout << *p << endl;
*p = 20;
cout << *p << endl;
}
template<class T>
struct Delete
{
void operator()(T* ptr) { delete ptr;}
};
template<class T>
struct DeleteArray
{
void operator()(T* ptr) { delete[] ptr; }
};
template<class T>
struct Free
{
void operator()(T* ptr) { free(ptr); }
};
template<class T, class Del = Delete<T>>
class SmartPtr
{
public:
SmartPtr(T* ptr = nullptr) //构造函数
: _ptr(ptr)
{ }
~SmartPtr()
{
Del del;
del(_ptr);
}
private:
T* _ptr; //管理的资源
};
共享的智能指针shared_ptr
shared_ptr的初始化:
共享智能指针是指多个智能指针可以同时管理同一块有效的内存,共享智能指针shared_ptr 是一个模板类,如果要进行初始化有三种方式:通过构造函数、std::make_shared辅助函数以及reset方法。共享智能指针对象初始化完毕之后就指向了要管理的那块堆内存,如果想要查看当前有多少个智能指针同时管理着这块内存可以使用共享智能指针提供的一个成员函数use_count
获取原始指针:
对应基础数据类型来说,通过操作智能指针和操作智能指针管理的内存效果是一样的,可以直接完成数据的读写。但是如果共享智能指针管理的是一个对象,那么就需要取出原始内存的地址再操作,可以调用共享智能指针类提供的get()方法得到原始地址
指定删除器:
当智能指针管理的内存对应的引用计数变为0的时候,这块内存就会被智能指针析构掉了。另外,我们在初始化智能指针的时候也可以自己指定删除动作,这个删除操作对应的函数被称之为删除器,这个删除器函数本质是一个回调函数,我们只需要进行实现,其调用是由智能指针完成的
#include <iostream>
using namespace std;
#include <string>
#include <memory>
class Test
{
public:
Test() : m_num(0)
{
cout << "construct Test..." << endl;
}
Test(int x) : m_num(0)
{
cout << "construct Test, x = " << x << endl;
}
Test(string str) : m_num(0)
{
cout << "construct Test, str = " << str << endl;
}
~Test()
{
cout << "destruct Test..." << endl;
}
void setValue(int v)
{
this->m_num = v;
}
void print()
{
cout << "m_num: " << this->m_num << endl;
}
private:
int m_num;
};
int main()
{
/*-------------------------- 一,初始化智能指针shared_ptr ------------------------------*/
//1.通过构造函数初始化
shared_ptr<int> ptr1(new int(3));
cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
//2.通过移动和拷贝构造函数初始化
shared_ptr<int> ptr2 = move(ptr1);
cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
shared_ptr<int> ptr3 = ptr2;
cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
//3.通过 std::make_shared初始化
shared_ptr<int> ptr4 = make_shared<int>(8);
shared_ptr<Test> ptr5 = make_shared<Test>(7);
shared_ptr<Test> ptr6 = make_shared<Test>("GOOD LUCKLY!");
//4.通过reset初始化
ptr6.reset(); //重置ptr6, ptr6的引用基数为0
cout << "ptr6管理的内存引用计数: " << ptr6.use_count() << endl;
ptr5.reset(new Test("hello"));
cout << "ptr5管理的内存引用计数: " << ptr5.use_count() << endl;
cout << endl;
cout << endl;
/*-------------------------- 二,共享智能指针shared_ptr的使用 ------------------------------*/
//1.方法一
Test* t = ptr5.get();
t->setValue(1000);
t->print();
//2.方法二
ptr5->setValue(7777);
ptr5->print();
printf("\n\n");
/*------------------------------------ 三,指定删除器 -----------------------------------*/
//1.简单举例
shared_ptr<Test> ppp(new Test(100), [](Test* t) {
//释放内存
cout << "Test对象的内存被释放了......." << endl;
delete t;
});
printf("----------------------------------------------------------------------\n");
2.如果是数组类型的地址,就需要自己写指定删除器,否则内存无法全部释放
//shared_ptr<Test> p1(new Test[5], [](Test* t) {
// delete[]t;
// });
//3.也可以使用c++给我们提供的 默认删除器函数(函数模板)
shared_ptr<Test> p2(new Test[3], default_delete<Test[]>());
//4.c++11以后可以这样写 也可以自动释放内存
shared_ptr<Test[]> p3(new Test[3]);
return 0;
}
#include <iostream>
#include <memory>
#include <string>
using namespace std;
//有了这个函数模板,我们就不用自己去释放数组类型的地址了
template <typename T>
shared_ptr<T> make_share_array(size_t size)
{
//返回匿名对象
return shared_ptr<T>(new T[size], default_delete<T[]>());
}
int main()
{
shared_ptr<int> ptr1 = make_share_array<int>(10);
cout << ptr1.use_count() << endl;
shared_ptr<string> ptr2 = make_share_array<string>(7);
cout << ptr2.use_count() << endl;
return 0;
}
独占的智能指针unique_ptr
初始化:
std::unique_ptr是一个独占型的智能指针,它不允许其他的智能指针共享其内部的指针,可以通过它的构造函数初始化一个独占智能指针对象,但是不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr。
删除器:
unique_ptr指定删除器和shared_ptr指定删除器是有区别的,unique_ptr指定删除器的时候需要确定删除器的类型,所以不能像shared_ptr那样直接指定删除器
#include <iostream>
using namespace std;
#include <string>
#include <memory>
#include <functional>
class Test
{
public:
Test() : m_num(0)
{
cout << "construct Test..." << endl;
}
Test(int x) : m_num(1)
{
cout << "construct Test, x = " << x << endl;
}
Test(string str) : m_num(2)
{
cout << "construct Test, str = " << str << endl;
}
~Test()
{
cout << "destruct Test..." << endl;
}
void setValue(int v)
{
this->m_num = v;
}
void print()
{
cout << "m_num: " << this->m_num << endl;
}
private:
int m_num;
};
int main()
{
/*-------------------------- 一,初始化智能指针unique_ptr ------------------------------*/
//1.通过构造函数初始化
unique_ptr<int> ptr1(new int(3));
//2.通过移动函数初始化
unique_ptr<int> ptr2 = move(ptr1);
//.通过reset初始化
ptr2.reset(new int(7));
/*-------------------------- 二,unique_ptr的使用 ------------------------------*/
//1.方法一
unique_ptr<Test> ptr3(new Test(666));
Test* pt = ptr3.get();
pt->setValue(6);
pt->print();
//2.方法二
ptr3->setValue(777);
ptr3->print();
/*------------------------------------ 三,指定删除器 -----------------------------------*/
//1.函数指针类型
//using ptrFunc = void(*)(Test*);
//unique_ptr<Test, ptrFunc> ptr4(new Test("hello"), [](Test* t) {
// cout << "-----------------------" << endl;
// delete t;
// });
//2.仿函数类型(利用可调用对象包装器)
unique_ptr<Test, function<void(Test*)>> ptr4(new Test("hello"), [](Test* t) {
cout << "-----------------------" << endl;
delete t;
});
/*---------- 四,独占(共享)的智能指针可以管理数组类型的地址,能够自动释放 ---------*/
unique_ptr<Test[]> ptr5(new Test[3]);
//在c++11中shared_ptr不支持下面的写法,c++11以后才支持的
shared_ptr<Test[]> ptr6(new Test[3]);
return 0;
}
弱引用的智能指针weak_ptr
弱引用智能指针std::weak_ptr可以看做是shared_ptr的助手,它不管理shared_ptr内部的指针。std::weak_ptr没有重载操作符*和->,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数,它的主要作用就是作为一个旁观者监视shared_ptr中管理的资源是否存在
#include <iostream>
#include <memory>
using namespace std;
int main()
{
shared_ptr<int> sp(new int);
weak_ptr<int> wp1;
weak_ptr<int> wp2(wp1);
weak_ptr<int> wp3(sp);
weak_ptr<int> wp4;
wp4 = sp;
weak_ptr<int> wp5;
wp5 = wp3;
return 0;
}
weak_ptr wp1;构造了一个空weak_ptr对象
weak_ptr wp2(wp1);通过一个空weak_ptr对象构造了另一个空weak_ptr对象
weak_ptr wp3(sp);通过一个shared_ptr对象构造了一个可用的weak_ptr实例对象
wp4 = sp;通过一个shared_ptr对象构造了一个可用的weak_ptr实例对象(这是一个隐式类型转换)
wp5 = wp3;通过一个weak_ptr对象构造了一个可用的weak_ptr实例对象
通过调用std::weak_ptr类提供的use_count()方法可以获得当前所观测资源的引用计数
通过调用std::weak_ptr类提供的expired()方法来判断观测的资源是否已经被释放
通过调用std::weak_ptr类提供的lock()方法来获取管理所监测资源的shared_ptr对象
通过调用std::weak_ptr类提供的reset()方法来清空对象,使其不监测任何资源
利用weak_ptr可以解决shared_ptr的一些问题
返回管理this的shared_ptr
解决循环引用问题
new/delete/new[]/delete[]运算符重载
1、C++中缺省的堆内存管理器速度较慢,重载new和delete底层使用malloc/free可以提高运行速度。
2、new在失败时会发生异常,而每次使用new时为了安全都应该进行异常捕获,而重载new操作符只需要在操作符函数中进行一次错误处理即可。
3、一些占字节数比较小的类,频繁使用new,可能会产生大量的内存碎片,而重载new操作符后,可以适当的扩大申请的字节数,减少内存碎片产生的机率。
4、重载new/delete 可以记录堆内存使用的信息
5、重载delete可以检测到释放内存失败时的信息,检测到内存泄漏
vector<unique_ptr<string>> vec;
unique_ptr<string> p3(new string("I'm P3"));
unique_ptr<string> p4(new string("I'm P4"));
vec.push_back(std::move(p3));
vec.push_back(std::move(p4));
cout << "vec.at(0):" << *vec.at(0) << endl;
cout << "vec[1]:" << *vec[1] << endl;
vec[0] = vec[1]; /* 不允许直接赋值 */
vec[0] = std::move(vec[1]); // 需要使用move修饰,使得程序员知道后果
cout << "vec.at(0):" << *vec.at(0) << endl;
cout << "vec[1]:" << *vec[1] << endl;
class Test
{
void* ptr;
public:
Test(const int val){}
Test(void)
{
cout << "构造函数" << endl;
}
~Test(void)
{
cout << "析构函数" << endl;
}
static void* operator new(size_t size)
{
printf("创建堆内存%d字节\n",size);
malloc(size);
}
static void operator delete(void* ptr)
{
cout << "释放内存" << endl;
free(ptr);
}
};
int main()
{
Test* p1 = new Test;
p1 = NULL;
delete p1;
}
引用操作符重载
是一个一元操作符,作用于指针,获取指针所指单元的内容。当某个类中对操作符重载时,是将该类对象当做一个指针看待,用*操作符提取指针所指向的内容
#include <iostream>
using namespace std;
template<typename T> class DataContainer {
T *p;
public:
DataContainer(T* pp) {
p=pp;
}
~DataContainer() {
delete p;
}
template<typename T> friend T operator*(const DataContainer<T>&);
};
template<typename T> T operator*(const DataContainer<T>& d) {
return *(d.p);
};
int main() {
DataContainer<int> intData(new int(5));
DataContainer<double> doubleData(new double(7.8));
cout << *intData << endl;
cout << *doubleData << endl;
return 0;
}
不能重载的操作符
域限定符 ::
直接成员访问操作符 .
三目运算符 ?:
字节长度操作符 sizeof
类型信息操作符 typeid
强制类型转换
static_cast、const_cast、reinterpret_cast和dynamic_cast
static_cast用于数据类型的强制转换,强制将一种数据类型转换为另一种数据类型。例如将整型数据转换为浮点型数据,可以实现C++中内置基本数据类型之间的相互转换。
int a = 10;
int b = 3;
double result = static_cast<double>(a) / static_cast<double>(b);
const_cast则正是用于强制去掉这种不能被修改的常数特性
onst_cast则正是用于强制去掉这种不能被修改的常数特性,但需要特别注意的是const_cast不是用于去除变量的常量性,而是去除指向常数对象的指针或引用的常量性,其去除常量性的对象必须为指针或引用
const int a = 10;
const int * p = &a;
*p = 20; //compile error
int b = const_cast<int>(a); //compile error
#include<iostream>
using namespace std;
int main()
{
const int a = 10;
const int * p = &a;
int *q;
//普通的指针*q p指针通过const_cast去掉其常量性
q = const_cast<int *>(p);
*q = 20; //fine
cout <<a<<" "<<*p<<" "<<*q<<endl;
cout <<&a<<" "<<p<<" "<<q<<endl;
return 0;
}
reinterpret_cast
改变指针或引用的类型、将指针或引用转换为一个足够长度的整形、将整型转换为指针或引用类型
reinterpret_cast<type_id> (expression):type-id必须是一个指针、引用、算术类型、函数指针或者成员指针。
它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值
int *a = new int;
double *d = reinterpret_cast<double *>(a);
dynamic_cast:
(1)其他三种都是编译时完成的,dynamic_cast是运行时处理的,运行时要进行类型检查。
(2)不能用于内置的基本数据类型的强制转换。
(3)dynamic_cast转换如果成功的话返回的是指向类的指针或引用,转换失败的话则会返回NULL。
(4)使用dynamic_cast进行转换的,基类中一定要有虚函数,否则编译不通过
#include<iostream>
using namespace std;
class base
{
public :
void m(){cout<<"m"<<endl;}
};
class derived : public base
{
public:
void f(){cout<<"f"<<endl;}
};
int main()
{
derived * p;
p = new base;
p = static_cast<derived *>(new base);
p->m();
p->f();
return 0;
}
#include<iostream>
#include<cstring>
using namespace std;
class A
{
public:
virtual void f()
{
cout<<"hello"<<endl;
};
};
class B:public A
{
public:
void f()
{
cout<<"hello2"<<endl;
}
};
class C
{
void pp()
{
return;
}
};
int fun()
{
return 1;
}
int main()
{
A* a1=new B;//a1是A类型的指针指向一个B类型的对象
A* a2=new A;//a2是A类型的指针指向一个A类型的对象
B* b;
C* c;
b=dynamic_cast<B*>(a1);//结果为not null,向下转换成功,a1之前指向的就是B类型的对象,所以可以转换成B类型的指针。
if(b==NULL)
{
cout<<"null"<<endl;
}
else
{
cout<<"not null"<<endl;
}
b=dynamic_cast<B*>(a2);//结果为null,向下转换失败
if(b==NULL)
{
cout<<"null"<<endl;
}
else
{
cout<<"not null"<<endl;
}
c=dynamic_cast<C*>(a);//结果为null,向下转换失败
if(c==NULL)
{
cout<<"null"<<endl;
}
else
{
cout<<"not null"<<endl;
}
delete(a);
return 0;
}
指向类成员函数的函数指针
函数指针赋值要使用 &
使用 .* (实例对象)或者 ->*(实例对象指针)调用类成员函数指针所指向的函数
类成员函数指针指向类中的非静态成员函数
对于 nonstatic member function (非静态成员函数)取地址,获得该函数在内存中的实际地址
对于 virtual function(虚函数), 其地址在编译时期是未知的,所以对于 virtual member function(虚成员函数)取其地址,所能获得的只是一个索引值
//指向类成员函数的函数指针
#include <iostream>
#include <cstdio>
using namespace std;
class A
{
public:
A(int aa = 0):a(aa){}
~A(){}
void setA(int aa = 1)
{
a = aa;
}
virtual void print()
{
cout << "A: " << a << endl;
}
virtual void printa()
{
cout << "A1: " << a << endl;
}
private:
int a;
};
class B:public A
{
public:
B():A(), b(0){}
B(int aa, int bb):A(aa), b(bb){}
~B(){}
virtual void print()
{
A::print();
cout << "B: " << b << endl;
}
virtual void printa()
{
A::printa();
cout << "B: " << b << endl;
}
private:
int b;
};
int main(void)
{
A a;
B b;
//指向类成员函数的函数指针定义
void (A::*ptr)(int) = &A::setA;
A* pa = &a;
//对于非虚函数,返回其在内存的真实地址
printf("A::set(): %p\n", &A::setA);
//对于虚函数, 返回其在虚函数表的偏移位置
printf("B::print(): %p\n", &A::print);
printf("B::print(): %p\n", &A::printa);
std::thread t = std::thread(&A::setA, a);
a.print();
a.setA(10);
a.print();
a.setA(100);
a.print();
//对于指向类成员函数的函数指针,引用时必须传入一个类对象的this指针,所以必须由类实体调用
(pa->*ptr)(1000);
a.print();
(a.*ptr)(10000);
a.print();
return 0;
}
//类成员函数指针指向类中的静态成员函数
#include <iostream>
using namespace std;
class A{
public:
//p1是一个指向非static成员函数的函数指针
void (A::*p1)(void);
//p2是一个指向static成员函数的函数指针
void (*p2)(void);
A(){
/*对
**指向非static成员函数的指针
**和
**指向static成员函数的指针
**的变量的赋值方式是一样的,都是&ClassName::memberVariable形式
**区别在于:
**对p1只能用非static成员函数赋值
**对p2只能用static成员函数赋值
**
**再有,赋值时如果直接&memberVariable,则在VS中报"编译器错误 C2276"
**参见:http://msdn.microsoft.com/zh-cn/library/850cstw1.aspx
*/
p1 =&A::funa; //函数指针赋值一定要使用 &
p2 =&A::funb;
//p1 =&A::funb;//error
//p2 =&A::funa;//error
//p1=&funa;//error,编译器错误 C2276
//p2=&funb;//error,编译器错误 C2276
}
void funa(void){
puts("A");
}
static void funb(void){
puts("B");
}
};
int main()
{
A a;
//p是指向A中非static成员函数的函数指针
void (A::*p)(void);
(a.*a.p1)(); //打印 A
//使用.*(实例对象)或者->*(实例对象指针)调用类成员函数指针所指向的函数
p = a.p1;
(a.*p)();//打印 A
A *b = &a;
(b->*p)(); //打印 A
/*尽管a.p2本身是个非static变量,但是a.p2是指向static函数的函数指针,
**所以下面这就话是错的!
*/
// p = a.p2;//error
void (*pp)(void);
pp = &A::funb;
pp(); //打印 B
return 0;
}
函数指针变量 = 函数名;
//例子
int Add(int a, int b)
{
return a+b;
}
int (*p)(int,int) = Add;
//函数指针数组
int Add(int a, int b)
{
return a+b;
}
int Sub(int a, int b)
{
return a-b;
}
int (*p[2])(int , int) = {Add, Sub}; //定义
int x=2, y=3;
int result = p[0](x, y); //使用
class Point{
public:
float x() { printf("aaaa");};
float y() {};
protected:
float _x{}, _y{};
};
Point orgin;
Point *ptr = new Point;
//定义一个指向成员函数的指针
float (Point::* pmf)(); // 声明
pmf = &Point::x; // 赋值
float (Point::* coord)() = &Point::x; //定义
(orgin.*pmf)();
(ptr->*coord)();
const类对象,const成员函数
类的成员函数后面加 const,表明这个函数不会对这个类对象的数据成员(准确地说是非静态数据成员)作任何改变
const 修饰的成员函数:const 放在函数参数表的后面,而不是在函数前面或者参数表内 只能读取数据成员,不能改变数据成员
常量(即 const)对象可以调用 const 成员函数,而不能调用非const修饰的函数
const数据成员:跟const常量一样,只是一个在类里(而且是在构造函数里),一个在类外而已,都必须初始化
const成员函数读取数据成员的值, 但不能修改它们。若要修改时,数据成员前必须加mutable
可以调用任意成员函数
const对象:仅能调用const成员函数,但是构造函数和析构函数是唯一不是const成员函数却可以被const对象调用的成员函数
const成员函数的写法有两种
class Test
{
public:
// 常数据成员只能通过初始化列表,获得初值
// a 为常成员数据,不能把 a=i 写到构造函数体内,必须通过初始化列表获取初值
// 普通成员也可在初始化列表中赋值
Test(int r, int i) :a(i), ri(r)
{
}
private:
int ri;
const int a;
static const float pi; //静态常数据成员
};
// //静态常数据成员,类外初始化
const float Test::pi = 3.14;
1、void fun(int a,int b) const{}
2、void const fun(int a,int b){}
这两种写法的本质是:void fun (const 类 *this, int a,int b);
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
class A
{
public:
void f()
{
cout<<"non const"<<endl;
}
void f() const
{
cout<<" const"<<endl;
}
};
int main(int argc, char **argv)
{
A a;
a.f();
const A &b=a;
b.f();
const A *c=&a;
c->f();
A *const d=&a;
d->f();
A *const e=d;
e->f();
const A *f=c;
f->f();
return 0;
}
//const成员函数可以被对应的具有相同形参列表的非const成员函数重载
class Screen {
public:
char get(int x,int y);
char get(int x,int y) const;
};
int main()
{
const Screen cs;
Screen cc2;
char ch = cs.get(0, 0); // 调用const成员函数
ch = cs2.get(0, 0); // 调用非const成员函数
}
类中的const成员变量都要放在初始化列表之中进行
> const数据成员
> 引用数据成员
> 对象数据成员(内置类)
const成员函数
> void print() const => const 类名 * const this
> 在其内部是不能修改数据成员
> 只能调用const成员函数,不能调用非const成员函数
> const对象只能调用const成员函数,必须要提供一个const版本的成员函数
const成员函数和成员变量这一块的逻辑容易混乱
继承、派生
继承(inheritance)机制:是类型层次结构设计中实现代码的复用重要手段
class 子类: 继承权限 基类
{
};
继承:
- 1.一个类自动拥有了来自另外一个类的属性和方法
- 2.一个类是一个类
- 层次关系-继承、派生
私有的能被继承,不能被访问
/基类
class A{
public:
int mA;
protected:
int mB;
private:
int mC;
};
//1. 公有(public)继承
class B : public A{
public:
void PrintB(){
cout << mA << endl; //可访问基类public属性
cout << mB << endl; //可访问基类protected属性
//cout << mC << endl; //不可访问基类private属性
}
};
class SubB : public B{
void PrintSubB(){
cout << mA << endl; //可访问基类public属性
cout << mB << endl; //可访问基类protected属性
//cout << mC << endl; //不可访问基类private属性
}
};
void test01(){
B b;
cout << b.mA << endl; //可访问基类public属性
//cout << b.mB << endl; //不可访问基类protected属性
//cout << b.mC << endl; //不可访问基类private属性
}
//2. 私有(private)继承
class C : private A{
public:
void PrintC(){
cout << mA << endl; //可访问基类public属性
cout << mB << endl; //可访问基类protected属性
//cout << mC << endl; //不可访问基类private属性
}
};
class SubC : public C{
void PrintSubC(){
//cout << mA << endl; //不可访问基类public属性
//cout << mB << endl; //不可访问基类protected属性
//cout << mC << endl; //不可访问基类private属性
}
};
void test02(){
C c;
//cout << c.mA << endl; //不可访问基类public属性
//cout << c.mB << endl; //不可访问基类protected属性
//cout << c.mC << endl; //不可访问基类private属性
}
//3. 保护(protected)继承
class D : protected A{
public:
void PrintD(){
cout << mA << endl; //可访问基类public属性
cout << mB << endl; //可访问基类protected属性
//cout << mC << endl; //不可访问基类private属性
}
};
class SubD : public D{
void PrintD(){
cout << mA << endl; //可访问基类public属性
cout << mB << endl; //可访问基类protected属性
//cout << mC << endl; //不可访问基类private属性
}
};
void test03(){
D d;
//cout << d.mA << endl; //不可访问基类public属性
//cout << d.mB << endl; //不可访问基类protected属性
//cout << d.mC << endl; //不可访问基类private属性
}
class Person
{
public:
Person() :m_sex('f'), m_age(20)
{
m_name = new char[1];
*m_name = '\0';
cout << "Person()" << endl;
}
Person(const char* name,char sex,int age):m_sex(sex), m_age(age)
{
m_name = new char[strlen(name) + 1];
strcpy_s(m_name, strlen(name) + 1, name);
}
Person(Person& p) :m_sex(p.m_sex), m_age(p.m_age)
{
m_name = new char[strlen(p.m_name) + 1];
strcpy_s(m_name, strlen(p.m_name) + 1, p.m_name);
}
Person& operator=(Person& p)
{
if (this == &p)
return *this;
delete[]m_name;
m_name = new char[strlen(p.m_name) + 1];
strcpy_s(m_name, strlen(p.m_name) + 1, p.m_name);
m_sex = p.m_sex;
m_age = p.m_age;
return *this;
}
void Print()
{
cout << m_name << " " << m_sex << " " << m_age << endl;
}
~Person()
{
if (m_name != NULL)
{
delete[]m_name;
m_name = NULL;
}
}
private:
char* m_name;
char m_sex;
int m_age;
};
class Student :public Person
{
public:
Student() :m_num(0), m_score(0)
{
cout << "Student()" << endl;
}
Student(int num,const char*name,char sex,int age,int score):m_num(num),Person(name,sex,age),m_score(score)
{
}
void Print()
{
cout << m_num << " ";
Person::Print();
cout << m_score << endl;
}
Student(Student& s) :Person(s), m_num(s.m_num), m_score(s.m_score)
{
}
Student& operator=(Student& s)
{
if (this == &s)
return *this;
Person::operator=(s);
m_num = s.m_num;
m_score = s.m_score;
return *this;
}
private:
int m_num;
int m_score;
};
void main()
{
Student s;
Student s1(1001, "zhangsan", 'f', 20, 78);
s.Print();
s1.Print();
Student s2(s1);
s2.Print();
s = s1;
s.Print();
}
Person() 先调用基类
Student() 构造s对象
0 f 20 没有给s对象赋值,为构造函数的默认值
0
1001 zhangsan f 20 打印s1对象
78
1001 zhangsan f 20 用s1对象拷贝构造s2对象,并将s2对象打印
78
1001 zhangsan f 20 将s1对象的值赋给s对象,并将s对象打印
78
class A{
public:
A(){
cout << "A类构造函数!" << endl;
}
~A(){
cout << "A类析构函数!" << endl;
}
};
class B : public A{
public:
B(){
cout << "B类构造函数!" << endl;
}
~B(){
cout << "B类析构函数!" << endl;
}
};
class C : public B{
public:
C(){
cout << "C类构造函数!" << endl;
}
~C(){
cout << "C类析构函数!" << endl;
}
};
void test(){
C c;
}
子类对象在创建时会首先调用父类的构造函数
父类构造函数执行完毕后,才会调用子类的构造函数
当父类构造函数有参数时,需要在子类初始化列表(参数列表)中显示调用父类构造函数
析构函数调用顺序和构造函数相反
class Base{
public:
//重载函数
void func1(){
cout << "Base::void func1()" << endl;
};
void func1(int param){
cout << "Base::void func1(int param)" << endl;
}
//非重载函数
void myfunc(){
cout << "Base::void myfunc()" << endl;
}
};
class Derived1 : public Base{};
class Derived2 : public Base{
public:
void myfunc(){
//基类myfunc被隐藏,可通过类作用域运算符指定调用基类myfunc函数
//Base::myfunc();
cout << "Derived2::void myfunc()" << endl;
}
};
class Derived3 : public Base{
public:
//改变成员函数的参数列表
void func1(int param1, int param2){
//Base::func1(10); //类的内部可通过类作用域运算符访问基类重载版本的函数
cout << "Derived3::void func1(int param1,int param2)" << endl;
};
};
class Derived4 : public Base{
public:
//改变成员函数的返回值
int func1(int param){
Base::func1(10);
cout << "Derived4::int func1(int param)" << endl;
return 0;
}
};
//和基类非重载函数重名
void test01(){
Derived1 derived1;
derived1.myfunc();
//和基类函数重名
Derived2 derived2;
derived2.myfunc();
}
//和基类重载函数重名
void test02(){
Derived3 derived3;
//derived3.func1(); //基类重载版本的函数fun1被全部隐藏,子类外部不可访问
//derived3.func1(10);
derived3.func1(10,20);
Derived4 derived4;
//derived4.func1(); //基类重载版本的函数fun1被全部隐藏,子类外部不可访问
derived4.func1(10);
}
//结论:任何时候重新定义基类中的任何一个函数,子类中这种函数的任何版本都
//会被隐藏(非覆盖,可通过类作用域运算符调用)
不是所有的函数都能自动从基类继承到派生类中。构造函数和析构函数用来处理对象的创建和析构操作,构造和析构函数只知道对它们的特定层次的对象做什么,也就是说构造函数和析构函数不能被继承,必须为每一个特定的派生类分别创建
另外operator=也不能被继承,因为它完成类似构造函数的行为。也就是说尽管我们知道如何由=右边的对象如何初始化=左边的对象的所有成员,但是这个并不意味着对其派生类依然有效
静态成员函数和非静态成员函数的共同点:
他们都可以被继承到派生类中。
如果重新定义一个静态成员函数,所有在基类中的其他重载函数会被隐藏。
如果我们改变基类中一个函数的特征,所有使用该函数名的基类版本都会被隐藏
class Base{
public:
static int getNum(){ return sNum; }
static int getNum(int param){
return sNum + param;
}
public:
static int sNum;
};
int Base::sNum = 10;
class Derived : public Base{
public:
static int sNum; //基类静态成员属性将被隐藏
#if 0
//重定义一个函数,基类中重载的函数被隐藏
static int getNum(int param1, int param2){
return sNum + param1 + param2;
}
#else
//改变基类函数的某个特征,返回值或者参数个数,将会隐藏基类重载的函数
static void getNum(int param1, int param2){
cout << sNum + param1 + param2 << endl;
}
#endif
};
int Derived::sNum = 20;
多继承
class Base1{
public:
void func1(){ cout << "Base1::func1" << endl; }
};
class Base2{
public:
void func1(){ cout << "Base2::func1" << endl; }
void func2(){ cout << "Base2::func2" << endl; }
};
//派生类继承Base1、Base2
class Derived : public Base1, public Base2{};
int main(){
Derived derived;
//func1是从Base1继承来的还是从Base2继承来的?
//derived.func1();
derived.func2();
//解决歧义:显示指定调用那个基类的func1
derived.Base1::func1();
derived.Base2::func1();
return EXIT_SUCCESS;
}
菱形继承和虚继承
两个派生类继承同一个基类而又有某个类同时继承者两个派生类,这种继承被称为菱形继承,或者钻石型继承。
class BigBase{
public:
BigBase(){ mParam = 0; }
void func(){ cout << "BigBase::func" << endl; }
public:
int mParam;
};
class Base1 : public BigBase{};
class Base2 : public BigBase{};
class Derived : public Base1, public Base2{};
int main(){
Derived derived;
//1. 对“func”的访问不明确
//derived.func();
//cout << derived.mParam << endl;
cout << "derived.Base1::mParam:" << derived.Base1::mParam << endl;
cout << "derived.Base2::mParam:" << derived.Base2::mParam << endl;
//2. 重复继承
cout << "Derived size:" << sizeof(Derived) << endl; //8
return EXIT_SUCCESS;
}
虚基类
class BigBase{
public:
BigBase(){ mParam = 0; }
void func(){ cout << "BigBase::func" << endl; }
public:
int mParam;
};
class Base1 : public BigBase{};
class Base2 : public BigBase{};
class Derived : public Base1, public Base2{};
int main(){
Derived derived;
//1. 对“func”的访问不明确
//derived.func();
//cout << derived.mParam << endl;
cout << "derived.Base1::mParam:" << derived.Base1::mParam << endl;
cout << "derived.Base2::mParam:" << derived.Base2::mParam << endl;
//2. 重复继承 Derived继承自BigBase的函数和数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以
cout << "Derived size:" << sizeof(Derived) << endl; //8
return EXIT_SUCCESS;
}
class BigBase{
public:
BigBase(){ mParam = 0; }
void func(){ cout << "BigBase::func" << endl; }
public:
int mParam;
};
class Base1 : virtual public BigBase{};
class Base2 : virtual public BigBase{};
class Derived : public Base1, public Base2{};
int main(){
Derived derived;
//二义性问题解决
derived.func();
cout << derived.mParam << endl;
//输出结果:12
cout << "Derived size:" << sizeof(Derived) << endl;
return EXIT_SUCCESS;
}
//BigBase 菱形最顶层的类,内存布局图没有发生改变
//Base1和Base2通过虚继承的方式派生自BigBase,这两个对象的布局图中可以看出编译器为我们的对象中增加了一个vbptr (virtual base pointer),vbptr指向了一张表,这张表保存了当前的虚指针相对于虚基类的首地址的偏移量
//Derived派生于Base1和Base2,继承了两个基类的vbptr指针,并调整了vbptr与虚基类的首地址的偏移量
//工程开发中真正意义上的多继承是几乎不被使用,因为多重继承带来的代码复杂性远多于其带来的便利,多重继承对代码维护性上的影响是灾难性的,在设计方法上,任何多继承都可以用单继承代替
虚函数
用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数。
存在虚函数的类都有一个一维的虚函数表叫做虚表。每一个类的对象都有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应的。
多态性是一个接口多种实现,是面向对象的核心。分为编译多态性和运行多态性。
运行多态用虚函数来实现,结合动态绑定。
纯虚函数是虚函数再加上=0。并且该函数只有声明,没有实现。
抽象类是指包括至少一个纯虚函数的类
编译器在编译的时候,发现Base类中有虚函数,此时编译器会为每个包含虚函数的类创建一个虚表(即vtable),该表是一个一维数组(而不是一个链表),在这个数组中存放每个虚函数的地址。由于Base类和Derive类都包含了一个虚函数func(),编译器会为这两个类都建立一个虚表。
那么如何定位虚表呢?编译器另外还为每个带有虚函数的类的对象自动创建一个虚表指针(即vptr),这个指针指向了对象所属类的虚表。在程序运行时,根据对象的类型去初始化vptr,从而让vptr正确的指向所属类的虚表。所以在调用虚函数时,就能够找到正确的函数
// g++ 23_Virtual.cpp
#include <iostream>
using namespace std;
class CAnimal{
public:
CAnimal(){
cout << "Calling CAnimal(): this=" << this << endl;
}
void eat(){
cout << "Animal eat" << endl;
}
virtual void run(){
cout << "Animal run" << endl;
}
private:
};
class CDog : public CAnimal{
public:
CDog(){
cout << "Calling CDog(), this=" << this << endl;
}
void eat(){ // 重写了基类的eat(),基类的会隐藏
cout << "Dog eat" << endl;
}
void run(){
cout << "Dog run" << endl;
}
};
int main ()
{
CAnimal animal;
CDog dog;
CAnimal* pAnimal = &animal; // 基类指针指向基类对象
pAnimal->eat(); // 调用非虚函数,按照指针类型调用,打印 Animal eat
pAnimal->run(); // 调用虚函数,按照对象类型调用,打印 Animal run
cout << endl;
pAnimal = &dog; // 基类指针指向派生类对象
pAnimal->eat(); // 调用非虚函数,按照指针类型调用,Animal eat
pAnimal->run(); // 调用虚函数,按照对象类型调用,打印 Dog run
cout << endl;
return 0;
}
静态绑定、动态绑定
在 C 语言中,函数名绑定非常简单,因为每个函数名都对应一个不同的函数。 在 C++ 中,由于重载函数的出现,函数名绑定变得复杂,编译器必须査看函数参数以及函数名才能确定使用哪个函数。编译器可以在编译期间完成这样的函数名绑定。但是虚函数的出现使编译器无法在编译期间知道调用的是哪个函数,必须在运行时才完成函数名绑定
静态绑定(binding):在编译期间就可以完成的函数名绑定。
动态绑定(binding):在运行期间才可以完成的函数名绑定。
// g++ 23_Dynamic_binding.cpp -std=c++11
#include <iostream>
using namespace std;
class CAnimal{
public:
virtual void run(){
cout << "Animal run" << endl;
}
};
class CDog : public CAnimal{
public:
virtual void run() override{ //override 显式覆盖基类run
cout << "Dog run" << endl;
}
};
int main ()
{
const int OBJ_NUM = 4;
CAnimal *pAnimal[OBJ_NUM]; // 基类指针数组,用于管理对象
// 程序运行后,根据输入类型创建对象
char kind; // 用于获取类型
for(int i=0; i<OBJ_NUM; i++)
{
cout << "请输入要创建的对象:1表示CAnimal, 2表示CDog" << endl;
while(cin >> kind && (kind != '1' && kind != '2'))
cout << "请输入1或2" << endl;
if(kind == '1')
pAnimal[i] = new CAnimal();
else
pAnimal[i] = new CDog();
}
cout << endl;
// 按照实际的对象打印
for(int i=0; i<OBJ_NUM; i++)
{
pAnimal[i]->run();
}
for(int i=0; i<OBJ_NUM; i++)
{
delete pAnimal[i]; // 释放对象
}
return 0;
}
// g++ 23_Virtual_usage2.cpp
#include <iostream>
#include <string.h>
using namespace std;
class CAnimal{
public:
CAnimal(){
cout << "Calling CAnimal(): this=" << this << endl;
}
virtual void eat(int a) const{
cout << "Animal eat" << endl;
}
virtual CAnimal *run(int a) const{
cout << "Animal run" << endl;
return new CAnimal();
}
virtual ~CAnimal(){
cout << "~CAnimal" << endl;
}
};
class CDog : public CAnimal{
public:
CDog(){
cout << "Calling CDog(), this=" << this << endl;
}
virtual void Eat(int x){}; // e 写成 E,函数名不一致,新的虚函数
virtual void eat(short x){}; // 参数列表不一样,新的虚函数
virtual void eat(int x){}; // const 属性不一样,新的虚函数
virtual void eat(int a) const{ // 重写了基类的eat()
cout << "Dog eat bones" << endl;
}
virtual CDog *run(int a) const{ // 重写了基类的 run ,但返回值为 CDog*
cout << "CDog run" << endl;
return new CDog();
}
~CDog(){
cout << "~CDog" << endl;
}
};
int main ()
{
CAnimal* pAnimal = new CAnimal(); // 基类指针指向基类对象
CAnimal* pRun = pAnimal->run(1);
pAnimal->eat(1);
delete pAnimal;
pAnimal = NULL;
cout << endl;
delete pRun;
pRun = NULL;
cout << endl;
pAnimal = new CDog(); // 基类指针指向派生类对象
pRun = pAnimal->run(1);
pAnimal->eat(1);
delete pAnimal;
pAnimal = NULL;
cout << endl;
delete pRun;
pRun = NULL;
cout << endl;
return 0;
}
final 和 override 说明符
final 说明符:如果基类已经把函数定义成 final 了, 则之后任何尝试覆盖该函数的操作都将引发错误
override 说明符:可以告诉编译器,我们写的这个函数是为了重写基类的虚函数,如果 函数名,参数列表,const属性,返回值 这些不一致,就给我报错。在例子中,我们声明的虚函数如果和基类不一致会成为新的虚函数,加上override之后,如果不构成重写就会报错
class CAnimal{
public:
virtual void finlaFun(int a) const final; // 指定为final,不允许被重写
};
class CDog : public CAnimal{
public:
virtual void finlaFun(int a) const{} // 基类指定为final了,重写会报错
};
class CAnimal{
public:
virtual void eat(int x) const; // 虚函数
};
class CDog: public CAnimal{
public:
virtual void Eat(int x)override{}; // e 写成 E,函数名不一致,报错
virtual void eat(short x)override{}; // 参数列表不一样,报错
virtual void eat(int x)override{}; // const 属性不一样,报错
virtual void eat(int a) const override{ // 重写了基类的eat()
cout << "Dog eat bones" << endl;
}
}
纯虚函数
语法:virtual 返回值类型 函数名(参数列表)=0,当类中有了纯虚函数,这个类也称为抽象类。抽象类特点:无法实例化对象,子类必须重写抽象类中的纯虚函数,否则也属于抽象类
class Base
{
public:
virtual void Examp() = 0;//纯虚函数
~Base()
{
cout << "父类的析构函数" << endl;
}
};
class Son:public Base
{
public:
void Examp() //子类必须得重写重写抽象类中的纯虚函数,不然不能实例化对象
{
cout << "重写了父类的纯虚函数" << endl;
}
~Son()
{
cout << "子类的析构函数" << endl;
}
};
int main()
{
Son p1;
p1.Examp();
system("pause");
}
虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
#include <iostream>
using namespace std;
class Animal {
public:
Animal()
{
cout << "Animal构造函数" << endl;
}
// 纯虚函数
virtual void speak() = 0;
virtual ~Animal()
{
cout << "Animal析构函数" << endl;
}
};
class Dog :public Animal {
public:
Dog()
{
cout << "Dog构造函数" << endl;
}
// 子类一定会重写父类的虚函数
void speak()
{
cout << "狗在汪汪" << endl;
}
~Dog()
{
cout << "Dog析构函数" << endl;
}
};
int main()
{//子类没有析构,这会造成内存泄漏的。这时,我们把它设置为虚析构,就可以释放子类的内存空间
Animal *p = new Dog;
p->speak();
delete p;
return 0;
}
构造的顺序:父类-->成员-->子类。
析构的顺序:子类-->成员-->父类。
有时间继续