//构造函数
Stock food = Stock("World Cabbage", 250, 1.2); //显式普通构造函数:写出了函数名;Stock(....)是临时对象,然后复制给了food.
Stock garment("Furry Mason", 50, 5.5); //隐式普通构造函数:没有函数名
Stock second(); //这是一个函数, 为了和函数区分,隐式调用默认构造函数时候不加()
Stock second; //对象,隐式调用默认构造函数
Stock second = Stock(); //对象,显式调用默认构造函数
Stock* second = new Stock; //对象,调用默认构造函数
Stock* second = new Stock(); //对象,显式调用默认构造函数
//初始化和赋值
Stock stock2 = Stock("xxxx", 2, 2.0); //是初始化,可能会创建临时对象,也可能不创建。
stock1 = Stock("yyy", 4, 2.5); //是赋值,Stock(...)创建临时变量,然后赋值,然后删除临时变量
SS ss = 8; //SS有一个构造函数,其参数只有一个,
SS ss; ss = 8; //涉及:用SS(int)创建临时对象,把8作为初始化值,逐成员赋值形式把临时对象的内容复制到ss中。
ss=SS(8); ss=(SS)8; //用explicit禁止后,前一个也不能叫转换,可以叫显式调用构造函数赋值。后一个是强制类型转换
const Stock& get_big(const Stock& s) const {
if (s.data > data) return s;
else return ????; //这就是this指针的作用, *this
}
Stock arr[4]; //调用的默认构造函数
Stock arr[4] = {Stock(), Stock(), Stock(), Stock()}; //显式调用一般构造函数
//enum
enum egg{small, big}; egg mine = big; //big可能冲突, sizeof(egg) = 4; 用int来表示enum值
enum class shirt(small, big); shirt clth = shirt::small; //防止其他enum中也有small, 冲突
~~enum class : short shirt{xx, ml}; //xx, ml用short类型实现~~ 错了
enum shirt: short {xx, ml}; //sizeof(shirt) = 2
ostream& operator<<(ostream& os, const Time& time){
os << time.h << time.m << "\n"; return os;
} //可以级联, cout<<t1<<t2;
void operator<<(ostream& os, const Time& time){
os<<time.h << time.m << "\n";
} //只能使用一次: cout << t1; 等价于:cout--os, t1--time
//转换函数
operator double() const {return xx;} Stone wolf; //定义,必须是1类成员函数2没有返回类型3不能有参数
double host = double(wolf); //显式调用1
double host = (double) wolf; //显式调用2
double host = wolf; //隐式调用
explict operator double() const {return xx;}; double x=wolf; //出错,必须double x = double(wolf);或者double x=(double)wolf;
//static成员变量,作用于属于类,但是生命周期是程序运行时间
class te{private: static int num;} //声明写在类里面,类写在头文件中
int te::num = 0; //static成员变量必须在类外初始化,写在实现文件中,如果写在头文件中,会重复定义error. const int可以在类内初始化,~~静态枚举也可以类内初始化~~ 。错的,enum不能用static
struct LTD{
LTD(const LTD& des) = delete; //用delete显式禁止编译器生成默认拷贝构造函数
void operator=(const LTD& des) = delete; //禁止,这个运算符重载
}
#include<new>
chaff* p1, *p2; chaff buff1[30]; chaff buff2[50];
int *p3, *p4;
p1 = new chaff; p3 = new int[30];
p2 = new (buff1) chaff; //在buff1的位置分配chaff的内存
p4 = new (buff2) int[20]; //在buff2的位置分配int[20]的内存
char* p = new char; delete p;
char* p = new char[len]; delete [] p;
stringbad* pp = new stringbad(bad1); //用bad1来初始化了新new的stringbad, 利用了拷贝构造函数,如果没有自定义,使用默认的拷贝构造函数
stringbad* pp2 = new stringbad("ddss"); //调用对应构造函数初始化新创建的对象
stringbad* pp3 = new stringbad; //调用默认构造函数
//构造函数中有new动作时候,函数按值传递还是按引用传递都会受影响
struct stringbad {
stringbad(const char* p); stringbad(); //必须给一个默认构造函数,构造函数中有new的操作
~stringbad(); //因为有new,析构函数必须有,且有delete的操作
}
void callme1(stringbad & temp); void callme2(stringbad temp); //按引用和按值传递
callme1(obj1); //没问题
callme2(obj2); //出问题,值传递,复制了指针的值即是同一个地址, obj2-复制-temp-函数结束,temp释放,调用析构函数,释放了同一块内存的东西,导致obj2中的数据受损
//对于局部变量,跳出scope时候自动调用析构函数,删除的顺序与创建局部变量的顺序相反
//除了自己定义的构造函数,在参数传递,stringbad te2 = te1;这种情况时候都会调用拷贝构造函数,拷贝构造函数的形参是const stringbad&, 复制构造函数会创建一个副本,也就是stringbad te2 = stringbad(te1)
//参数传递初始化析构了一次,实参在函数结束又析构了一次,两次释放同一块内存导致终止程序运行。
stringbad& string::operator=(const string& other){
if (this == &other) return *this; //this是地址, *this是对象
delete[] str; //如果没有上面的判断,在obj1=obj1完成之前,obj1里面的东西已经空了
len = other.len; str=new char[len+1]; std::strcpy(str, other.str); return *this;
}
struct string{
string(const char*);
string(const string&);
string& operator=(const string&);
};
string te; char* ch[40]; cin.getline(ch, 40);
te = ch; //这时候没有operator=(const char*) 所以采用的是:string(ch)-->te=string(ch)--> ~string(ch), 但是如果重载了operator=(const char*)就不用创建删除临时对象了
类的public函数就是接口。
封装:
1.public接口和实现细节分开,数据作为private,不能直接访问就是封装
2.类函数定义和类声明放在不同的文件中,定义编译成lib不可见也是封装
内联函数应该放到头文件中,被使用内联函数的文件include。
类创建的每个新对象都有自己的存储空间,用于存储内部变量和类成员;同一个类的所有对象共享同一组类方法。
通过成员函数访问数据成员,需要有一个函数负责数据成员的初始化–构造函数。
声明类对象时候自动调用构造函数。
1.显式调用构造函数
2.隐式调用构造函数
构造函数的形式:用于初始化数据成员。一般构造函数,传递参数过去;默认构造函数不传递参数。默认构造函数:1有形参,在形参阶段提供默认值,2没形参,在函数内部提供默认值。 给一般构造函数的参数提供默认值,相当于一举两得。
析构函数用于delete构造函数中new的东西,如果构造函数没有new则析构函数相当于没干事。什么时候写析构函数,看构造函数有没有申请内存空间。
插播:定位newy运算符
头文件: #include
什么时候调用类对象的析构函数?
1.如果是static类型的,程序结束时候自动调用析构函数
2.如果是局部类型的,程序块结束时候调用析构函数
3.如果是new出来的,使用delete时候调用析构函数
4.如果是临时的,使用完后调用析构函数
类对象的赋值, 每个数据成员的内容进行赋值。
const对象意味着所有数据成员都是const,在调用成员函数时候,不能调用非const成员函数,因为可能会改变数据成员。
如果一个类的构造函数只有一个参数,可以用赋值语法创建对象并初始化。这也可以看成是类的默认类型转换。 explcit可以禁止这种特性。也就是explicit作为单个参数的构造函数的前缀。
作用域为类的常量: 枚举,static; 所有对象都不包含枚举,在类作用域中碰到枚举就用枚举值替换。这两个都是因为类没有实例化,不能在类中开辟内存。
友元(友元函数,友元类,友元成员函数)和public函数可以操作private成员变量。
友元函数在类中用friend声明,但不是类的成员函数。不需要::调用也不需要.而是operator+(x,y)这样。
<< 重载必须用友元的原因:cout<<Time; 必须要用两个对象来调用<<, 如果是成员函数则: Time << xxx; 这种调用显然有问题。
运算符重载函数也是可以重载的,只要标识符不一样(个数,类型,const)
类里面有一种叫转换函数的东西, 也叫运算符函数,用于强制类型转换。因为隐式可能是因为误操作,所以加explicit前缀,使转换函数必须显式调用。
类里面的特殊成员函数:
1.默认构造函数,没有定义任何构造函数时候生效
2.默认析构函数,没有定义析构函数时候生效
3.拷贝构造函数,没有定义拷贝构造函数时候特定调用形式下生效
4.赋值运算符,如果没有重载的话,类会默认生效这个
5.地址运算符,没有定义时候类默认生效
6.移动构造函数
7.移动复制运算符
1.1.默认构造函数构造的对象的数据成员是未知的,只能有一种形式的默认构造函数,如果有多种,编译器不知道匹配哪个报错
2.2.用于初始化时候,不是赋值时候。下面都是初始化操作都会调用拷贝构造函数。
stringbad ditto(mott);
stringbad mee = mott; //可能用拷贝,也可能用赋值
stringbad eg = stringbad(mott); //同上
stringbad* sg = new stringbad(mott)
void fun(stringbad xx); fun(mott);
逐个赋值非静态变量的值,指针就是charp; ditto.p=mott.p; 按值传递; ditto.obj=mott.obj 调用obj的拷贝构造函数;
这也是浅拷贝(默认拷贝构造函数)的弊端,在有初始化操作时候,会多释放一次同一块内存,所以要自己定义拷贝构造函数,初始化时候,重新开辟空间并复制之前的内容到新开辟的空间。这样在初始化使用完后就不会析构掉原始数据的内存了,析构的是被初始化对象自己的内存。
如果一个类包含了用new来初始化的指针对象时候,就要显式定义一个拷贝构造函数。
4.4.默认的赋值运算符原型是:
stringbad& stringbad::operator=(const stringbad&); 接收一个对象引用,返回一个对象引用
赋值和初始化的一个区分就是,初始化是对没有初始值的变量进行的操作,赋值时对已经有初始值的变量进行的操作。比如:
stringbad ob1(“xxxxxx”);
stringbad obj2; obj2 = obj1; //就是赋值
为什么赋值运算符重载时候不能赋值给自身就是上面这个问题,obj1 = obj1,在数据复制给左边的obj1之前,右边的obj1里面的数据就被析构掉了。必须返回引用, 可以级联。代码如上。
c++11的空指针: nullptr. int p = nullptr.
静态成员函数:
1.不能通过对象调用静态成员函数
2.静态成员函数没有this指针,不与对象关联,所以只能处理静态成员变量,静态成员函数,属于类的成分
重载赋值运算符的好处是节省了临时对象的构建和删除。
构造函数有多个,但是析构函数只有一个,所以不同的构造函数要兼容析构函数。
返回对象,结合函数,一个类对象作为函数的返回值:
1.返回对象时候如果不是引用,调用拷贝构造函数
2.如果返回的引用是来自于形参,则形参是const,返回值也得是const引用
3.按值返回时候(返回局部变量),使用拷贝构造函数生成返回的对象
4.返回指向对象的指针,