Chinaunix首页 | 论坛 | 博客
  • 博客访问: 82363
  • 博文数量: 21
  • 博客积分: 371
  • 博客等级: 一等列兵
  • 技术积分: 225
  • 用 户 组: 普通用户
  • 注册时间: 2012-11-15 21:32
文章分类

全部博文(21)

文章存档

2013年(5)

2012年(16)

我的朋友

分类: C/C++

2012-12-14 23:41:06

1.       在类内部定义的函数默认为inline,另外将const加在形参表之后,此时的函数不能改变其所操作的对象的数据成员.并且在声明和定义处都要加上const.

2.       成员的默认访问属性取决于类是通过何种方式定义的,struct定义的类其成员都是公共的,Class定义的类其成员默认是私有的

3.       数据抽象和封装提供了两个重要优点:

◆避免类内部出现无意的,可能破坏对象状态的用户级错误

◆随时间推移可以根据需要改变或缺陷报告来完善类实现,而无须改变用户级代码

         改变头文件中的类定义可有效改变包含该头文件的每个源文件的程序文本,因此,当类发生改变时,使用该类的代码必须重新编译.

4.       像其他inline一样,inline成员函数的定义必须在调用该函数的每一个源文件中是可见的.不在类定义体内定义的inline成员函数,其定义通常应放在有类定义的同一头文件中.

5.       定义对象时,将为其分配存储空间,(一般而言)定义类型时不进行存储分配.

6.       在普通的非const成员函数中,this的类型是一个指向类类型的const指针,可以改变this所指向的值,但是不能改变this所保存的地址,不能从const成员函数返回指向类对象的普通引用.const成员函数只能返回*this作为一个const引用.

7.       有时我们希望类的数据成员(甚至在const成员函数内)可以修改.这可以通过将他们声明为mutable成员,必须在成员声明之前使用.

8.       可以使用类型别名来简化类(例如typedef std::string::size_type index;是使用index来表示std::string::size_type这种类型.在其类外部定义的形参中如果用到index可以不必限定(即不需使用Class::index来表示),但如果是返回值时,则必须要加限定(即使用Class::index)

9.        类定义实际上是在两个阶段中处理

1).首先编译成员声明

2).只有在所有成员出现之后才编译他们的定义本身

10.使用全局变量可以使用::来约定.(MFC,当使用全局的函数,要用::来做约定)

11.如果没有为类成员提供初始化式,则编译器会隐式地使用成员类型的默认构造函数.如果那个类没有默认的构造函数,则编译器尝试使用默认构造函数将会失败.

         可以初始化const对象或引用类型的对象,但不能对它们赋值.在开始执行构造函数的函数体之前,要完成初始化.初始化const或者引用类型数据成员的唯一机会是在构造函数初始化列表中.

         另外,当成员需要使用初始化列表时,通常常规的使用构造函数初始化列表就可避免发生编译时错误.

12.构造函数初始化列表仅指定用于初始化成员的值,并不指定这些初始化执行的次序.成员被初始化的次序就是定义成员的次序.

13.一个类哪怕只定义了一个构造函数,编译器也不会再生成默认构造函数.对于指针和数组,只有定义在全局作用域总的对象才初始化.当对象定义在局部作用域中时,内置或复合类型的成员不进行初始化.

         实际上,如果定义了其他构造函数,则提供一个默认的构造函数几乎总是对的.通常在默认构造函数中给成员提供的初始值应该指出该对象是空的.

可以将构造函数声明为explicit,来防止在需要隐式转换的上下文中使用构造函数

14.友元声明可以出现在类中的任何地方:友元不是授予友元关系的那个类的成员,所以他们不受其声明出现部分的访问控制影响.通常,将友元声明成组的放在类定义的开始或者结尾是个好主意.

15.用友元引入的类名和函数(定义或声明),可以像预先声明的一样使用

16.static和非static成员函数的区别是:static成员函数没有this指针.static成员函数也不被声明为虚函数.

static数据成员必须在类定义体的外部定义(正好一次).另外不像普通数据成员,static成员不是通过类构造函数进行初始化,而是应该定义时进行初始化.



简单地说,类就是定义了一个新的类型和一个新作用域。

 

