类和对象
数据成员的访问定义成员函数调用成员函数私有成员函数构造函数和析构函数构造函数成员初始化列表在构造函数中使用new的注意事项this指针const成员函数运算符重载友元函数拷贝构造函数隐式转换法深拷贝与浅拷贝静态成员变量静态成员函数C++面向对象的三大特性:封装、继承和多态。
数据成员的访问
class Point{double x,y;public:int a,b;protected:int c,d;};
使用class关键字与struct不同,成员在默认情况下是私有的。
public:公有,可以由类的用户访问,private:私有,除了对自身的成员函数外都不可见。protected:保护,可以在它的类的子类中被访问。
定义成员函数
如果不想要用户直接访问数据成员,但又可以控制设置它的值,解决方案是使数据成员私有,并且依靠成员函数来提供间接访问。
class Point{public://内联定义函数void set_x(doublex newx){x = newx;}void set_y(double newy){y = newy;}double get_x(){return x;}double get_y(){return y;}private:double x, y;};//在类声明之外定义void Point::set_x(doublex newx){x = newx;}void Point::set_y(double newy){y = newy;}double Point::get_x(){return x;}double Point::get_y(){return y;}
函数是公开的,可以被用户访问。数据成员是私有的,不能访问。
C++提供两种方式定义成员函数:
内联,一个成员函数可以通过类本身中提供定义,被内联定义。函数原型紧跟要执行的语句的大括号{},与用inline关键字修饰的全局函数一样,内联函数的函数体被直接扩展到调用函数的主体中。(优化运行性能,但是占用更多内存,适用于简短函数)一个成员函数可以在类声明之外定义。在这种情况下,函数定义要求用作用域符(::)来说明函数的作用域。
格式:
返回类型 类名::函数名(参数){ 语句组 }
在类声明之外定义一个成员函数能使你编写任意长的函数,而无需让类声明本身来负担。
调用成员函数
两种方式:
对象.函数(参数)通过指向对象的指针:指针->函数(参数)
私有成员函数
私有成员函数和私有数据成员一样,对外隐藏,只能被类中其他函数调用。
函数成员还可以用protected被做成受保护的,此时类的子类可以参考甚至修改(重载)此函数。private和protected的区别只与涉及子类的地方有关
构造函数和析构函数
构造函数是一个初始化函数,当一个对象在内存中被分配后,它会自动被调用。它与类的名称相同,且无返回值。与其他函数一样,可以被内联,也可以在类外定义。
在其他成员函数中可以调用构造函数,但是只是产生一个匿名对象,对本对象并无影响
析构函数是清理函数。作用在对象销毁前系统自动调用,执行清理操作。
都只会被调用一次。
构造函数成员初始化列表
如果Classy是一个类,而mem1、mem2和mem3都是这个类的数据成员,则类构造函数可以使用如下的语法来初始化数据成员:
Classy::Classy(int n, int m) :mem1(n),mem2(0),mem3(n*m+2){...}
上述代码将mem1初始化为n,mem2初始化为0,将mem3初始化为n*m+2,从概念上说,初始化工作是在对象创建时完成的,此时还未执行括号中的任何代码。注意事项:
这种格式只能用于构造函数必须用这种格式初始化非静态const数据成员(至少在C++11之前)必须用这种格式初始化引用&数据成员。数据成员初始化的顺序与它们出现在类中的声明顺序相同,与排列顺序中的顺序无关。
必须用带有初始化列表的构造函数:
1.成员类型是没有默认构造函数的类。若没有提供显示初始化式,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。
2.const成员或引用类型的成员。因为const对象或引用类型只能初始化,不能对他们赋值。
#include <iostream>using namespace std;//A 没有默认构造函数(无参构造函数)class A{public:A(int k);static int e;private:const int a=1;const int b;int & c;};int d = 5;A::A(int k):b(2),c(d){//a = 1;cout << "a = "<<a << endl;cout << "b = " << b << endl;cout << "c = " << c << endl;cout << "k = " << k << endl;}//静态成员变量的初始化: 类型+ 使用类名+作用域限定符int A::e = 6;class B{public:B();B(int k);private:A a;int f;};//因为a无默认构造函数,所以必须在初始化列表中初始化,给a传入参数B::B(int k):a(k){}B::B() : a(6){}int main(void) {A a(7);cout << "A::e = "<<A::e << endl;cout << "a.e = " << a.e << endl;cout << "---------------------" << endl;B b(9);return 0;}/**结果:a = 1b = 2c = 5k = 7A::e = 6a.e = 6---------------------a = 1b = 2c = 5k = 9*/
在构造函数中使用new的注意事项
如果构造函数中使用的new动态分配内存,则必须提供使用delete的析构函数。new和delet必须相互兼容。new对应delete,new[ ] 对应于delete[]。如果有多个构造函数,则必须以相同的方式使用new和delete,因为只有一个析构函数,所有构造函数必须跟它兼容。this指针
this指针指向被调用对象本身。每个非静态成员函数(包括构造函数和析构函数)都有一个this指针。this是调用对象的地址。*this才是被调用对象(解引用)。所以可以使用 this->a,或者(*this).a来调用被调用对象的数据和函数。
const成员函数
保证函数不会修改调用对象。
cosnt Stock land = Stock("Test");land.show();
对于当前的编译器来说,会对第二行报错,因为show的代码无法保证调用对象不被修改。我们以前通过将函数参数声明为const引用,或者指向const的指针来解决这种问题。但是这里show()方法没有任何参数,它所使用的对象由方法调用隐式地提供。C++的解决方法是将const关键字放在函数括号的后面:
//函数声明void show() cosnt;//函数定义void Stock::show() const
以这种方法声明和定义的函数被称为const成员函数,只要类方法不修改调用对象,就应该将其声明为const。
注意:
常成员函数不能调用其他非常成员函数(静态函数除外)常成员函数可以修改静态成员变量const对象只能调用常成员函数两个成员函数的名字和参数表相同,但一个是 const 的,一个不是,则它们算重载
#include <iostream>#include <vector>using namespace std;class A{public:A(int aa = 0):a(aa) {}static int b;void show()const {cout << ++b << endl;setA1();cout << "show const" << endl;//setA();错误}void show() {cout << ++b << endl;setA1();cout << "show" << endl;//setA();错误}void setA() {cout << ++a << endl;}static void setA1() {cout << ++b << endl;}private:int a;};int A::b = 1;int main(void) {A a;const A ca;ca.show();a.show();cout << a.b << endl;cout << ca.b << endl;return 0;}/*23show const45show55*/
运算符重载
友元函数
通过让函数成为类的友元,可以赋予该函数与类的成员函数相同的访问权限。
创建友元函数:
第一步是将其原型放在类声明中,并在类声明前加上关键字 friend:
friend Time test(double n,Time &t);
虽然该函数是在类中声明的,但它不是成员函数,因此不能用成员运算符调用。虽然友元函数不是成员函数,但是它与成员函数的访问权限相同。
第二步是在类外编写函数的定义,不需要在定义中使用关键字friend,也不需要使用::限定符。
拷贝构造函数
拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:
用同一类的一个已知的对象去新建并初始化该类的另一个对象。复制对象把它作为参数传递给函数,值传递—参数为对象。复制对象,并从函数返回这个对象—返回值为对象。编译器生成临时对象
如果在类中没有定义拷贝构造函数,编译器会自行定义一个(浅拷贝)。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数(深拷贝)。拷贝构造函数的最常见形式如下:
//const关键字是为了防止被拷贝的对象被修改//使用引用的方式传递参数classname (const classname &obj) {// 构造函数的主体}
#include <iostream>using namespace std;class Line{public:int getLength(void);Line();Line(int len); // 简单的构造函数Line(const Line &obj);// 拷贝构造函数~Line(); // 析构函数private:int *ptr;};// 成员函数定义,包括构造函数Line::Line() {cout << "调用无参构造函数" << endl;// 为指针分配内存ptr = new int;*ptr = 0;}Line::Line(int len){cout << "调用构造函数" << endl;// 为指针分配内存ptr = new int;*ptr = len;}Line::Line(const Line &obj){cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;ptr = new int;*ptr = *obj.ptr; // 拷贝值}Line::~Line(void){cout << "释放内存" << endl;delete ptr;}int Line::getLength(void){return *ptr;}void display(Line obj){cout << "line 大小 : " << obj.getLength() << endl;}Line g() {Line line3(18);return line3;}// 程序的主函数int main(){Line line1(10);//调用普通构造函数Line line2 = line1;//情况1,同Line line2 = Line(line1);用类的一个已知的对象去初始化该类的另一个对象时,需要调用拷贝构造函数display(line1);//情况2,对象作为参数传递给函数,需要调用拷贝构造函数display(line2);//情况2Line line3 = g();//情况3,从函数返回对象display(line3);//情况2return 0;}/*结果:调用构造函数//Line line1(10);调用拷贝构造函数并为指针 ptr 分配内存//Line line2 = line1;/调用拷贝构造函数并为指针 ptr 分配内存//display(line1);line 大小 : 10//display(line1);释放内存//display(line1);调用拷贝构造函数并为指针 ptr 分配内存//display(line2);line 大小 : 10//display(line2);释放内存//display(line2);调用构造函数//Line line3 = g();调用拷贝构造函数并为指针 ptr 分配内存//Line line3 = g();释放内存//Line line3 = g();调用拷贝构造函数并为指针 ptr 分配内存//display(line3);line 大小 : 18display(line3);释放内存display(line3);释放内存//line3释放内存//line2释放内存//line1*/
不要用拷贝构造函数初始化匿名对象:
Line line1(10);//错误,“Line line1”: 重定义//编译器会认为 Line (line1) == Line line1Line(line1);
隐式转换法
Line line1 = 10;//等于Line line1 = Line(10);
默认情况下,C++编译器会给一个类添加三个函数:
默认构造默认拷贝构造默认析构
如果我们写了有参构造函数,编译器就不再提供默认构造;但会提供默认拷贝函数。 如果我们写了拷贝构造函数,编译器就不再提供其他构造函数;
拷贝构造 > 有参构造 > 默认构造
深拷贝与浅拷贝
默认的拷贝构造函数—浅拷贝。复制的是成员的值。按字节拷贝,当对象的数据资源是由指针指向的堆时,默认的拷贝构造函数只是将指针复制。 ,多个对象共用同一块资源,同一块资源释放多次,崩溃或者内存泄漏
深拷贝:深拷贝会重新开辟内存空间。,每个对象拥有自己的资源,必须显式提供拷贝构造函数和赋值运算符。
静态成员变量
#include <iostream>using std::cout;using std::cin;using std::endl;class Point{public:static int npoints;//特殊情况,用const和static一起修饰,只可以在类中声明一次const static int a = 0;};//static变量必须在类外定义一次,并只定义一次int Point::npoints = 0;int main(void) {Point p1, p2, p3;Point::npoints++;p1.npoints++;p2.npoints += 5;cout << p3.npoints << endl;//p3.npoints == 7return 0;}
注意:不能在类声明中初始化静态成员变量,这是因为声明只描述了如何分配内存,但并不分配内存。例外情况:静态数据成员为const整数类型或枚举型。
数据通常是由单个对象所拥有的,而不是类拥有。然而它可以声明由同一个类的所有对象共享的一个或多个数据成员,无论创建了多少对象,程序都只创建一个静态类变量副本,这样的数据成员称为静态的。
成员函数也可以是静态的,静态成员函数的定义和声明和其他函数一样,但是用途受限,不能引用this指针,不能引用非静态成员函数。
静态成员可以是公有私有和受保护的。
class Student{public:Student(char *name, int age, float score);void show();public:static int m_total; //静态成员变量private:char *m_name;int m_age;float m_score;};
static 成员变量属于类,不属于某个具体的对象,即使创建多个对象,也只为 m_total 分配一份内存,所有对象使用的都是这份内存中的数据。当某个对象修改了 m_total,也会影响到其他对象。
static 成员变量必须在类声明的外部初始化,具体形式为:
type class::name = value;
type 是变量的类型,class 是类名,name 是变量名,value 是初始值。将上面的 m_total 初始化:
int Student::m_total = 0;
静态成员变量在初始化时不能再加 static,但必须要有数据类型。被 private、protected、public 修饰的静态成员变量都可以用这种方式初始化。
注意:static 成员变量的内存既不是在声明类时分配,也不是在创建对象时分配,而是在(类外)初始化时分配。反过来说,没有在类外初始化的 static 成员变量不能使用。
static 成员变量既可以通过对象来访问,也可以通过类来访问。
static 成员变量不占用对象的内存,而是在所有对象之外开辟内存,即使不创建对象也可以访问。
总结:
一个类中可以有一个或多个静态成员变量,所有的对象都共享这些静态成员变量,都可以引用它。
static 成员变量和普通 static 变量一样,都在内存分区中的全局数据区分配内存,到程序结束时才释放。这就意味着,static 成员变量不随对象的创建而分配内存,也不随对象的销毁而释放内存。而普通成员变量在对象创建时分配内存,在对象销毁时释放内存。
静态成员变量必须初始化,而且只能在类体外进行。例如:
int Student::m_total = 10;
初始化时可以赋初值,也可以不赋值。如果不赋值,那么会被默认初始化为 0。全局数据区的变量都有默认的初始值 0,而动态数据区(堆区、栈区)变量的默认值是不确定的,一般认为是垃圾值。
静态成员变量既可以通过对象名访问,也可以通过类名访问,但要遵循 private、protected 和 public 关键字的访问权限限制。当通过对象名访问时,对于不同的对象,访问的是同一份内存。
静态成员函数
静态成员函数(static)不能通过对象来调用,也不能使用this指针。函数声明必须包含关键字static,但如果函数定义是独立的,则其中不能包含关键字static。如果静态成员函数是在公有部分声明的,则可以使用类名和作用域解析运算符::来调用它。
//在Test类中声明和定义static int howmany(){return num_string;}//调用:int count = Test::howmany;
由于静态成员函数不与特定的对象相关联。因此只能使用静态数据成员。