C++知识整理(中)

一、 类(class)
1、 类与封装
类只是一个模板(Template),编译后不占用内存空间,所以在定义类时不能对成员变量进行初始化,因为没有地方存储数据。只有在创建对象以后才会给成员变量分配内存,这个时候就可以赋值了。
类通常分为两个部分:类的实现细节和类的使用方式
当使用类时,不需要关心其实现细节
当创建类时,才需要考虑其内部实现细节。
2、 封装的基本概念
并不是类的每个属性都是对外公开的。
而一些类的属性是对外公开的。
必须在类的表示法中定义属性和行为的公开级别。
3、 C++中类的封装
所谓封装,是指尽量隐藏类的内部实现,只向用户提供有用的成员函数。
类可以看做是一种数据类型,它类似于普通的数据类型,但是又有别于普通的数据类型。类这种数据类型是一个包含成员变量和成员函数的集合。
成员变量:C++中用于表示类属性的变量
成员函数:C++中用于表示类行为的函数
C++中可以给成员变量和成员函数定义访问级别。
Public:成员变量和成员函数可以在类的内部和外界访问和调用
Private:成员变量和成员函数只能在类的内部被访问调用
类的成员变量和普通变量一样,也有数据类型和名称,占用固定长度的内存。但是,在定义类的时候不能对成员变量赋值,因为类只是一种数据类型或者说是一种模板,本身不占用内存空间,而变量的值则需要内存来存储。
类的成员函数也和普通函数一样,都有返回值和参数列表,它与一般函数的区别是:成员函数是一个类的成员,出现在类体中,它的作用范围由类来决定;而普通函数是独立的,作用范围是全局的,或位于某个命名空间内。
接下来给出了 Student 类的定义,如下所示:

class Student{
public:
    //成员变量
    char *name;
    int age;
    float score;
    //成员函数
    void say(){
        cout<<name<<"的年龄是"<<age<<",成绩是"<<score<<endl;
    }
};

这段代码在类体中定义了成员函数。你也可以只在类体中声明函数,而将函数定义放在类体外面,如下图所示:

class Student{
public:
    //成员变量
    char *name;
    int age;
    float score;
    //成员函数
    void say();  //函数声明
};
//函数定义
void Student::say(){
    cout<<name<<"的年龄是"<<age<<",成绩是"<<score<<endl;
}

在类体中直接定义函数时,不需要在函数名前面加上类名,因为函数属于哪一个类是不言而喻的。
但当成员函数定义在类外时,就必须在函数名前面加上类名予以限定。::被称为域解析符(也称作用域运算符或作用域限定符),用来连接类名和函数名,指明当前函数属于哪个类。
成员函数必须先在类体中作原型声明,然后在类外定义,也就是说类体的位置应在函数定义之前。
根据C++软件设计规范,实际项目开发中的成员变量以及只在类内部使用的成员函数(只被成员函数调用的成员函数)都建议声明为 private,而只将允许通过对象调用的成员函数声明为 public。
4、 类成员的作用域
类成员的作用域都只在类的内部,外部无法直接访问
成员函数可以直接访问成员变量和调用成员函数
类的外部可以通过类变量访问public成员
类成员的作用域与访问级别没有关系(C++中用struct定义的类中所有成员默认为public)
Java、C# 程序员注意,C++ 中的 public、private、protected 只能修饰类的成员,不能修饰类,C++中的类没有共有私有之分。
5、 struct定义的类与class定义的类的区别
用struct定义类时,所有成员的默认访问级别为public
用class定义类时,所有成员的默认访问级别为private
6、 类的真正形态
C++中的类支持声明和实现的分离。
.h头文件中只有类的声明(成员变量和成员函数的声明),即是将类的声明放在头文件中。
.cpp源文件中完成类的其它实现(成员函数的具体实现),即是将成员函数的定义放在源文件中。
成员变量大都以m_开头,这是约定成俗的写法,不是语法规定的内容。以m_开头既可以一眼看出这是成员变量,又可以和成员函数中的形参名字区分开,如下所示:

Student stu;
stu.m_name = "小明";
stu.m_age = 15;
stu.m_score = 92.5f;
stu.show();

