C++面向对象编程入门:类(class)

  上两篇内容我们着重说了结构体相关知识的操作。

  以后的内容我们将逐步完全以c++作为主体了,这也意味着我们的教程正式进入面向对象的编程了。

  前面的教程我已经再三说明,结构体的掌握非常重要,重要在哪里呢?重要在结构体和类有相同的特性,但又有很大的区别,类是构成面向对象编程的基础,但它是和结构体有着极其密切的关系。

  我们在c语言中创建一个结构体我们使用如下方法:

struct test 

    private: 
        int number; 

    public: 
        float socre; 
};

  类的创建方式和结构体几乎一样,看如下的代码:

class test 

    private: 
        int number; 

    public: 
        float socre; 
    public: 

        int rp() 
        { 

            return number; 
        } 

        void setnum(int a) 

        { 
            number=a; 
        } 
};

  但是大家注意到没有,标准c中是不允许在结构体中声明函数的,但c++中的类可以,这一点就和c有了本质的区别,很好的体现了c++面向对象的特点!

  过去的c语言是一种非面向对象的语言

  他的特性是:

  程序=算法+数据结构

  但c++的特性是

  对象=算法+数据结构

  程序=对象+对象+对象+对象+........

  所以根据这一特性,我们在定义一个自己定义的结构体变量的时候。这个变量就应该是叫做对象或者叫实例。

  例如

test a;

  那么a就是test结构的一个对象(实例)

  test结构体内的成员可以叫做是分量,例如:

a.socre=10.1f;

  那么number就是test结构的对象a的分量(或者叫数据成员,或者叫属性)score;

  在c语言中结构体中的各成员他们的默认存储控制是public 而
c++中类的默认存储控制是private,所以在类中的成员如果需要外部掉用一定要加上关键字public声明成公有类型,这一特性同样使用于类中的成员函数,函数的操作方式和普通函数差别并不大。

  例如上面的例子中的rp()成员函数,我们如果有如下定义:

test a;

  的话,调用rp()就应该写成:

a.rp();

  成员函数的调用和普通成员变量的调用方式一致都采用.的操作符。

  这一小节为了巩固联系我给出一个完整的例子。

  如下(重要和特殊的地方都有详细的注解):

#include <iostream> 
using namespace std; 
class test 

    private://私有成员类外不能够直接访问 

        int number; 
    public://共有成员类外能够直接访问 

        float socre; 
    public: 
        int rp() 

        { 
            return number; 

        } 
        void setnum(int a) 
        { 
            number=a; 

        } 
}; 
 
void main() 

    test a; 
    //a.number=10;//错误的,私有成员不能外部访问 
    a.socre=99.9f; 

    cout<<a.socre<<endl;//公有成员可以外部访问 
    a.setnum(100);//通过公有成员函数setnum()间接对私有成员number进行赋值操作 
    cout<<a.rp();//间接返回私有成员number的值 
    cin.get(); 
}

  好了,介绍了在类内部定义成员函数(方法)的方法,下面我们要介绍一下域区分符(::)的作用了。

  下面我们来看一个例子,利用这个例子中我们要说明两个重要问题:

#include <iostream> 
using namespace std; 
int pp=0; 
class test 


    private: 
        int number; 
    public: 

        float socre; 
        int pp; 
    public: 

        void rp(); 
}; 
void test::rp()//在外部利用域区分符定义test类的成员函数 

    ::pp=11;//变量名前加域区分符给全局变量pp赋值 
    pp=100;//设置结构体变量 

 
void main() 

    test a; 

         test b; 
    a.rp(); 
    cout<<pp<<endl; 
    cout<<a.pp<<endl; 
     
    cin.get(); 
}

  问题1:

  利用域区分符我们可以在类定义的外部设置成员函数,但要注意的是,在类的内部必须预先声明:

void test::rp()

  在函数类型的后面加上类的名称再加上域区分符(::)再加函数名称,利用这样的方法我们就在类的外部建立了一个名为rp的test类大成员函数(方法),可能很多人要问,这么做有意义吗?在类的内部写函数代码不是更好?

  答案是这样的:在类的定义中,一般成员函数的规模一般都比较小,而且一些特殊的语句是不能够使用的,而且一般会被自动的设置成为inline(内联)函数,即使你没有明确的声明为inline,那么为什么有会被自动设置成为inline呢?因为大多数情况下,类的定义一般是放在头文件中的,在编译的时候这些函数的定义也随之进入头文件,这样就会导致被多次编译,如果是inline的情况,函数定义在调用处扩展,就避免了重复编译的问题,而且把大量的成员函数都放在类中使用起来也十分不方便,为了避免这种情况的发生,所以c++是允许在外部定义类的成员函数(方法)的,将类定义和其它成员函数定义分开,是面向对象编程的通常做法,我们把类的定义在这里也就是头文件了看作是类的外部接口,类的成员函数的定义看成是类的内部实现。写程序的时候只需要外部接口也就是头文件即可,这一特点和我们使用标准库函数的道理是一致的,因为在类的定义中,已经包含了成员函数(方法)的声明。

  问题二

  域区分符和外部全局变量和类成员变量之间的关系。

  在上面的代码中我们看到了,外部全局和类内部都有一个叫做pp的整形变量,那么我们要区分操作他们用什么方法呢?

  使用域区分符就可以做到这一点,在上面的代码中::pp=11;操作的就是外部的同名称全局变量,pp=100;操作的就是类内部的成员变量,这一点十分重要!

  问题三

  一个类的所有对象调用的都是同一段代码,那么操作成员变量的时候计算机有是如何知道哪个成员是属于哪个对象的呢?

  这里牵扯到一个隐藏的this指针的问题,上面的代码在调用a.rp()的的时候,系统自动传递一了个a对象的指针给函数,在内部的时候pp=100;的时候其实就是this->pp=100;

  所以你把上面的成员函数写成如下形势也是正确的:

void test::rp() 


    ::pp=11; 
    this->pp=100;//this指针就是指向a对象的指针 
}

  类的成员函数和普通函数一样是可以进行重载操作的,关于重载函数前面已经说过,这里不再说明。

  给出例子仔细看:

#include <iostream>   
using namespace std;   
class test   
{   
    private:   
        int number;   

    public:   
        float socre;   
        int pp;   

    public:   
        void rp(int); 
        void rp(float); 
};   
void test::rp(int a)//在外部利用域区分符定义test类的成员函数   
{   
    cout<<"调用成员函数!a:"<<a<<endl; 
}   

void test::rp(float a)//在外部利用域区分符定义test类的成员函数   
{   
    cout<<"调用成员函数!a:"<<a<<endl; 


void main()   
{   

    test a; 
    a.rp(100); 
    a.rp(3.14f); 
    cin.get();   
}

  下面我们来看一下利用指针和利用引用间接调用类的成员函数,对于对于指针和引用调用成员函数和调用普通函数差别不大,在这里也就不再重复说明了,注意看代码,多试多练习既可。

  代码如下:

#include <iostream>   
using namespace std;   
class test   
{   
    private:   
        int number;   

    public:   
        float socre;   
        int pp;   

    public:   
        int rp(int); 
};   
int test::rp(int a)//在外部利用域区分符定义test类的成员函数   
{   
    number=100; 

    return a + number; 


 
void run(test *p)//利用指针调用 

    cout<<p->rp(100)<<endl; 

void run(test &p)//利用引用 


    cout<<p.rp(200)<<endl; 


 
void main()   

{   
    test a; 
    run(&a); 
    run(a); 
    cin.get();   
}

  前面我们说过,类的成员如果不显式的生命为public那么它默认的就是private就是私有的,私有声明可以保护成员不能够被外部访问,但在c++还有一个修饰符,它具有和private相似的性能,它就是protected修饰符。

  在这里我们简单说明一下,他们三着之间的差别:

protected: 
        const static int gbs = 5;  //好球单位得分 
        const static int bbs = -3;  //坏球单位扣分 
        float gradescore;  //平均成绩 

    public: 
        float GetGS(float goodball,float badball)  //goodball为好球数量,badball为坏求数量 
        { 

            gradescore = (goodball*gbs + badball*bbs) / (goodball + badball); 

            return gradescore;  //返回平均成绩 
        } 
};

  主函数调用:

#include <iostream>   

#include "ballscore.h" 
using namespace std;   
 
void main()   
{   
    ballscore jeff; 
    cout<<jeff.GetGS(10,3); 

    jeff.gradescore=5.5//想篡改jeff的平均成绩是错误的! 

    cin.get(); 
}

  在上面的代码中头文件和类的使用很好了体现了类的黑盒特性,谁也不能够在外部修改球员的平均成绩!

  类体中的有一个地方要注意

const static int gbs = 5;//好球单位得分
const static int bbs =
-3;//坏球单位扣分

  之所以要修饰成const static
因为c++中类成员只有静态整形的常量才能够被初始化,到这里整个程序也就说完了,当然真正大比赛不可能是这样,只是为了说明问题就题命题而已,呵呵!

  下面我们说一下关于iostream>   
using namespace std;   
class ballscore 
{   
    protected: 
        const static int gbs = 5;//好球单位得分 
        const static int bbs = -3;//坏球单位扣分 
        float gradescore;//平均成绩 

    public: 
        float GetGS(float goodball,float badball)  //goodball为好球数量,badball为坏求数量 
        { 

            int gradescore=0; 
   //新定义一个和成员变量float gradescore相同名字的类成员函数局部变量 

            ballscore::gradescore = (goodball*gbs + badball*bbs) /
            (goodball + badball);  //由于局部变量与类成员变量同名使用的时候必须在其前加上类名和域区分符 
            return ballscore::gradescore;//返回平均成绩 
        } 
}; 
int ballscore=0;//定义一个与类名称相同的普通全局变量 

int test; 
void main() 

    class test//局部类的创建 
    { 
        float a; 
        float b; 

    }; 
    test test; 
    ::test=1;  //由于局部类名隐藏了外部变量使用需加域区分符 
    class ballscore jeff;  //由于全局变量int ballsocre和类(ballsocre)名称相同,隐藏了类名称,这时候定义类对象需加class前缀以区分 

    cout<<jeff.GetGS(10,3); 
    cin.get(); 
}

  类的作用域是只指定义和相应的成员函数定义的范围,在该范围内,一个类的成员函数对同一类的数据成员具有无限制的访问权。

  在类的使用中,我们经常会碰到以下三种情况:

  1.类的成员函数的局部变量隐藏了类的同名成员变量,看如对上面程序的分析。