12.1 类的定义和声明

    类由类成员组成。类成员包括属性,字段,成员函数,构造函数,析构函数等组成。

 

    类设计应该遵从抽象封装性。

    类抽象性指对于类的使用者来说只需知道类接口即可使用类功能。类的具体实现由设计者负责。即使某个功能发生了变更但由于使用者是以接口方式调用类所以用户代码无需做任何修改。

    类封装性指类用户只需知道类的功能无需了解具体实现。实现代码对用户来说不可见。

 

    C++类没有访问级别限限制,定义类时不能用public 或 private 做修饰。类成员有访问级别,可以定义 public protect private



点击(此处)折叠或打开

  1. class Screen
  2. {
  3.     public:
  4.       // 类成员只能声明不允许定义 替换成string name("tom") 会产生编译错误, 数据成员初始化工作在构造函数中执行
  5.       string name;

  6.       // 给类定义别名类型成员 index 由于别名要在外部访问所以一定要定义在 public
  7.       typedef std::string::size_type index;

  8.       // 内部定义的函数,等价于inline
  9.       char get() const { return contents[cursor]; }

  10.       // 内部声明一个成员函数(无定义),且函数是内联的inline表示在编译时该声明会被替换成定义语句
  11.       inline char get(index ht, index wd) const;

  12.       // 内部声明一个成员函数(无定义)
  13.       index get_cursor() const;
  14.       
  15.       // ...
  16. };

  17. // 定义类 Screen 的成员函数 get 具体实现
  18. char Screen::get(index r, index c) const
  19. {
  20.      index row = r * width; // compute the row location
  21.      return contents[row + c]; // offset by c to fetch specified character
  22. }

  23. // 定义类 Screen 的成员函数 get_cursor 具体实现,且是内联的
  24. inline Screen::index Screen::get_cursor() const
  25. {
  26.      return cursor;
  27. }
注意:类的inline修饰符可以放在类内部申明也可以放在外部定义。一般放在内部声明便于理解。  

             类定义完毕后一定要加上封号结束符 ;

             类数据成员只允许声明不允许定义;

 

    可以声明类而不定义它。成为前向声明又叫不完全类,这样的类无法定义实例也无法使用成员。一般用来处理类相互依赖的情况。定义了类就能定义类对象:myclass obj; 一定要注意不能是 myclass obj() ; 类对象定义时会分配内存空间,每个类都有自己的空间相互间不受影响。    

 

12.2 隐含的this指针

    类对象包含一个 this 指针指向自身(当前的实例对象)且无法更改指针指向。在普通的非 const 成员函数中,this 的类型是一个指向类类型的 const 指针。可以改变 this 所指向的值,但不能改变 this 所保存的地址。在 const 成员函数中,this 的类型是一个指向 const 类类型对象的 const 指针。既不能改变 this 所指向的对象,也不能改变 this 所保存的地址。

    基于成员函数是否为 const,可以重载一个成员函数;同样地,基于一个指针形参是否指向 const可以重载一个函数。

class mycls
{
    public:
        mycls(){}; // 想要定义 const mycls a; 必须要显示定义默认构造函数
        mycls &Get(){ return *this; };
        const mycls &Get() const { return *this; }; // 想如果const函数返回this引用或指针; 必须要返回const指针或引用,因为无法用const对象(this)初始化非const对象。

};

const mycls b;
mycls b1 = c.Get(); // 调用const版Get函数

const mycls b2 = c.Get(); // 调用const版Get函数
// b1 b2 定义时会调用类的拷贝函数。b1,b2是Get返回值的副本,b1还会将常量副本转变成变量


mycls &b3 = c.Get(); // 错误,不能用 const &mycls 初始化 &mycls (指针或者引用类型不能用常量初始化变量)
const mycls &b4 = c.Get();


mycls a;
mycls a1 = a.Get(); // 调用非const版Get函数

const mycls a2 = a.Get(); // 调用非const版Get函数

    由此可见调用那个版本和调用对象是否const有关系,const对象会调用const版本,非const对象会调用非const版本

    引用网上的总结:

         成员函数具有const重载时,类的const对象将调用类的const版本成员函数,类的非const对象将调用非const版本成员函数。

         如果只有const成员函数,类的非const对象也可以调用const成员函数。                          ——这个思路来描述很囧。下同。

         如果只有非const成员函数,类的const对象…额,不能调用非const成员函数。                ——其实跟上一句的意思是一样的:const对象只能调用它的const成员函数。

         总的来说,就是当我们调用一个成员函数时,编译器会先检查函数是否有const重载,如果有,将根据对象的const属性来决定应该调用哪一个函数。如果 没有const重载,只此一家,那当然就调用这一个了。这时编译器亦要检查函数是不是没有const属性而调用函数的对象又有const属性,若如此,亦 无法通过编译。  

 

    还有一点非常重要,想要定义类的const对象必须显示定义对应构造函数,无法依赖系统自动分配的构造函数。

 