二、 对象的构造
1、类成员变量赋值和读取成员变量的值
将成员变量都声明为 private,如何给它们赋值呢,又如何读取它们的值呢?
我们可以额外添加两个 public 属性的成员函数,一个用来设置成员变量的值,一个用来修改成员变量的值。例如,setname()函数就用来设置成员变量的值;如果希望获取成员变量的值,可以再添加函数 getname()。
给成员变量赋值的函数通常称为 set 函数,它的名字通常以set开头,后跟成员变量的名字;读取成员变量的值的函数通常称为 get 函数,它的名字通常以get开头,后跟成员变量的名字。
除了 set 函数和 get 函数,在创建对象时还可以调用构造函数来初始化各个成员变量。不过构造函数只能给成员变量赋值一次,以后再修改还得借助 set 函数。
这种将成员变量声明为 private、将部分成员函数声明为 public 的做法体现了类的封装性。
2、对象的初始化
从程序设计的角度,对象只是变量,因此:
在栈上创建对象时,成员变量初始值为随机值。
在堆上创建对象时,成员变量初始值为随机值。
在静态存储区创建对象时,成员变量初始值为0值。
3、构造函数(Constructor)
C++中可以定义与类名相同的特殊成员函数,这种特殊的成员函数叫做构造函数。
构造函数的特点:没有任何返回类型的声明,在对象定义时自动被调用。
构造函数必须是 public 属性的,否则创建对象时无法调用。当然,设置为 private、protected 属性也不会报错,但是没有意义。
构造函数没有返回值,因为没有变量来接收返回值,即使有也毫无用处,这意味着:
• 不管是声明还是定义,函数名前面都不能出现返回值类型,即使是 void 也不允许;
• 函数体中不能有 return 语句。
4、构造函数的重载(有参构造函数)
和普通成员函数一样,构造函数是允许重载的。一个类可以有多个重载的构造函数,创建对象时根据传递的实参来判断调用哪一个构造函数。
例如:

class Test
{
Public:
Test(int v)
{
// use v to initialize member
}
};

构造函数的调用是强制性的,一旦在类中定义了构造函数,那么创建对象时就一定要调用(自动调用构造函数),不调用是错误的。如果有多个重载的构造函数,那么创建对象时提供的实参必须和其中的一个构造函数匹配;反过来说,创建对象时只有一个构造函数会被调用。
例如:

class Test
{
public:
    Test() {  }
    Test(int v) {  }
};
int main()
{
    Test t;         // 调用 Test()
    Test t1(1);     // 调用 Test(int v)
    Test t2 = 1;    // 调用 Test(int v)
    return 0;
}

注意:对象定义和对象声明是不同的
对象的定义:申请对象的空间并调用构造函数。
对象的声明:告诉编译器存在这样一个对象。
例如:

Test t;   // 定义对象并调用构造函数
int main()
{
    extern Test t;  // 告诉编译器存在名为t的Test对象
    return 0;
}

5、特殊的构造函数
两个特殊的构造函数:无参构造函数(默认构造函数)和拷贝构造函数。
无参构造函数(默认构造函数):当类中没有定义构造函数时,编译器默认提供一个无参构造函数,并且其函数体为空。
拷贝构造函数:当类中没有定义拷贝构造函数时,编译器默认提供一个拷贝构造函数,简单的进行成员变量的值的复制。参数为const class_name&的构造函数。
最后需要注意的一点是,调用没有参数的构造函数也可以省略括号。例如,在栈上创建对象可以写作Student stu()或Student stu,在堆上创建对象可以写作Student *pstu = new Student()或Student *pstu = new Student,它们都会调用构造函数 Student()。
6、 浅拷贝和深拷贝
拷贝构造函数的意义是:兼容C语言的初始化方式和初始化行为能够符合预期的逻辑。
浅拷贝:拷贝后对象的物理状态相同。
深拷贝:拷贝后对象的逻辑状态相同
编译器提供的拷贝构造函数只进行浅拷贝!
什么时候需要进行深拷贝?在对象只有成员指代了系统中的资源:a,成员指向了动态内存空间。b,成员打开了外存中的文件。c,成员使用了系统中的网络端口。……
一般性原则:自定义拷贝构造函数,必然需要实现深拷贝!!!
7、C++构造函数的参数初始化表
类成员的初始化:C++中提供了初始化列表对成员变量进行初始化。
语法规则:

