前两篇关于类与对象的博客,都是类与对象中不可或缺的对象,这篇就是在前两篇的基础上,再对类与对象进行补充。
当我们进行拷贝造作函数,或者赋值运算符重载的时候,我们不给出这两个函数,编译器就会默认自动生成,默认对类进行位拷贝(按照基本类型进行值的拷贝)。
那么编译器给的到底有没有问题呢?
看代码:
class Slist
{
public:
Slist()
{
}
Slist(int size,int cap)
{
int* arr = (int*)malloc(sizeof(int)*cap);
_arr = arr;
_size = size;
_cap = cap;
}
void PopSlsit(Slist& p,int data)
{
p._arr[p._size] = data;
p._size++;
}
void print()
{
for (int i = 0; i < _size; i++)
{
cout << _arr[i] << endl;
}
}
~Slist()
{
if (_arr)
{
free(_arr);
_arr = NULL;
}
}
private:
int* _arr;
int _size;
int _cap;
};
int main()
{
Slist s1(0, 10);
Slist s2;
s1.PopSlsit(s1, 1);
s1.PopSlsit(s1, 2);
s1.PopSlsit(s1, 3);
s2 = s1;
s2.print();
system("pause");
return 0;
}
上边的代码能正确的打印出 1,2,3,但是最后就会触发一个断点,为什么会触发一个断点?
通过调试,我们看到当s1释放之后,s2的_arr也被释放,所以就是对同一个地址进行了两次释放,所以就会出错。
在拷贝的时候只拷贝地址,而并未复制资源,我们就成之为浅拷贝;
如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源也进行相应复制,这个 过程就可以叫做深拷贝。
如图:
1.const修饰普通变量
在C++中,const修饰的变量已经为一个常量,具有宏的属性,即在编译期间,编译器会将const所修饰的常量进行替换。
int main()
{
const int a = 10;
a = 30;//错误 error C3892: “a”: 不能给常量赋值
system("pause");
return 0;
}
2.const修饰类成员
(1)const修饰类成员变量时,该成员变量必须在构造函数的初始化列表中初始化
(2) const修饰类成员函数,实际修饰该成员函数隐含的this指针,该成员函数中不能对类的任何成员进行修改
看下边代码:
class Date
{
public:
void print()
{
_year = 2018;
_month = 11;
_day = 1;
cout << _year << " " << _month << " " << _day << " " << endl;
}
void print()const//相当于 void print (const Date* this)
{
_year = 2018;//出错
_month = 11;//出错
_day = 1;//出错
cout << _year << " " << _month << " " << _day << " " << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.print();
const Date d2;
d2.print();
system("pause");
return 0;
}
思考题:
1. const对象可以调用非const成员函数和const成员函数吗?
2. 非const对象可以调用非const成员函数和const成员函数吗?
3. const成员函数内可以调用其它的const成员函数和非const成员函数吗?
4. 非const成员函数内可以调用其它的const成员函数和非const成员函数吗?
class Date
{
public:
void print()
{
_year = 2018;
_month = 11;
_day = 1;
cout << _year << " " << _month << " " << _day << " " << endl;
}
void print1()const
{
cout << _year << " " << _month << " " << _day << " " << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
const Date d1;
//d1.print();//不能通过编译,error C2662: “void Date::print(void)”: 不能将“this”指针从“const Date”转换为“Date &”
d1.print1();
Date d2;
d2.print();
d2.print1();
system("pause");
return 0;
}
class Date
{
public:
void print()
{
cout << _year << " " << _month << " " << _day << " " << endl;
}
void print1()const
{
cout << _year << " " << _month << " " << _day << " " << endl;
}
void test()
{
print();
print1();
}
void test()const
{
//print();//error C2662: “void Date::print(void)”: 不能将“this”指针从“const Date”转换为“Date &”
print1();
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.test();
const Date d2;
d2.test();
system("pause");
return 0;
}
通过上述的两个代码:
const对象只能调用const修饰的成员函数;
非const对象可以调用const修饰的成员函数,也可以调用非const修饰的成员函数。
在const成员函数内,只能调用const修饰的成员函数
在非const成员函数内,既可以调用const修饰的成员函数,也可以调用非const修饰的成员函数。
前边说过的构造函数,由于能给成员多次赋值,所以只能称之为赋初值,而不是初始化(初始化只能有一次)。
1.初始化列表:在构造函数后边加一个冒号,后边在跟用逗号隔开的所有数据成员列表,并在每一个“成员变量”后面跟一个放在括号中的初始值或者表达式。
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{}
private:
int _year;
int _month;
int _day;
};
特性:
(1)每个成员在初始化列表中只能出现依次,也就是初始化只进行依次。
(2)初始化列表只能用于初始化数据成员,数据成员在类中的定义顺序就是初始化列表中的初始化顺序,否则就会出现混乱;
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{}
private:
const int _year;
int _month;
int _day;
};
class Date1
{
public:
Date1(int year, int day, int month)
:_year(year)
, _month(month)
, _day(day)
{}
private:
const int _year;
int _month;
int _day;
};
int main()
{
Date d(2018,11,2);
Date1 d1(2018, 11, 2);
system("pause");
return 0;
}
(3)尽量避免用成员初始化成员,成员的初始化顺序最好和成员的定义顺序一致;
(4)如果类中有以下三个成员,一定要在初始化列表中进行初始化:
。引用成员变量
。const成员变量
。类类型的成员
2.构造函数的作用
(1)构造对象
(2)给对象中成员变量一个初始值
(3)类型转化
对于单参构造函数,可以接受的参数转化成类类型的对象
class Date
{
public:
Date(int year)
:_year(year)
{}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d(2018);
int a = 2019; //把整形变量给日期类型对象赋值
d = a; //实际编译器做一个隐式的类型转化,用2019构造一个无名函数,最后赋值给d
system("pause");
return 0;
}
用explicit修饰构造函数,可以抑制这种构造函数的隐式转化。
class Date
{
public:
explicit Date(int year)
:_year(year)
{}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d(2018);
int a = 20;
d = a; //错误error C2679: 二进制“=”: 没有找到接受“int”类型的右操作数的运算符(或没有可接受的转换)
system("pause");
return 0;
}
3.默认构造函数
如果一个类未显示定义构造函数,编译器就会合成一个默认的构造函数;如果类显示定义了,编译器就不再合成。
看代码:
class Date
{
public:
/*Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}*/
void setdate(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d;
d.setdate(2018, 11, 1);
system("pause");
return 0;
}
由于d在创建的时候,是没有任何意义的,完全可以不用合成,合成就需要调用,调用这个构造函数又没有实际的意义,所以编译器就不会自动合成。
d对象创建时,并没有相关的汇编代码。
所以在调用构造函数的时候,编译器会根据自己需求来选择合适构造函数。如果类中没有显示定义构造函数,如果需要编译器会自己默认合成一个构造函数,但这个构造函数一定没有参数。
如下面代码:
class Time
{
public:
Time(int hour=11, int minute=59, int second=59)
:_hour(hour)
, _minute(minute)
, _second(second)
{}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
void setdate(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
int main()
{
Date d;
d.setdate(2018, 11, 1);
system("pause");
return 0;
}
因为在Date类创建当中有一个Time类,编译器在调用Date类的构造函数完成对Date类对象的构造, Date类为显示,所以编译器必须合成:因为Date类中中存在Time类,对Date类构造的时候必须对类中的Time类进行构造,所以编译器必须合成。
友元分成友元函数和友元类
1.友元函数
友元函数可以访问一个类的私有成员,它是定义在类外部的普通函数,在需要时在类中进行声明,在函数面前加friend关键字。
class Date
{
friend ostream& operator<<(ostream& _cout, const Date& d);
public:
Date()
{
}
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
int main()
{
Date d(2018, 11, 1);
cout << d << endl;
system("pause");
return 0;
}
特性:
(1)友元函数可以访问类中的私有成员,但不是类的成员;
(2)友元函数可以在类中任何一个地方进行声明;
(3)友元函数不能有const修饰
(4)一个函数可以是多个类的友元函数
2.友元类
class Time
{
friend class Date;//友元类的声明
public:
Time(int hour=0, int minute=0, int second=0)
:_hour(hour)
, _minute(minute)
, _second(second)
{}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
friend ostream& operator<<(ostream& _cout, const Date& d);
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
void TimeOfDay(int hour,int minute,int second)//直接访问私有成员
{
_t._hour = hour;
_t._minute = minute;
_t._second = second;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
int main()
{
Date d(2018, 11, 1);
d.TimeOfDay(11, 59, 59);
system("pause");
return 0;
}
特性:
(1)优点:友元类可以提高代码的效率;
在一个类中访问另一个类的私有成员,原来需要通过调用函数的方法,要花费大量的时间可空间。
(2)缺点:友元类破坏了类的封装性和隐藏性
类中私有的成员本来就是不能被别的类或者函数访问,友元的加入就是的这一种特性失去。
(3)友元类是单向的;
在A类中,用friend加B类进行声明,只能是B类中访问A的私有成员,而A类不能访问B类的私有成员。
(4)友元类没有传递性。
在友元的加持下,A类可以访问B类的私有成员,B类可以访问C类的私有成员,但不代表A类就可以访问C类的私有成员。
先来一个问题:如何知道一个类创建了多少个对象?以下两种方式是否可行,为什么?
(1) 在类中添加一个普通的成员变量进行计数
(2) 使用一个全局变量来计数
1.声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员 函数,称之为静态成员函数。静态的成员变量一定要在类外进行初始化。
class A
{
public:
A()
{
_count++;
}
A(const A& d)
{
_count++;
}
~A()
{
--_count;
}
static int GetAcount()
{
return _count;
}
private:
static int _count;
};
void test()
{
cout << A::GetAcount() << endl; //0
A a1, a2, a3;
A a4(a3);
cout << A::GetAcount() << endl; //4
}
int A::_count = 0;//在类的外部进行初始化
int main()
{
test();
system("pause");
return 0;
}
特性:
(1)静态成员变必须在类的外部定义,定义是不加static关键字。
(2)静态成员函数没有this指针,不能访问任何非静态的成员。
(3)静态成员会被所有的类共享,不属于某个具体的实例。
(4)静态成员和普通的成员一样。
问题:
1. 静态成员函数可以调用非静态成员函数吗?
class A
{
public:
A()
{
_count++;
}
A(const A& d)
{
_count++;
}
~A()
{
--_count;
}
static int GetAcount()
{
return _count;//错误error C2597: 对非静态成员“A::_count”的非法引用
}
int GetACount()
{
return _count;
}
private:
int _count;
};
void test()
{
cout << A::GetAcount() << endl;
A a1, a2, a3;
A a4(a3);
cout << A::GetAcount() << endl;
}
int main()
{
test();
system("pause");
return 0;
}
答:静态成员函数是不能调用非静态成员函数的。
2. 非静态成员函数可以调用类的静态成员函数吗?
class A
{
public:
A()
{
_count++;
}
A(const A& d)
{
_count++;
}
~A()
{
--_count;
}
static int GetAcount()
{
return _count;
}
int GetACount()
{
return _count;
}
private:
static int _count;
};
void test()
{
cout << A::GetAcount() << endl;
A a1, a2, a3;
A a4(a3);
cout << A::GetAcount() << endl;
//cout << A::GetACount() << endl;//错误error C2352: “A::GetACount”: 非静态成员函数的非法调用
}
int A::_count = 0;
int main()
{
test();
system("pause");
return 0;
}
答:非静态成员函数不能调用类的静态成员函数。
问题:在不用乘除法,for,while,if,else,switch,case,等关键字以及条件判断语句,完成1+2+3+4+......+n。
我们可以先定义一个类,接着在创建n个这样的类型,然后通过在构造函数函数中完成累加的工作。
class Test
{
public:
Test()//构造函数
{
++n;
sum += n;
}
static int getsum()
{
return sum;
}
private:
static int sum;
static int n;
};
int Test::sum = 0; //类外初始化静态变量
int Test::n = 0; //类外初始化静态变量
int main()
{
Test*ptr = new Test[10];//设 n=10
cout << Test::getsum() << endl;
delete[]ptr;
ptr = NULL;
system("pause");
return 0;
}