12.3 类作用域

    每个类对象独有独立的作用域。

    C++类定义一般分两部分,类成员申明(类定义内部)和类成员定义(类定义外部)。虽然成员定义在类外部但还是可以像类内部定义一样使用类所有成员。

 

12.4 构造函数

    构造函数是特殊的成员函数。在类对象定义时被调用。 不能通过定义的类对象调用构造函数,构造函数可以定义多个或者说构造函数允许重载。

    如果没有定义任何构造函数,系统就会给类分配一个无参的默认构造函数,类只要定义了一个构造函数,编译器也不会再生成默认构造函数。只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数

    

    定义类对象时不能写成 Sales_item myobj(); 编译器会理解成:一个返回 Sales_item 类型叫 myobj的函数声明。 正确写法是去掉后面的括号。

    构造函数后面不允许定义成 const,这样定义会产生语法错误: Sales_item() const {};

 

    构造函数在执行时会做类数据成员的初始化工作。从概念上讲,可以认为构造函数分两个阶段执行:(1)初始化阶段;(2)普通的计算阶段。计算阶段由构造函数函数体中的所有语句组成。

    不管成员是否在构造函数初始化列表中显式初始化,类类型的数据成员总是在初始化阶段初始化。初始化发生在计算阶段开始之前。

class mycls
{
    public:
        mycls()
        {
           age = 12; name = "tom";
         };

        mycls(int i):age(i) 
        {
           age = 12 + i; name = "tom";
         };

    private:
        int age;
        string name;
};

    mycls obj1 ;使用无参构造函数,虽然构造函数并没有显示初始化数据成员但类类型name还是会被初始化成默认值name初始化为"" age未初始化(其值是个随机数),初始化后构造函数重新赋值,最终age=12, ame = "tom" ;

    mycls obj2(4) ; 用构造函数参数初始化 age = 4, name = "",构造函数重新赋值,最终age=16, name = "tom" ;

    如果数据成员是自定义类类型,如果不显示初始化则类一定要有默认构造函数否则编译错误,成员被初始化的次序就是定义成员的次序。第一个成员首先被初始化,然后是第二个,依次类推。

    默认情况下可以用单个实参来调用的构造函数定义了从形参类型到该类类型的一个隐式转换。

class mycls
{
    public:
        int i;

        mycls(int i){ };
 
        explicit mycls(string s){ };

};

    mycls obj(2) ; 也可以这样使用这个构造函数 mycls obj = 2; 这里做了一个类型转换,但是这样的写法很不直观。
    可以通过将构造函数声明为 explicit,来防止在需要隐式转换的上下文中使用构造函数:mycls obj("tom"), 无法用 mycls obj = "tom" 因为转换被禁止,通常,除非有明显的理由想要定义隐式转换,否则,单形参构造函数应该为 explicit
    explicit 关键字只能用于类内部的构造函数声明上。在类的定义体外部所做的定义上不再重复它。

 

12.5 友元

    友元机制允许一个类将其非公有成员的访问权限授予指定的函数或类。

    将类作为自己的友元类如下定义

// 友元类
class me
{
    friend class he;
    
    private:
        int i;
        string s;
};

class he
{
    public:
        void show(me &it)
        {
            cout << it.i << it.s << endl;
        };
};

    类he是me的友元类,所以he中可以访问me的私有成员i和s;

    将类成员作为另一个类的友元函数情况比较复杂,需要用到前面讲过的前向声明(两个类之间有互相依赖关系)

// 类成员友元
class me; // 先要前向声明类

class he  // 友元类需要目标类做参数由于目标类已声明所以可以使用类引用或者指针--show(me &it)方法中的参数
{
    public:
        void show(me &it);
};

class me // 目标类需要声明类的的成员show作为自己的友元函数,he在上面做了成员声明所以成员show(me &it)可用

{
    friend void he::show(me &it);
    
    private:
        int i;
        string s;
};

void he::show(me &it) // 友元方法中使用目标类私有成员,目标类上一步定义了私有成员因此这里成员可用

{
    cout << it.i << it.s << endl;
};

    声明定义的顺序非常重要,一定要理解否则会产生各种未定义类编译错误。

 

