结构体与C的区别
- 类型上不再需要struct关键字,直接用结构体名即可
- C++结构体中允许函数存在:
- ①在结构体中声明,在结构体外实现(也可以直接在结构体内实现);
- ②结构体函数中访问数据 ,是可以直接访问的;
- 调用(和数据成员方式一样):
- (1)对象(结构体变量).成员
- (2)对象指针 -> 成员
- (3)(*对象指针).成员
- C++在没有写构造函数和权限限定时,用法和C语言一样
内存池
#include <iostream> using namespace std; //允许申请一段内存,供给程序使用,综合管理内存 //malloc 内存是在堆区 //calloc 内存是在自由储存区 void testMemory() { char* memorySum = new char[1024]; //int* pNum = new(申请内存的开始位置) int[3]; int* pNum = new(memorySum) int[3]{1,2,3]; //char* pstr = new(pNum+3) char[20]{"Iloveyou"};//与下面一句等效 char* pstr = new(memorySum+12) char[20]{"Iloveyou"}; for(int i = 0; i < 3; i++) { cout<<pNum[i]<<" "; } cout<<endl; /*等效for(int i = 0; i < 3; i++) * { * cout<<((int*)memorySum)[i]<<" "; * * } */ cout<<endl<<pstr<<endl; cout<<(memorySum+12)<<endl; delete[] memorySum; memorySum = nullptr; } int main() { testMemory(); return 0; }
string类型
- string创建
- ①带初始化
- ②不带初始化
- ③通过另一个字符串创建
- string基本操作:①拷贝;②赋值;③连接;④比较。
- to_string()函数
string str1 = "Iloveyou"; string str2 = to_string(1234); cout<<str2<<endl;
函数
函数 功能 capacity() 容器能储存数据的个数 size() 容器目前存在的元素数 reserve() 指定容器能储存数据的个数 resize() 重新指定有效元素的个数,区别reserve()在指定容量的大小
类和对象
//关键字 类名 Class classname { Access specifiers: //访问修饰符,有三种public protected private Data members/variables; //数据成员/变量 Member functions(){} //方法(成员函数) }
- 成员函数的声明在类中,而定义一般在类外;
- 对于类中的成员函数的使用需要加上类名限定,如Box::get(void),其中Box是类名,"::"是访问符号,又叫范围解析运算符或域操作符,get()是类中声明的函数,void是参数;
- 对于类中的数据成员/变量由于权限限定,一般不能直接拿来使用,需要重新定义一组相对应的数据成员/变量在函数中进行调用,如length = len; breadth = bre; height = hei;
class Box { public: double length; // 长度 double breadth; // 宽度 double height; // 高度 // 成员函数声明 double get(void); void set( double len, double bre, double hei ); }; // 成员函数定义 double Box::get(void) //::是域操作符,Box是类名,get()是类中的成员函数,void是参数 { return length * breadth * height; } void Box::set( double len, double bre, double hei) //重新定义相应的数据成员/对象 { length = len; breadth = bre; height = hei; }
- 调用成员函数是在对象上使用点运算符(.),这样它就能操作与该对象相关的数据;
Box myBox; // 创建一个对象 myBox.get(); // 调用该对象的成员函数
类构造函数和析构函数
- 类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。作用:构造函数可用于为某些成员变量设置初始值。
- 默认的构造函数没有任何参数,但如果需要,构造函数也可以带有参数。这样在创建对象时就会给对象赋初始值。
- 任何类中都只存在一个默认的构造函数,当我们自己写了构造函数后,默认的就不存在的。但是可以写不止一个构造函数,写出含有不同数量参数的构造函数,如
class Ti { public: Ti(string _name,int _age){ //第一个构造函数 name = _name; age = _age; cout<<"带参构造函数"<<endl; } Ti(){ cout<<"无参构造函数"<<endl; } //第二个构造函数 protected: string name = "Jone"; age = 18; } int main() { Ti skt("jone",28); Ti rng; }
- 构造函数有几个参数,在主函数中创建相应的对象的时候也需要写上相应数量的参数,如
class Ti { public: Ti(string _name,int _age){ //见下 name = _name; age = _age; } protected: string name = "Jone"; age = 18; } int main() { Ti ti("jone",28); //参数数量需要与类中构造函数定义的参数数量相同 }
- 构造函数可以直接使用delete删掉,在类中操作,如Line()=delete;
- 为了构造不同模样的函数,可以在类中对构造函数进行缺省处理,使得在主函数中使用构造函数是不必局限于固定的参数数量(不超过最大数量):
class girl { public: girl(string _name = "",int _age = 18){ //""内的部分省略了,于是主函数中相应的构造函 name = _name; //数不超过两个的,都可以创建,如0,1,2个参数 age = _age; } protected: string name; int age; }; int main() { girl girl1; //无参数 girl girl2("Lisa"); //一个参数 girl girl3("Kasa",19); //两个参数 return 0; }
- 使用默认的构造函数可以使用default,如Line()=default;
class Line { public: void setLength( double len ); double getLength( void ); Line() = default; // 使用默认的构造函数 private: double length; };
- 对于构造函数可以使用初始化列表来初始化字段格式如下 构造函数名(参数1,参数2,...):成员1(参数1),成员2(参数2),...{}
class Line { public: void setLength( double len ); double getLength( void ); Line(); // 这是构造函数声明 //Line() = delete; //删除构造函数 private: double length; }; Line::Line( double len): length(len) //使用初始化列表来初始化字段 { cout << "Object is being created, length = " << len << endl; } //等效于下面未使用初始化列表的初始化方式 Line::Line( double len) { length = len; cout << "Object is being created, length = " << len << endl; }
- 可以用初始化参数列表的方式来使用构造函数调用另一个构造函数,也叫做委托构造;
class student { pubilc: GIF(string name,int age):name(_name),age(_age) {} //通过初始化列表的方式用构造函数调用另一个构造函数,也叫做委托构造 GIF():GIF("名字",18) {} void print { cout<<name<<"\t"<<age<<endl; } protected: string name; int age; }
- 类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
- 当类中的数据成员是指针进行动态内存申请了内存时,需要手写析构函数。
- 析构函数一般不需要手动调用,析构函数的调用不代表生命周期的结束。
拷贝构造函数
- 拷贝构造函数也是构造函数,长相和构造函数一样,不过参数是固定的;
- 拷贝构造函数的作用是通过一个对象去初始化另一个对象,即当通过一个对象去创建另一个新的对象时需要调用拷贝构造函数;
- 即使不写拷贝构造函数,也存在一个默认的拷贝构造函数//后面浅拷贝会讲;
区分函数是否属于拷贝构造函数的重要依据,是否创建了对象,对比参数(MM mm)与(MM& mm);
- 注意写拷贝构造函数是的const 修饰,若不修饰,在对匿名对象进行赋值时可能会报错;
//对比上面一个代码框的代码(默认的拷贝构造函数),现在这个是手写的拷贝构造函数 class MM { public: MM(string _name,int _age) :name(_name),age(_age) {} void print() { cout<<name<<"\t"<<age,,endl; } //拷贝构造函数 MM(const MM& mm) //MM girl(mm); //const作用看下方匿名对象 { name = mm.name; //girl.name = mm.name; age = mm.age; //girl.age = mm.age; cout<<"拷贝构造"<<endl; } //右值引用拷贝构造 //当使用右值引用操作后,主函数中创建对象是不可以进行赋值操作 //MM(MM&& mm) //{ // name = mm.name; //girl.name = mm.name; // age = mm.age; //girl.age = mm.age; // cout<<"右值引用拷贝构造"<<endl; //} protected: string name; int age; } void printData(MM mm) //MM mm = 实参;创建了一个对象,这一种调用了拷贝构造函数 { mm.print(); } void printData2(MM& mm) //调用形参,没有创建对象,不存在拷贝本,没有调用拷贝构造函数 { mm.print(); } int main() { MM mm("mm",18); mm.print(); //显式调用 MM girl(mm); //现在调用的是上面手写的拷贝构造函数 girl.print(); //隐式调用 MM girl2 = mm; girl2.print(); //注意:这种不是拷贝构造,而是运算符重载 MM girl3; girl3 = mm; girl3.print(); //无名对象 匿名对象 MM temp; temp = MM("匿名",18); //进行右值操作后不能进行赋值操作 temp.print(); //匿名对象创建对象的时候,一定要用const修饰 MM temp2 = MM("匿名",19); //用右值操作可以,上方构造函数创建对象没用const修饰不可以 temp.print(); }
深浅拷贝
- 浅拷贝:默认的拷贝构造叫做浅拷贝,因为会出现析构问题(重复释放内存问题)。
- 深拷贝:在构造函数中进行动态内存申请,也就是在构造函数中做new操作,并且进行拷贝赋值(strcpy)的操作。
//由于浅拷贝会出现析构问题(重复释放内存)而出现错误导致程序崩溃 //如若不进行深拷贝,创建的三个对象里的name指针指向了同一个内存,即最初开辟的"baby"所指向的内存区域 #include<iostream> #include<string> #include<cstring> using namespace std; class MM { public: MM(const char* mname, int age) :age(_age) { name = new char[strlen(mname) + 1]; strcpy_s(name,strlen(mname) + 1, mname); } void prnt() { cout<<name<<"\t"<<age<<endl; } MM(const MM& object) //通过赋值的方式,也是浅拷贝,加上动态内存申请就是深拷贝 { name = object.name; //深拷贝需要做动态内存申请,也就是重新new,strcpy一遍 { name = new char[strlen(object.name) + 1]; strcpy_s(name,strlen(object.name) + 1,object.name);//+1是字符串结束标记 } age = object.age; } ~MM() { delete[] name; } protected: char* name; int age; } int main() { MM mm("baby",19); //下面两种都叫浅拷贝,都是通过默认的拷贝构造创建的对象 MM girl(mm); MM gm = mm; mm.print(); girl.print(); gm.print(); return 0; }
构造顺序和析构顺序问题
- 普通对象的构造顺序和析构顺序是相反的;
- new出来的对象,在delete的时候会直接调用析构;
- static对象,当程序关闭时释放;
#include<iostream> #include<string> using namespace std; class MM { public: MM(string name = "x") :name(name) { cout<<name; } ~MM() { cout<<name; } protected: string name; } int main() { { //普通变量的构造顺序和析构顺序相反 MM mm1("A"); //A static MM mm2("B"); //B static定义的变量在程序关闭的时候生命周期才会结束 MM* p3 = new MM("C"); //C MM mm4[4]; //xxxx delete p3; //C delete 直接调用析构函数 p3 = nullptr; //xxxxAB } //最终结果:ABCxxxxCxxxxAB return 0; }
C++结构体构造函数
- 结构体与类有相像之处,也有不同之处;
#include <iostream> #include <string> using namespace std; struct MM { //默认为公有属性,权限限定词(public/protected/proviate)也是可以用的 string name; int age; //MM(string name) :name(name) //{ // cout<<"构造函数"<<endl; //} }; int main() { //采用创建时候赋值的方式,也是调用构造函数 MM object = {"Lisa",18}; //如果这么写,需要在上面struct中写一个含有两个参数的构造函数 cout << object.name << "\t" << object.age << endl; }