protected: 
        const static int gbs = 5; 
        const static int bbs = -3; 
        float gradescore; 
    public: 

        float GetGS(float goodball,float badball) 

        { 
            int gradescore=0; 

            ballscore::gradescore = (goodball*gbs + badball*bbs) /
           (goodball + badball); 
            return ballscore::gradescore; 
        }

  代码中的int gradescore就把float gradescore给隐藏了,所以要使用成员变量float
gradescore的时候必须在其之前加上类名称和域区分符(::)。

  2.在类定义外部非类型名隐藏了类型名称的情况,看上面代码的分析!

class ballscore
{
    protected: 

        const static int gbs = 5; 
        const static int bbs = -3; 
        float gradescore; 
    public: 

        float GetGS(float goodball,float badball) 

        { 
            int gradescore=0; 

            ballscore::gradescore = (goodball*gbs + badball*bbs) / 
            (goodball + badball); 
            return ballscore::gradescore; 
        }
};
int
ballscore=0;

  代码中的全部变量int ballscore隐藏了类名称class ballscore

  所以在main中如如果要定义ballscore类的对象就要在类名称前加上class关键字

class ballscore jeff;

  3.类型名称隐藏了非类型名称,看对上面代码的分析

int test; 
void main() 

    class test 
    { 
        float a; 
        float b; 

    }; 
    test test; 
    ::test=1; 
    class ballscore jeff; 
    cout<<jeff.GetGS(10,3); 
    cin.get(); 
}

  在普通函数内部定义的类叫做局部类,代码中的test类就是一个局部类!

  代码中的test类隐藏了全局变量test如果要操作全局变量test那么就要在test前加上域区分符号(::),进行使用!

  ::test=1就是对全局变量test进行了赋值操作。

  我们最后说一下名字空间!

  名字空间就是指某一个名字在其中必须是唯一的作用域.

  如果这个定义想不明白,可以简单的说成,在一个区域内,某一个名字在里面使用必须是唯一的,不能出现重复定义的情况出现,这个区域就是名字空间!

  c++规定:

  1.一个名字不能同时设置为两种不同的类型

class test
{
//...
};
typedef int test;

这个就是错误的!

  2.非类型名(变量名,常量名,函数名,对象名,枚举成员)不能重名.

test a;
void a();

  就是错误的,因为a是一个test类的对象,它和函数a名称重名了!

  3.类型与非类型不在同一个名字空间上,可以重名,即使在同一作用域内,但两者同时出现时定义类对象的时候要加上前缀class以区分类型和非类型名!

class test
{
//.....
}

int test

class
test a;//利用class前坠区分,定义了一个test类的对象a

  好了,到这里关于类的知识点我们已经学习完,希望大家多多练习

 
 