ClassName::ClassName( ) : m1(v1), m2(v1,v2), m3(v3)
{
    // some other initialize operation
}

注意事项:
A,成员的初始化顺序与成员的声明顺序相同。
B,成员的初始化顺序初始化列表中的位置无关。
C,初始化列表先于构造函数的函数体执行。
类中的const成员:
A,类中的const成员会被分配空间的。
B,类中的const成员的本质是只读变量。
C,类中的consd成员只能在初始化列表中指定初始值。
编译器无法直接得到const成员的初始值,因为无法进入符号表成为真正意义上的常量。
注意:初始化与赋值是不同的,初始化是对正在创建的对象进行初始值设置,会自动调用构造函数。
而赋值是对已经存在的对象进行值设置,不会调用构造函数。
8、 对象的构造顺序
对于局部对象:当程序执行流到达对象的定义语句时进行构造。
对于堆对象:当程序执行流到达new语句时创建对象,使用用new创建对象将自动触发构造函数的调用。
对于全局对象:对象的构造顺序是不确定的,不同的编译器使用不同的规则确定构造顺序。
三、C++析构函数(Destructor)
创建对象时系统会自动调用构造函数进行初始化工作,同样,销毁对象时系统也会自动调用一个函数来进行清理工作,例如释放分配的内存、关闭打开的文件等,这个函数就是析构函数。析构函数的功能与构造函数相反。
析构函数(Destructor)也是一种特殊的成员函数,没有返回值,不需要程序员显式调用(程序员也没法显式调用),而是在销毁对象时自动被调用。构造函数的名字和类名相同,而析构函数的名字是在类名前面加一个~符号。定义:~ClassName()
析构函数的定义准则:当类中自定义了构造函数,并且构造函数中使用了系统资源(如:内存申请,文件打开,等),则需要自定义析构函数。
注意:析构函数没有参数,不能被重载,因此一个类只能有一个析构函数。如果用户没有定义,编译器会自动生成一个默认的析构函数。
四、 C++ this指针详解
this 是 C++ 中的一个关键字,也是一个 const 指针,它指向当前对象,通过它可以访问当前对象的所有成员。
所谓当前对象,是指正在使用的对象。例如对于stu.show();,stu 就是当前对象,this 就指向 stu。
下面是使用 this 的一个完整示例:

 #include <iostream>
 using namespace std;
 class Student{
 public:
     void setname(char *name);
     void setage(int age);
     void setscore(float score);
     void show();
 private:
     char *name;
     int age;
     float score;
 };
 void Student::setname(char *name){
     this->name = name;
 }
 void Student::setage(int age){
     this->age = age;
 }
 void Student::setscore(float score){
     this->score = score;
 }
 void Student::show(){
     cout<<this->name<<"的年龄是"<<this->age<<",成绩是"<<this->score<<endl;
 }
 int main(){
     Student *pstu = new Student;
     pstu -> setname("李华");
     pstu -> setage(16);
     pstu -> setscore(96.5);
     pstu -> show();
     return 0;
 }

this 只能用在类的内部,通过 this 可以访问类的所有成员,包括 private、protected、public 属性的。
本例中成员函数的参数和成员变量重名,只能通过 this 区分。以成员函数setname(char *name)为例,它的形参是name,和成员变量name重名,如果写作name = name;这样的语句,就是给形参name赋值,而不是给成员变量name赋值。而写作this -> name = name;后,=左边的name就是成员变量,右边的name就是形参,一目了然。
注意,this 是一个指针,要用->来访问成员变量或成员函数。
this 虽然用在类的内部,但是只有在对象被创建以后才会给 this 赋值,并且这个赋值的过程是编译器自动完成的,不需要用户干预,用户也不能显式地给 this 赋值。本例中,this 的值和 pstu 的值是相同的。
我们不妨来证明一下,给 Student 类添加一个成员函数printThis(),专门用来输出 this 的值,如下所示:

void Student::printThis(){
   cout<<this<<endl;
}

