C和C++中struct区别
-
c语言struct只有变量
-
c++语言struct 既有变量,也有函数
typedef struct _Person{
char name[64];
int age;
}Person;
typedef struct _Aninal{
char name[64];
int age;
int type; //动物种类
}Ainmal;
void PersonEat(Person* person){
printf("%s在吃人吃的饭!\n",person->name);
}
void AnimalEat(Ainmal* animal){
printf("%s在吃动物吃的饭!\n", animal->name);
}
int main(){
Person person;
strcpy(person.name, "小明");
person.age = 30;
AnimalEat(&person);
return EXIT_SUCCESS;
}
在c语言中,行为和属性是分开的,也就是说吃饭这个属性不属于某类对象,而属于所有的共同的数据,所以不单单是PeopleEat可以调用Person数据,AnimalEat也可以调用Person数据,那么万一调用错误,将会导致问题发生。
封装
-
把变量(属性)和函数(操作)合成一个整体,封装在一个类中
-
对变量和函数进行访问控制
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7EWfbO4X-1595337986759)(file:///C:\Users\Godfiry\AppData\Local\Temp\ksohtml12228\wps1.jpg)]
*[struct和class的区别?]*
class默认访问权限为private,struct默认访问权限为public.
/*请设计一个Person类,Person类具有name和age属性,提供初始化函数(Init),并提供对name和age的读写函数(set,get),但必须确保age的赋值在有效范围内(0-100),超出有效范围,则拒绝赋值,并提供方法输出姓名和年龄.*/
构造函数
构造函数主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
语法:构造函数函数名和类名相同,没有返回值,不能有void,但可以有参数。
按参数类型:分为无参构造函数和有参构造函数
按类型分类:普通构造函数和拷贝构造函数
//构造函数的几种调用方式
//1. 无参构造调用方式
void test01()
{
//调用无参构造函数
Person person1;
person1.PrintPerson();
//无参构造函数错误调用方式
//Person person2();
//person2.PrintPerson();
}
//2. 调用有参构造函数
void test02()
{
//第一种 括号法,最常用
Person person01(100);
person01.PrintPerson();
//调用拷贝构造函数
Person person02(person01);
person02.PrintPerson();
//第二种 匿名对象(显示调用构造函数)
Person(200); //匿名对象,没有名字的对象
Person person03 = Person(300);
person03.PrintPerson();
//注意: 使用匿名对象初始化判断调用哪一个构造函数,要看匿名对象的参数类型
Person person06(Person(400)); //等价于 Person person06 = Person(400);
person06.PrintPerson();
//第三种 =号法 隐式转换
Person person04 = 100; //Person person04 = Person(100)
person04.PrintPerson();
//调用拷贝构造
Person person05 = person04; //Person person05 = Person(person04)
person05.PrintPerson();
}
//不能调用拷贝构造函数去初始化匿名对象,也就是说以下代码不正确:
class Teacher{
public:
Teacher(){
cout << "默认构造函数!" << endl;
}
Teacher(const Teacher& teacher){
cout << "拷贝构造函数!" << endl;
}
public:
int mAge;
};
void test(){
Teacher t1;
//error C2086:“Teacher t1”: 重定义
Teacher(t1); //此时等价于 Teacher t1;
}
/* b为A的实例化对象,A a = A(b) 和 A(b)的区别?
当A(b) 有变量来接的时候,那么编译器认为他是一个匿名对象,当没有变量来接的时候,编译器认为你A(b) 等价于 A b.*/
拷贝构造的调用时机
- 对象以值传递的方式传给函数参数
- 函数局部对象以值传递的方式从函数返回(vs debug模式下调用一次拷贝构造,qt不调用任何构造)
- 用一个对象初始化另一个对象
//1. 旧对象初始化新对象
void test01(){
Person p(10);
Person p1(p);
Person p2 = Person(p);
Person p3 = p; // 相当于Person p2 = Person(p);
}
//2. 传递的参数是普通对象,函数参数也是普通对象,传递将会调用拷贝构造
void doBussiness(Person p){}
void test02(){
Person p(10);
doBussiness(p);
}
//3. 函数返回局部对象
Person MyBusiness(){
Person p(10);
cout << "局部p:" << (int*)&p << endl;
return p;
}
void test03() //编译器进行了优化
{
//vs release、qt下没有调用拷贝构造函数
//vs debug下调用一次拷贝构造函数
Person p = MyBusiness();
cout << "局部p:" << (int*)&p << endl;
}
第三种情况的解释
编译器存在一种对返回值的优化技术,RVO(Return Value Optimization).在vs debug模式下并没有进行这种优化,所以函数MyBusiness中创建p对象,调用了一次构造函数,当编译器发现你要返回这个局部的对象时,编译器通过调用拷贝构造生成一个临时Person对象返回,然后调用p的析构函数。
我们从常理来分析的话,这个匿名对象和这个局部的p对象是相同的两个对象,那么如果能直接返回p对象,就会省去一个拷贝构造和一个析构函数的开销,在程序中一个对象的拷贝也是非常耗时的,如果减少这种拷贝和析构的次数,那么从另一个角度来说,也是编译器对程序执行效率上进行了优化。
所以在这里,编译器偷偷帮我们做了一层优化: 当我们这样去调用: Person p = MyBusiness(); 编译器偷偷将我们的代码更改为:
void MyBussiness(Person& _result) { _result.X:X(); //调用Person默认拷贝构造函数 //.....对_result进行处理 return; }
构造函数的注意点:
默认情况 c++为我们提供三个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造(简单的值拷贝)
用户定义了拷贝构造,c++不会提供默认构造函数的拷贝构造
定义了构造函数,c++不会提供默认无参构造,但会提供拷贝构造
深拷贝和浅拷贝
浅拷贝
同一类型的对象之间可以赋值,使得两个对象的成员变量的值相同,两个对象仍然是独立的两个对象,这种情况被称为浅拷贝
问题:
浅拷贝没有任何副作用,但是当类中有指针,并且指针指向动态分配的内存空间,析构函数做了动态内存释放的处理,会导致内存问题。(两个对象的指针同一个内存空间),当空间的释放的时候,只能释放一次,出现运行时错误,)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z6Wmo0Ck-1595337986763)(file:///C:\Users\Godfiry\AppData\Local\Temp\ksohtml5504\wps1.jpg)]
深拷贝(有指针时,自定义拷贝构造,析构函数)
类中有指针,并且此指针有动态分配空间,析构函数做了释放处理,往往需要自定义拷贝构造函数,自行给指针动态分配空间,深拷贝。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-imMW5yOw-1595337986766)(file:///C:\Users\Godfiry\AppData\Local\Temp\ksohtml5504\wps2.jpg)]
#include<iostream>
using namespace std;
class person
{
public:
person(char *name,int age);
~person();
person(const person &person);
private:
char *m_name;
int m_age;
};
person::person(char *name,int age)
{
char *m_name = (char*)malloc(sizeof(char)*100);
strcpy(m_name,name);
m_age = age;
}
person::person(const person &person)
{
char *m_name = (char*)malloc(sizeof(char)*100);
strcpy(m_name,person.m_name);
m_age = person.m_age;
}
person::~person()
{
if(m_name != NULL)
{
free(m_name);
}
}
int main()
{
person p1("zhang3",5);
person p2 = p1;
return 0;
}
析构函数
析构函数主要用于对象****销毁前****系统自动调用,执行一些清理工作。
- 析构函数函数名是在类名前面加”~”组成,没有返回值,不能有void,不能有参数,不能重载。
多个对象的构造和析构
初始化列表
(初始化成员列表(参数列表)只能在构造函数使用。)
//传统方式初始化
Person(int a,int b,int c)
{
mA = a;
mB = b;
mC = c;
}
//初始化列表方式初始化
Person(int a, int b, int c):mA(a),mB(b),mC(c)
{
}
类对象作为成员
一个类可以作为其他类的成员
先调用对象成员的构造函数,再调用本身的构造函数。
析构函数和构造函数调用顺序相反,先构造,后析构。
#include<iostream>
using namespace std;
class person
{
public:
person()
{
cout << "person new" <<endl;
}
~person()
{
cout << "person delete" <<endl;
}
};
class man
{
public:
man()
{
cout << "man new" <<endl;
}
~man()
{
cout << "man delete" <<endl;
}
private:
person p;
};
int main()
{
man m1;
return 0;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oXNwLKWE-1595337986771)(C:\Users\Godfiry\AppData\Roaming\Typora\typora-user-images\image-20200719103418272.png)]
expilt关键字(保留)
c++提供了关键字explicit,禁止通过构造函数进行的隐式转换。声明为explicit的构造函数不能在隐式转换中使用。
注意点
- explicit用于修饰构造函数,防止隐式转化。
- 是针对单参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造)而言。
动态对象的创建(堆空间创建对象)
C语言中使用malloc和free实现空间的申请和释放
但在初始化一个对象时 使用malloc 和free时出现以下问题:
- 程序员必须确定对象的长度。
- malloc返回一个void指针,c++不允许将void赋值给其他任何指针,必须强转。
- malloc可能申请内存失败,所以必须判断返回值来确保内存分配成功。
- 用户在使用对象之前必须记住对他初始化,构造函数不能显示调用初始化(构造函数是由编译器调用),用户有可能忘记调用初始化函数。
//c语言实例化一个对象
class Person{
public:
Person(){
mAge = 20;
pName = (char*)malloc(strlen("john")+1);
strcpy(pName, "john");
}
void Init(){
mAge = 20;
pName = (char*)malloc(strlen("john")+1);
strcpy(pName, "john");
}
void Clean(){
if (pName != NULL){
free(pName);
}
}
public:
int mAge;
char* pName;
};
int main(){
//分配内存
Person* person = (Person*)malloc(sizeof(Person));
if(person == NULL){
return 0;
}
//调用初始化函数
person->Init();
//清理对象
person->Clean();
//释放person对象
free(person);
return EXIT_SUCCESS;
}
//c的动态内存分配函数太复杂,容易令人混淆,是不可接受的,c++中我们推荐使用运算符new 和 delete.
c++创建一个对象
//用new创建一个对象时,它就在堆里为对象分配内存并调用构造函数完成初始化。
Person* person = new Person;
//相当于:
Person* person = (Person*)malloc(sizeof(Person));
if(person == NULL){
return 0;
}
person->Init();
解释:
- New操作符能确定在调用构造函数初始化之前内存分配是成功的,所有不用显式确定调用是否成功。
- 它带有内置的长度计算、类型转换和安全检查。
静态成员(静态成员变量和成员函数)
静态成员变量
在一个类中,若将一个成员变量声明为static,这种成员称为静态成员变量。与一般的数据成员不同,无论建立了多少个对象,都只有一个静态数据的拷贝。静态成员变量,属于某个类,所有对象共享。
在编译阶段就进行分配空间,对象阿hi没有创建,就已经分配了空间
注意:
- 静态成员变量必须在类中声明,在类外定义。
- 静态数据成员不属于某个对象,在为对象分配空间中不包括静态成员所占空间。
- 静态数据成员可以通过类名或者对象名来引用。
class Person
{
public:
//类的静态成员属性
static int sNum;
private:
static int sOther;
};
//类外初始化,初始化时不加static
int Person::sNum = 0;
int Person::sOther = 0;
int main()
{
//1. 通过类名直接访问
Person::sNum = 100;
cout << "Person::sNum:" << Person::sNum << endl;//100
//2. 通过对象访问
Person p1, p2;
p1.sNum = 200;
cout << "p1.sNum:" << p1.sNum << endl;//200 共享数据
cout << "p2.sNum:" << p2.sNum << endl;//200
//3. 静态成员也有访问权限,类外不能访问私有成员
//cout << "Person::sOther:" << Person::sOther << endl;
Person p3;
//cout << "p3.sOther:" << p3.sOther << endl;
system("pause");
return 0;
}
静态成员函数
在类定义中,前面有static说明的成员函数称为静态成员函数。静态成员函数使用方式和静态变量一样,同样在对象没有创建前,即可通过类名调用。静态成员函数主要为了访问静态变量,但是,不能访问普通成员变量。
静态成员函数的意义,不在于信息共享,数据沟通,而在于管理静态数据成员,完成对静态数据成员的封装。
注意:
n 静态成员函数只能访问静态变量,不能访问普通成员变量
解释:(各个对象的普通对象值不同 共有函数无法访问各个对象变量
静态成员函数的使用和静态成员变量一样
静态成员函数也有访问权限
普通成员函数可访问静态成员变量、也可以访问非经常成员变量
class Person
{
public:
//普通成员函数可以访问static和non-static成员属性
void changeParam1(int param){
mParam = param;
sNum = param;
}
//静态成员函数只能访问static成员属性
static void changeParam2(int param)
{
//mParam = param; //无法访问
sNum = param;
}
private:
static void changeParam3(int param)
{
//mParam = param; //无法访问
sNum = param;
}
public:
int mParam;
static int sNum;
};
//静态成员属性类外初始化
int Person::sNum = 0;
int main(){
//1. 类名直接调用
Person::changeParam2(100);
//2. 通过对象调用
Person p;
p.changeParam2(200);
//3. 静态成员函数也有访问权限
//Person::changeParam3(100); //类外无法访问私有静态成员函数
//Person p1;
//p1.changeParam3(200);
return 0;
}
const 静态成员变量
如果一个类的成员,既要实现共享,又要实现不可改变,那就用 static const 修饰。****定义静态const数据成员时,最好在类内部初始化****。
class Person
{
public:
//static const int mShare = 10;
const static int mShare = 10; //只读区,不可修改
};
int main()
{
cout << Person::mShare << endl;
//Person::mShare = 20;
return EXIT_SUCCESS;
}
单例模式
Singleton(单例):在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()工厂方法,让客户可以访问它的唯一实例;为了防止在外部对其实例化,将其默认构造函数和拷贝构造函数设计为私有;在单例类内部定义了一个Singleton类型的静态对象,作为外部共享的唯一实例。
class Printer
{
public:
static Printer* getInstance(){ return pPrinter;}//返回一个对象
void PrintText(string text){
cout << "打印内容:" << text << endl;
cout << "已打印次数:" << mTimes << endl;
cout << "--------------" << endl;
mTimes++;
}
private:
Printer(){ mTimes = 0; }
Printer(const Printer&){}
private:
static Printer* pPrinter;//私有成员
int mTimes;
};
Printer* Printer::pPrinter = new Printer;//编译阶段创建一个对象
void test(){
Printer* printer = Printer::getInstance();//共有函数获取
printer->PrintText("离职报告!");
printer->PrintText("入职合同!");
printer->PrintText("提交代码!");
}
this指针
成员变量和函数的存储
c++通过封装,把数据和函数放到了一起,**但是成员函数(member function)虽然内含在class声明之内,却不出现在对象中。(不占对象空间)**n
每一个非内联成员函数(non-inline member function)只会诞生一份函数实例.
class MyClass01{
public:
int mA;
};
class MyClass02{
public:
int mA;
static int sB;
};
class MyClass03{
public:
void printMyClass(){
cout << "hello world!" << endl;
}
public:
int mA;
static int sB;
};
class MyClass04{
public:
void printMyClass(){
cout << "hello world!" << endl;
}
static void ShowMyClass(){
cout << "hello world!" << endl;
}
public:
int mA;
static int sB;
};
int main(){
MyClass01 mclass01;
MyClass02 mclass02;
MyClass03 mclass03;
MyClass04 mclass04;
cout << "MyClass01:" << sizeof(mclass01) << endl; //4
//静态数据成员并不保存在类对象中
cout << "MyClass02:" << sizeof(mclass02) << endl; //4
//非静态成员函数不保存在类对象中
cout << "MyClass03:" << sizeof(mclass03) << endl; //4
//静态成员函数也不保存在类对象中
cout << "MyClass04:" << sizeof(mclass04) << endl; //4
return EXIT_SUCCESS;
}
C++类对象中的变量和函数是分开存储。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iQ3zdWzx-1595337986775)(file:///C:\Users\Godfiry\AppData\Local\Temp\ksohtml15096\wps1.jpg)]
代码是如何区分那个对象调用自己的呢?
通过this指针
this指针说明
c++通过提供特殊的对象指针,this指针,解决上述问题。This指针指向被调用的成员函数所属的对象。
c++规定,this指针是隐含在对象成员函数内的一种指针。当一个对象被创建后,它的每一个成员函数都含有一个系统自动生成的隐含指针this,用以保存这个对象的地址,也就是说虽然我们没有写上this指针,编译器在编译的时候也是会加上的。因此****this也称为“指向本对象的指针”,****this指针并不是对象的一部分,不会影响sizeof(对象)的结果。
this指针是C++实现封装的一种机制,它将对象和该对象调用的成员函数连接在一起,在外部看来,每一个对象都拥有自己的函数成员。一般情况下,并不写this,而是让系统进行默认设置。
说明:
- this指针永远指向当前对象
- 隐含于每个类的非静态成员函数中
- 静态成员函数没有this指针,不能操作非静态成员变量
this指针使用
- 当形参和成员变量同名时,可用this指针来区分
- 在类的非静态成员函数中返回对象本身,可使用return *this.
class Person{
public:
//1. 当形参名和成员变量名一样时,this指针可用来区分
Person(string name,int age){
//name = name;
//age = age; //输出错误
this->name = name;
this->age = age;
}
//2. 返回对象本身的引用
//重载赋值操作符
//其实也是两个参数,其中隐藏了一个this指针
Person PersonPlusPerson(Person& person){
string newname = this->name + person.name;
int newage = this->age + person.age;
Person newperson(newname, newage);
return newperson;
}
void ShowPerson(){
cout << "Name:" << name << " Age:" << age << endl;
}
public:
string name;
int age;
};
//3. 成员函数和全局函数(Perosn对象相加)
Person PersonPlusPerson(Person& p1,Person& p2){
string newname = p1.name + p2.name;
int newage = p1.age + p2.age;
Person newperson(newname,newage);
return newperson;
}
int main(){
Person person("John",100);
person.ShowPerson();
cout << "---------" << endl;
Person person1("John",20);
Person person2("001", 10);
//1.全局函数实现两个对象相加
Person person3 = PersonPlusPerson(person1, person2);
person1.ShowPerson();
person2.ShowPerson();
person3.ShowPerson();
//2. 成员函数实现两个对象相加
Person person4 = person1.PersonPlusPerson(person2);
person4.ShowPerson();
system("pause");
return EXIT_SUCCESS;
}
const修饰成员函数时
用const修饰的成员函数时,const修饰this指针指向的内存区域,成员函数体内不可以修改本类中的任何普通成员变量,
当成员变量类型符前用mutable修饰时例外。
#include<iostream> using namespace std; //const修饰成员函数 class Person{ public: Person(){ this->mAge = 0; this->mID = 0; } //在函数括号后面加上const,修饰成员变量不可修改,除了mutable变量 void sonmeOperate() const { //没有加mutable this->mAge = 200; //mAge不可修改 error: assignment of data-member `Person::mAge' in read-only structure this->mID = 10; } void ShowPerson(){ cout << "ID:" << mID << " mAge:" << mAge << endl; } private: int mAge; mutable int mID; }; int main(){ Person person; person.sonmeOperate(); person.ShowPerson(); system("pause"); return 0; }
const修饰对象
- 常对象只能调用const的成员函数
- 常对象可访问 const 或非 const 数据成员,不能修改,除非成员用mutable修饰
class Person{
public:
Person(){
this->mAge = 0;
this->mID = 0;
}
void ChangePerson() const{
mAge = 100;
mID = 100;
}
void ShowPerson(){
this->mAge = 1000;
cout << "ID:" << this->mID << " Age:" << this->mAge << endl;
}
public:
int mAge;
mutable int mID;
};
void test(){
const Person person;
//1. 可访问数据成员
cout << "Age:" << person.mAge << endl;
//person.mAge = 300; //不可修改
person.mID = 1001; //但是可以修改mutable修饰的成员变量
//2. 只能访问const修饰的函数
//person.ShowPerson();
person.ChangePerson();
}