《挑战30天C++入门极限》C++面向对象编程入门:类(class)的更多相关文章

  1. 《挑战30天C++入门极限》C++面向对象编程入门:构造函数与析构函数

        C++面向对象编程入门:构造函数与析构函数 请注意,这一节内容是c++的重点,要特别注意! 我们先说一下什么是构造函数. 上一个教程我们简单说了关于类的一些基本内容,对于类对象成员的初始化我们 ...

  2. C++ Primer 学习笔记_72_面向对象编程 --句柄类与继承[续]

    面向对象编程 --句柄类与继承[续] 三.句柄的使用 使用Sales_item对象能够更easy地编写书店应用程序.代码将不必管理Item_base对象的指针,但仍然能够获得通过Sales_item对 ...

  3. 《Java从入门到放弃》JavaSE入门篇:面向对象概念(入门版)

    要知道什么是面向对象,你首先要有个对象吧,所以······没有对象的可以回家洗洗睡了· 好吧,前面是开玩笑,要说明什么是面向对象,我们还是先 例子: 小呆:"小傻,你今天早餐吃的什么?&qu ...

  4. day24:面向对象设计与面向对象编程、类和对象

    一.三大编程范式: 面向过程: 面向函数: 面向对象: 二.程序的进化论: 1.编程最开始就是无组织无结构,从简单控制流中按步写指令 2.从上述的指令中提取重复的代码块或逻辑,组织到一起(比方说,你定 ...

  5. java面向对象编程(类、对象)

    一.面向对象编程概述面向对象编程(Object  Oriented  Programming,缩写为OOP)是当今最流行的程序设计技术,它具有代码易于维护.可扩展性好和代码可常用等优点.面向对象的设计 ...

  6. 1.面向过程编程 2.面向对象编程 3.类和对象 4.python 创建类和对象 如何使用对象 5.属性的查找顺序 6.初始化函数 7.绑定方法 与非绑定方法

    1.面向过程编程 面向过程:一种编程思想在编写代码时 要时刻想着过程这个两个字过程指的是什么? 解决问题的步骤 流程,即第一步干什么 第二步干什么,其目的是将一个复杂的问题,拆分为若干的小的问题,按照 ...

  7. Python面向对象编程、类

    一.面向对象编程 面向对象--Object Oriented Programming,简称oop,是一种程序设计思想.在说面向对象之前,先说一下什么是编程范式,编程范式你按照什么方式来去编程,去实现一 ...

  8. python学习笔记(七):面向对象编程、类

    一.面向对象编程 面向对象--Object Oriented Programming,简称oop,是一种程序设计思想.在说面向对象之前,先说一下什么是编程范式,编程范式你按照什么方式来去编程,去实现一 ...

  9. python自动化测试学习笔记-7面向对象编程,类,继承,实例变量,邮件

    面向对象编程(OOP)术语: class TestClass(object):   val1 = 100       def __init__(self):     self.val2 = 200   ...

随机推荐

  1. Python之TensorFlow的(案例)验证码识别-6

    一.这里的案例相对比较简单,主要就是通过学习验证码的识别来认识深度学习中我们一般在工作中,需要处理的东西会存在哪些东西. 二.因为我没有数据集,没有关系,这里自己写了一个数据集,来做测试,为了方便我把 ...

  2. (七) Docker 部署 MySql8.0 一主一从 高可用集群

    参考并感谢 官方文档 https://hub.docker.com/_/mysql y0ngb1n https://www.jianshu.com/p/0439206e1f28 vito0319 ht ...

  3. NEST 多IndexType与分页

    /// <summary> /// POST /_all/employee/_search?typed_keys=true /// </summary> public void ...

  4. 易百教程人工智能python修正-人工智能数据准备-预处理数据

    预处理数据 在我们的日常生活中,需要处理大量数据,但这些数据是原始数据. 为了提供数据作为机器学习算法的输入,需要将其转换为有意义的数据. 这就是数据预处理进入图像的地方. 换言之,可以说在将数据提供 ...

  5. 【转载】C#中通过Distinct方法对List集合进行去重

    在C#的List集合对象中,可以使用Distinct方法来对List集合元素进行去重,如果list集合内部元素为值类型,则Distinct方法根据值类型是否相等来判断去重,如果List集合内部元素为引 ...

  6. element-ui DatePicker 日期格式处理

    1.使用DatePicker 日期选择器得到的日期格式是这样的 解决方案,添加 value-format="yyyy-MM-dd" <el-date-picker type= ...

  7. js原型,原型链

    先铺垫下原型规则: 1.所有的引用类型(数组,对象,函数)都具有对象特性,可自由扩展属性(出了null外) 2.所有的引用类型(数组,对象,函数)都有一个__proto__属性(隐式原型),属性值是一 ...

  8. React Native 开发豆瓣评分(三)集成 Redux

    什么是 redux redux 是一个用于管理 js 应用状态(state)的容器.比如组件 A 发生了变化,组件 B 要同时做出响应.常见的应用场景就是用户的登录退出操作:未登录状态,个人中心显示登 ...

  9. 如何在backoffice里创建Hybris image container以及分配给product

    登录backoffice,在media container视图点击新建按钮: Catalog选择Product Catalog: 在Properties界面,可以选择media实例放入该contain ...

  10. 爬虫之cookie与代理

    一, 基于requests模块的cookie操作 引言:有些时候,我们在使用爬虫程序去爬取一些用户相关信息的数据(爬取张三“人人网”个人主页数据)时,如果使用之前requests模块常规操作时,往往达 ...