然后在 main() 函数中创建对象并调用 printThis():

 Student *pstu1 = new Student;
 pstu1 -> printThis();
 cout<<pstu1<<endl;
 Student *pstu2 = new Student;
 pstu2 -> printThis();
 cout<<pstu2<<endl;

可以发现,this 确实指向了当前对象,而且对于不同的对象,this 的值也不一样。
几点注意:
this 是 const 指针,它的值是不能被修改的,一切企图修改该指针的操作,如赋值、递增、递减等都是不允许的。
this 只能在成员函数内部使用,用在其他地方没有意义,也是非法的。
只有当对象被创建后 this 才有意义,因此不能在 static 成员函数中使用。
this 到底是什么?
this 实际上是成员函数的一个形参,在调用成员函数时将对象的地址作为实参传递给 this。不过 this 这个形参是隐式的,它并不出现在代码中,而是在编译阶段由编译器默默地将它添加到参数列表中。
this 作为隐式形参,本质上是成员函数的局部变量,所以只能用在成员函数的内部,并且只有在通过对象调用成员函数时才给 this 赋值。
成员函数最终被编译成与对象无关的普通函数,除了成员变量,会丢失所有信息,所以编译时要在成员函数中添加一个额外的参数,把当前对象的首地址传入,以此来关联成员函数和成员变量。这个额外的参数,实际上就是 this,它是成员函数和成员变量关联的桥梁。
五、 C++ 类的static静态成员变量
静态成员变量属于整个类所有。
静态成员变量的生命期不依赖于任何对象。
可以通过类名直接访问public静态成员变量。
所有对象共享类的静态成员变量。
可以通过对象名访问public静态成员变量。
静态成员变量的特性:
A,在定义时直接通过static关键字修饰。
B,静态成员变量需要在类外单独分配空间。
C,静态成员变量在程序内部位于全局数据区。
语法规则:static 成员变量必须在类声明的外部初始化,具体形式为:

Type ClassName::VarName = value;

type 是变量的类型,class 是类名,name 是变量名,value 是初始值。
在C++中,我们可以使用静态成员变量来实现多个对象共享数据的目标。静态成员变量是一种特殊的成员变量,它被关键字static修饰,例如:

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,但必须要有数据类型。被 private、protected、public 修饰的静态成员变量都可以用这种方式初始化。
注意:static 成员变量的内存既不是在声明类时分配,也不是在创建对象时分配,而是在(类外)初始化时分配。反过来说,没有在类外初始化的 static 成员变量不能使用。
static 成员变量既可以通过对象来访问,也可以通过类来访问。请看下面的例子:

//通过类类访问 static 成员变量
 Student::m_total = 10;
 //通过对象来访问 static 成员变量
 Student stu("小明", 15, 92.5f);
 stu.m_total = 20;
 //通过对象指针来访问 static 成员变量
 Student *pstu = new Student("李华", 16, 96);
 pstu -> m_total = 20;

这三种方式是等效的。
注意:static 成员变量不占用对象的内存,而是在所有对象之外开辟内存,即使不创建对象也可以访问。具体来说,static 成员变量和普通的 static 变量类似,都在内存分区中的全局数据区分配内存。
几点说明
1) 一个类中可以有一个或多个静态成员变量,所有的对象都共享这些静态成员变量,都可以引用它。
2) static 成员变量和普通 static 变量一样,都在内存分区中的全局数据区分配内存,到程序结束时才释放。这就意味着,static 成员变量不随对象的创建而分配内存,也不随对象的销毁而释放内存。而普通成员变量在对象创建时分配内存,在对象销毁时释放内存。
3) 静态成员变量必须初始化,而且只能在类体外进行。例如:

int Student::m_total = 10;

初始化时可以赋初值,也可以不赋值。如果不赋值,那么会被默认初始化为 0。全局数据区的变量都有默认的初始值 0,而动态数据区(堆区、栈区)变量的默认值是不确定的,一般认为是垃圾值。
4) 静态成员变量既可以通过对象名访问,也可以通过类名访问,但要遵循 private、protected 和 public 关键字的访问权限限制。当通过对象名访问时,对于不同的对象,访问的是同一份内存。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值