12.6 static  成员

    类的静态成员不属于任何一个类对象所以静态成员中(主要是静态方法)不包含this指针因此也无法声明成const函数,它是所有类对象共享数据。

    不同于其他语言的访问方式,静态成员既可以通过类型访问:myclass::staticname()  也可以通过类对象(对象,指针或者引用)访问:obj.staticname()。  

    一般来说类数据成员在类定义体内不能初始化化,但有个特例 const static 数据成员就可以在类的定义体中进行初始化

    类非 static 数据成员在类体内声明,必须要在类体外定义。

class me
{    
    public:
        void show()
        {
            cout << i << j << endl;
        };

    private:
        static int i;
        const static int j = 1;
};

int me::i = 1//这一步不能少,否则编译器检查到show()方法中使用i类体外又没有定义会产生编译错误
int me::j; //这一步不能少,但只是定义不能再赋值

    需要强调const static 体内初始化但体外定义也不能少,但是如果体外不作定义在定义类对象时会产生编译异常。

 

    最后补充一点关于类成员函数的重载。函数重载不但可以用参数类型和参数个数不同来重载,还可以通过const修饰变量来实现函数重载,即函数名称、参数个 数、参数类别都一样,唯一的区别在于变量是否为const修饰。用 const做重载依据有两种类型:const参数,const函数:

点击(此处)折叠或打开

  1. class A
  2. {
  3.   public:
  4.     A() {}
  5.     void func(int *a) //相当于void func(int *a, A *this)
  6.     {
  7.         std::cout << "_func_int_ptr_" << std::endl;
  8.     }
  9.     void func(const int *a) //相当于void func(const int *a, A *this)
  10.     {
  11.         std::cout << "_func_const_int_ptr_" << std::endl;
  12.     }
  13.     void func(int *a) const //相当于void func(int *a, const A *this)
  14.     {
  15.         std::cout << "_const_func_int_ptr_" << std::endl;
  16.     }
  17.     void func(const int *a) const //相当于void func(const int *a, const A *this)
  18.     {
  19.         std::cout << "_const_func_const_int_ptr_" << std::endl;
  20.     }
  21. };
  22.  
  23. int main(int argc, char* argv[])
  24. {
  25.     A a;
  26.     int nValue = 3;
  27.     const int nValueCnst = 3;
  28.     a.func(&nValue);
  29.     a.func(&nValueCnst);
  30.  
  31.     const A aa;
  32.     aa.func(&nValue);
  33.     aa.func(&nValueCnst);
  34.     return 0;
  35. }
  36.  
  37. 其输出为:
  38. _func_int_ptr_
  39. _func_const_int_ptr_
  40. _const_func_int_ptr_
  41. _const_func_const_int_ptr_


    从这里可以看出,通过const修饰一个变量可以实现同名称函数的重载。另外,一个类的非const对象可以调用其const函数(如果只定义了 const函数版本,非const对象就可以调用const成员函数)。但const 对象无法调用非  const 函数(非const函数可能会修改 this 而 this 是 const对象,有潜在BUG)。

 

    总结起来,可以初始化的情况有如下几个地方:
    1. 类型为const 且 static 的整型变量可以在定义时直接初始化值(只能用赋值初始化不能用直接初始化) 也可以在体外。


    2. 普通const常量(不包含第一种情况)必须要在构造函数初始化列表中初始化值。


    3. 只要有static修饰,必须要在类定义体外定义并给值(第一种情况时也需要这么做,不过只能定义不能再给值) static数据不属于任何对象所以不能出现在构造函数初始化列表。


    4. 普通的变量可以在构造函数的内部,通过赋值方式进行。当然这样效率不高。

    5. 数组成员不能在初始化列表里初始化的。只能自动调用数组的无参构造函数(可以在构造函数内操作数组)。  

点击(此处)折叠或打开

  1. class obj
  2. {
  3. public:
  4. int a;
  5. const int b;
  6. static int c;
  7. static const d = 1; // 体内定义时不能用直接初始化给值
  8. static const e;

  9. obj():a(0),b(0){};
  10. };

  11. int obj::c = 2 ; // 体外定义(不能出现statci关键字)
  12. const int obj::d ; // 体外定义,d已经在体内给只所以只需定义不能给值(const 必须)
  13. const int obj::e = 1 ; // 定义并给值

阅读(1313) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~