一、类 对象 继承
1. 类和对象
1.1 C++在C的基础上增加了面向对象编程,类和对象就是 面向对象编程中 的核心概念
1.2 类是C++的一个核心特性(类就是用户自定义的类型),类中包含数据元素(变量)和处理数据元素的方法(函数),类中的数据和函数被称为类的成员
1.3 类提供了对象的蓝图,一般来说对象是根据类创建的。打个比方,类相当于房屋设计图,对象相当于根据设计图设计的房子
1.4 类的基础使用示例:由于成员的属性为public , 因此可以使用 直接成员访问运算符 . 进行访问
#include <iostream>
using namespace std;
class Fruit
{
public:
string name;
int quatity;
float price;
float getTotalPrice();
void setPara(string n,int q,float p);
};
float Fruit::getTotalPrice() //通过 :: 指定范围,类的成员函数可以在类中定义,也可以在类外定义
{
return quatity*price;
}
void Fruit::setPara(string n,int q,float p)
{
name=n; quatity=q; price=p;
}
int main( )
{
Fruit Apple,Banana;
float totalPrice;
Apple.name="Apple"; Apple.quatity=10; Apple.price=2.0; //直接成员访问运算符 .
totalPrice=Apple.quatity * Apple.price;
cout<<Apple.name<<"'s total price is: "<<totalPrice<<endl;
Banana.setPara("Banana",20,1.5);
totalPrice=Banana.getTotalPrice();
cout<<Banana.name<<"'s total price is: "<<totalPrice<<endl;
return 0;
}
2. 数据封装 public/protected/private
2.1 数据封装是面向对象编程的一个重要特点,用于防止函数直接访问类的内部成员。类成员的访问限制 通过在类内部 使用关键字 public, private, protected 进行标记来控制,默认访问修饰符是private
2.2 公有成员 public 可以直接访问,在1.4的代码示例中已经演示
2.3 私有成员 private 在类的外部不可访问、不可查看,若不指定,默认情况下类的所有成员都是private 。实际操作中,在私有区域定义数据,在公有区域定义相关函数,以便在类的外部调用这些函数
#include <iostream>
using namespace std;
class Fruit
{
public:
string name;
int quatity;
public:
float getCostPrice()
{
return CostPrice;
}
void setCostPrice(float cost)
{
CostPrice=cost;
}
private:
float CostPrice;
};
int main( )
{
Fruit Orange;
float totalCostPrice;
Orange.name="Orange"; Orange.quatity=10; //无法通过 Orange.CostPrice进行访问
Orange.setCostPrice(3.12);
cout<<Orange.name<<"'s cost price is "<<Orange.getCostPrice();
cout<<" and the total cost price is "<<Orange.getCostPrice()*Orange.quatity;
return 0;
}
2.4 受保护成员 protected , protected 和 private几乎相同,除了protected 可以被子类/派生类访问,如下所示
#include <iostream>
using namespace std;
class Fruit
{
protected:
float CostPrice;
private:
float MinCost;
};
class HardShellFruit:Fruit //HardShellFruit是继承自Fruit的子类
{
public:
void setHardShellCostPrice(float cost)
{
CostPrice=cost;
//MinCost=cost; 会触发报错,因为MinCost是private
}
float getHardShellCostPrice(void)
{
return CostPrice;
}
};
int main( )
{
HardShellFruit mangosteen;
mangosteen.setHardShellCostPrice(16.1);
cout<<"Cost price of mangosteen is "<<mangosteen.getHardShellCostPrice()<<endl;
return 0;
}
3. 类的函数
3.1 构造函数简介
构造函数是类的一种特殊成员函数,在每次创建对象时执行。构造函数的名称与类的名称完全相同,不会返回任何类型(包括void)。构造函数默认不带参数,当构造函数带参数时,可用于为部分成员变量赋初值
#include <iostream>
using namespace std;
class Fruit
{
public:
string name;
int quatity;
float CostPrice;
public:
Fruit(int qua,float cost) //构造函数
{
cout<<"Object is being created, the default quatitiy is "<<qua;
cout<<" , the default CostPrice is "<<cost<<endl;
quatity=qua;
CostPrice=cost;
}
float getCostPrice()
{
return CostPrice;
}
};
int main( )
{
Fruit strawberry(100,5.1);
cout<<"the strawberry's default costprice is "<<strawberry.CostPrice;
cout<<endl;
strawberry.CostPrice=5.3;
cout<<"the strawberry's current costprice is "<<strawberry.getCostPrice();
return 0;
}
3.2 构造函数之使用初始化列表来初始化字段
示例如下,A B两种写法是等价的,A ==Fruit::Fruit(float cost):CostPrice(cost){ } ==就是使用初始化列表来初始化字段
Fruit::Fruit(float cost):CostPrice(cost){ } //A
Fruit::Fruit(float cost) //B
{
CostPrice=cost;
}
当需要初始化多个成员变量时,通过逗号分隔即可
Fruit::Fruit( int qua, float cost): quatity(qua), CostPrice(cost){}
完整编码如下
#include <iostream>
using namespace std;
class Fruit
{
public:
string name;
int quatity;
float CostPrice;
public:
Fruit(float cost);
float getCostPrice()
{
return CostPrice;
}
};
Fruit::Fruit(float cost):CostPrice(cost){ }
/*
Fruit::Fruit(float cost)
{
CostPrice=cost;
}
*/
int main()
{
Fruit strawberry(5.1);
cout<<"the strawberry's default costprice is "<<strawberry.CostPrice;
cout<<endl;
strawberry.CostPrice=5.3;
cout<<"the strawberry's current costprice is "<<strawberry.getCostPrice();
return 0;
}
3.3 拷贝构造函数
3.3.1 拷贝构造函数是一种特殊的构造函数,它在创建对象时,使用另一个同类的对象来初始化新创建的对象。使用场景:类带有指针变量并有动态内存分配(malloc new)。如果类中没有定义拷贝构造函数,编译器会自行定义一个
3.3.2 构造函数的基本形态
classname (const classname &obj) //obj是一个对象引用,用于初始化另一个对象
{
// 构造函数的主体
}
3.3.3 编码示例
#include <iostream>
using namespace std;
class Fruit
{
public:
float getCost()
{
return *ptrCost;
}
Fruit( float cost ) //Construct Func.
{
cout << "Construct Func. works..." << endl;
ptrCost = new float; //for pointer, memory allocation is first than use.
*ptrCost = cost;
}
Fruit( const Fruit &obj) // Copy Construct Func.
{
cout << "Copy Construct Func. works..." << endl;
ptrCost=new float;
*ptrCost = *obj.ptrCost; //copy value
}
~Fruit() // deconstruct Func.
{
cout << "Deconstruct Func. works and free memory." << endl;
delete ptrCost;
}
private:
float *ptrCost;
};
int main()
{
Fruit Lemon(23.2);
cout<<"Lemon is ready!"<<endl;
Fruit Grape=Lemon;
cout<<"Grape is ready"<<endl;
cout<<"Lemon's cost is: "<<Lemon.getCost()<<endl;
cout<<"Grape's cost is: "<<Grape.getCost()<<endl;
return 0;
}
3.4 析构函数
析构函数是类的一种特殊成员函数,在每次删除所创建对象时执行。析构函数的名称与类的名称完全相同,但需要在前面加波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(如关闭文件、释放内存)前释放资源
#include <iostream>
using namespace std;
class Fruit
{
public:
string name;
int quatity;
float CostPrice;
public:
Fruit()
{ cout<<"Object has been created."<<endl; }
~Fruit() //析构函数
{ cout<<"Object has been deleted."<<endl; }
float getCostPrice()
{
return CostPrice;
}
};
int main()
{
Fruit strawberry;
return 0;
}
3.5 友元函数 friend
友元可以是函数、也可以是类,其关键字是friend,友元可以访问类的private成员
#include <iostream>
using namespace std;
class Fruit
{
public:
friend void printFruit(Fruit fruit); //friend Func.
Fruit(int qua,float p):quantity(qua),price(p){}; //使用初始化列表来初始化字段
private:
int quantity;
float price;
};
void printFruit(Fruit fruit)
{
cout<<fruit.quantity<<' '<<fruit.price; //friend Func. can access Pirate!
cout<<endl;
}
int main()
{
Fruit tomato(100,3.1);
printFruit(tomato);
return 0;
}
3.6 内联函数 inline
3.6.1 引入内联函数,是为了提升程序中函数调用的效率,本质是通过空间换时间,所以内联函数一般都是不超过10行的小函数
3.6.2 内联函数的关键字是 inline,在使用内联函数时要注意:
(1) 在内联函数中不允许使用循环语句和开关语句(break)
(2) 内联函数的定义必须出现在内联函数第一次调用之前
(3) 类的成员函数都是内联函数
#include <iostream>
using namespace std;
inline int min(int a,int b) //
{
return (a<b)?a:b;
}
int main()
{
cout<<"min(2,3):"<<min(2,3)<<endl;
cout<<"min(2,1):"<<min(2,1)<<endl;
return 0;
}
3.7 this指针
3.7.1 在成员函数内部,this可以用来指向调用对象
3.72 通俗的解释:当你进入一个房子后,你可以看到房子内的桌子、椅子、地板,但是你看不到房子的全貌。对于类来说,你可以看到成员函数、成员变量,但你看不到实例本身,使用this可以让我们看到这个实例本身。个人理解:类就好比这座房子,this就好比一把钥匙,通过钥匙来打开了这座房子的门,那么里面的东西就随意你使用
3.7.3 友元函数没有 this 指针,因为友元不是类的成员,只有成员函数才有 this 指针
#include <iostream>
using namespace std;
class Fruit
{
public:
float price;
Fruit(int p):price(p){
};
bool compare(Fruit fruit)
{
return this->price > fruit.price;
}
};
int main()
{
Fruit grape(2.23), orange(2.27);
cout<<"Orange is more expensive than grape: ";
if(grape.compare(orange))
cout<<"true."<<endl;
else
cout<<"false."<<endl;
return 0;
}
3.8 赋值运算符重载函数 operator=
3.8.1 operator= 就是对赋值运算符 = 进行重载,在部分情况下,类对象进行析构时,会重复释放一块内存,从而导致程序崩溃,这种情况下,就需要重载赋值运算符 = 了
3.8.2 这里的部分情况,我还没有完全理解,就示例中展示的情况是:类对象向类对象赋值 且 构造函数里面涉及指针操控,后面遇到了再研究吧
3.8.3 一般形式如下
String& operator=(const String &a_b) {}
String B;
B = A;
3.8.4 示例
#include <cstring>
#include <iostream>
using namespace std;
class String
{
public:
String()
{
}
String(const char* ptrInputStr) //不加const会引起warning,why?
{
ptrStr = new char[strlen(ptrInputStr) + 1];
strcpy_s(ptrStr, strlen(ptrInputStr) + 1, ptrInputStr);
}
~String()
{
delete ptrStr;
}
String& operator=(const String &a_b) //该程序如果没有 赋值运算符 = 重载函数,在vs2019下会触发程序崩溃
{
cout<<"operator= is work."<<endl;
// 避免自赋值
if (this != &a_b)
{
// 避免内存泄露
if (ptrStr != NULL)
{
delete ptrStr;
ptrStr = NULL;
}
ptrStr = new char[strlen(a_b.ptrStr)+1];
strcpy_s(ptrStr, strlen(a_b.ptrStr) + 1,a_b.ptrStr);
}
return *this;
}
public:
char* ptrStr;
};
int main()
{
String A("keep coding");
cout << A.ptrStr << endl;
String B;
B = A;
cout << B.ptrStr << endl;
return 0;
}
3.8.3 参考资料 C++中的赋值运算符重载函数(operator=)作者:liitdar
3.9 类的静态成员
3.8.1 可以通过static 关键字将类成员定义为静态的。对于静态成员,无论创建多少个类的对象,静态成员都只有一个副本,静态成员在类的所有对象中是共享的
3.8.2 静态成员变量的初始化不能放在类的定义中,但是可以在类的外部进行初始化。如果不存在初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。静态成员变量示例
#include <iostream>
using namespace std;
class Fruit
{
public:
static int count;
Fruit()
{
count++;
}
};
int Fruit::count=0;
int main()
{
cout<<"Fruit's count is:"<<Fruit::count<<endl;
Fruit apple;
cout<<"Fruit's count is:"<<Fruit::count<<endl;
Fruit banana;
cout<<"Fruit's count is:"<<banana.count<<endl;
Fruit orange;
cout<<"Fruit's count is:"<<apple.count<<endl;
return 0;
}
3.8.5 静态成员函数没有this指针,只能访问静态成员。静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。示例如下:
#include <iostream>
using namespace std;
class Fruit
{
public:
static int count;
Fruit()
{
count++;
}
static statistics() //静态成员函数
{
return count;
}
};
int Fruit::count=0;
int main()
{
cout<<"Fruit's count is:"<<Fruit::statistics()<<endl;
Fruit apple;
cout<<"Fruit's count is:"<<Fruit::statistics()<<endl;
Fruit banana;
cout<<"Fruit's count is:"<<Fruit::statistics()<<endl;
Fruit orange;
cout<<"Fruit's count is:"<<Fruit::statistics()<<endl;
return 0;
}
二、继承
继承分为public, private, protected三种方式
1. public继承
#include <iostream>
using namespace std;
class Fruit
{
public:
float publicCost;
protected:
float protectedCost;
private:
float privateCost;
};
class HardShellFruit:public Fruit //HardShellFruit是 public继承 Fruit的子类
{
public:
void PrintPrice()
{
cout<<publicCost;
cout<<protectedCost; //基类的protected成员,在派生类中仍是protected,因此可以被派生类访问
//cout<<privateCost; 基类的private成员不能被派生类访问
}
};
int main( )
{
HardShellFruit mangosteen;
mangosteen.publicCost=20.1;
//mangosteen.protectedCost; 无法访问
// mangosteen.privateCost; 无法访问
return 0;
}
2. protected继承
#include <iostream>
using namespace std;
class Fruit
{
public:
float publicCost;
protected:
float protectedCost;
private:
float privateCost;
};
class HardShellFruit:protected Fruit //HardShellFruit是继承自Fruit的子类
{
public:
void PrintPrice()
{
cout<<publicCost; //protected继承,基类public成员变为 protected成员,可以在子类访问
cout<<protectedCost; //protected继承,基类protected成员还是protected成员,可以在子类访问
//cout<<privateCost; 基类的private成员不能被派生类访问
}
};
int main( )
{
HardShellFruit mangosteen;
//mangosteen.publicCost=20.1; protected继承,类public成员变为 protected成员,无法在类外访问
//mangosteen.protectedCost; 无法访问
// mangosteen.privateCost; 无法访问
return 0;
}
3. private继承
private也是默认继承
#include <iostream>
using namespace std;
class Fruit
{
public:
float publicCost;
protected:
float protectedCost;
private:
float privateCost;
};
class HardShellFruit:private Fruit //HardShellFruit是继承自Fruit的子类
{
public:
void PrintPrice()
{
cout<<publicCost; //private继承,基类public成员变为 private成员,可以在子类访问
cout<<protectedCost; //private继承,基类protected成员变为 private成员,可以在子类访问
//cout<<privateCost; 基类的private成员不能被派生类访问
}
};
int main( )
{
HardShellFruit mangosteen;
//mangosteen.publicCost=20.1; private继承,类public成员变为 private成员,无法在类外访问
//mangosteen.protectedCost; 无法访问
// mangosteen.privateCost; 无法访问
return 0;
}
三、其他
1. new/delete 与 malloc/free
- new和malloc都是告诉计算机开辟一段新的存储空间。大部分场景下new和malloc可以通用
- 他们之间的区别包括:
2.1 malloc与free是c++/c语言的标准函数,new/delete是C++的运算符
2.2 都可用于申请动态内存和释放内存。new/delete比malloc/free更加智能,因为new和delete在对象创建的时候自动执行构造函数,对象消亡之前会自动执行析构函数 //目前不是很理解
2.3 new/delete的功能完全覆盖了malloc和free,但C++不能把malloc/free淘汰,因为C++程序会调用C函数,而C只能用malloc/free管理动态内存
2.4 new返回指定类型的指针,并且可以自动计算出所需要的大小。malloc必须用户指定大小,并且默然返回类型为void*,必须强行转换为实际类型的指针。请看示例
#include <stdlib.h>
#include <iostream>
using namespace std;
#define max 10
int main()
{
int i;
int *p; //指针使用前先申请空间
p=(int*)malloc(sizeof(int)) ;
*p=1;
int *p_new;
p_new=new int;
*p_new=2;
cout<<"p="<<*p<<" p_new="<<*p_new<<endl;
free(p) ;
delete p_new;
int *a;
a=(int*)malloc(sizeof(int)*max);
int *b;
b=new int[max];
for(int i=0;i<max;i++)
{
a[i]=i+10;
b[i]=i+100;
}
cout<<"start print:"<<endl;
i=max;
while(i--)
{
cout<<a[i]<<"---"<<b[i]<<endl;
}
free(a);
delete b;
return 0;
}
2. reference 引用
- reference 引用:引用是已存在变量的另一个名字, 对应的符号是 & (指针的符号是 * )。引用和指针的区别是:
(1)不存在空引用,引用必须连接到一块合法的内存
(2)一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向另一个对象
(3)引用必须在创建时被初始化;指针可以在任何时间被初始化 - 进一步解释
(1)指针 B 指向 A:B 借用了 A 的值,但是B的内存地址和A不同。引用 B 引用 A:B的内存地址和值 与A 完全相同,A和B任意一个发生改变, A和B同时改变
(2)套用火影设定:指针更像是普通的分身法(虚拟分身),即使是分身破灭,对自身也没有影响;引用更像是影分身(实体分身),影分身发生了变化,本身也会受到影响
(3)你被朋友起了绰号"派大星",这个绰号就相当于你名字的一个引用,在朋友看来,无论叫哪个都是在叫你 - 示例代码
#include <iostream>
using namespace std;
int main ()
{
int i;
int &reference=i;
i=3;
cout<<i<<' '<<reference<<endl; //print:3 3
reference=4;
cout<<i<<' '<<reference<<endl; //print:4 4
return 0;
}
- 先进行下了解,后面在应用中再继续补充
3. vector基础使用
- vector是STL中的一种数据结构(也称为容器),相当于数组的加强版,使用时需要 #include 。STL Standard Template Library 标准模板库
- 例题:给定n个字符串,对n个字符串按照字典序排列
#include <algorithm> //sort
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int input;
string str;
vector<string> vs; //vector的存储对象是string
cin>>input; //输入待排序的元素个数
while(input--)
{
cin>>str;
vs.push_back(str); // push_back():向vs中添加元素
}
sort(vs.begin(),vs.end()); //begin() 首元素, vs.end() 尾元素
vector<string>::iterator vit; //定义迭代器
cout<<"Print result:"<<endl;
for(vit=vs.begin();vit!=vs.end();vit++)
cout<<*vit<<endl;
return 0;
}
4. const
const: 定义一个常量,程序执行期间不能改变。把常量定义为大写字母形式,是一个很好的编程习惯
5. size_t
size_t 同 int 一样,都可以进行变量定义: int a; size_t a; 都代表定义了一个整形变量a. 但是int固定为4byte,而size_t的大小与OS有关、因此移植性更好