首页 > 代码库 > C++类学习笔记

C++类学习笔记

  在C++中struct类型(结构体)属于类类型。

  class student {} 与 strcut student {}是一样的,唯一的区别就是class中如果未对成员进行public、private或protected等访问限定声明则默认为private的,而struct中则默认为public。类同样遵循结构体的字节对齐原则。

 

  私有的成员函数只能被类内的其他成员函数调用,即使是其对象也不行。私有的成员变量只能被本类的成员函数和(友元类)所调用。友元类后续补充。

  受保护的成员则可以被本类以及派生类所调用。

 

  类内的成员函数一般只在类内声明,然后在类外进行定义,这样子可以使类体更清晰。同时在类内定义的函数会被默认为内置(inline)函数。在类体外定义的函数则不会被默认为inline函数,此时我们需要的时候就得显示的声明了。
PS:   "inline函数一般只用于内容及其简单的函数,因为内联函数的代码会在任何调用它的地方展开,如果函数太复杂,代码膨胀带来的恶果很可能会大于效率的提高带来的益处。
现在的编译器会自动决定是否对函数进行上面的操作,而不是根据你前面加不加inline。但是inline本身还是有另外一个意义:一个可执行文件的cpp文件中一个函数只能被定义一次。如果你把函数定义在一个.h文件中并让两个cpp包含就会造成这个函数分别在两个cpp中被定义产生错误。但是inline函数是允许在多个cpp中多次定义的,就解决了这个问题。"

   

  C++中函数给参数设定默认值,如:
  void display(int a = 0) {cout<<a<<endl;};

  如果display()这样子调用将会输出0。而且如果声明是指定了默认的参数值,定义函数时则不需要再设定默认值。如果都设置了且不一致,以声明是为准。总之呢,一切以声明中的为准,定义中设定的默认值无效!

  

  软件工程的一个最基本原则: 将接口与实现分离--->待学习!简单解释:将类的定义放在一个同文件中如haha.h,而类的实现则放在一个源文件中如haha.cpp,这样就实现分离了,当提供给用户的时候,我们把源文件编译成静态链接库(由目标代码组成即二进制代码文件)之后提供给用户,后缀为lib,这样子用户就不可能再修改类的实现了。(源文件单独编译,可以避免多次选用该类时多次编译源文件)

 

  类库包含两个部分:①类声明头文件,②经过编译的类实现目标文件

 

  类的数据成员是不能在声明的时候进行初始化的。如class a {haha = 1;}这是错误的!因为类其实就是一种数据类型,并不占存储空间,因此无法容纳数据。

  

  构造函数在每次生成对象时自动执行。构造函数也可以像成员函数一样进行接口与实现分离。构造函数木有返回值,因此不需要声明类型。构造函数不能被用户调用。如果用户没有自定义构造函数,C++系统则会自己生成一个构造函数,但是函数体是空的,不会进行任何初始化的操作。

 

  函数的重载,函数名相同,返回类型不同(非必须),参数不同(必须),这些同样适用于构造函数的重载。

  无参数的构造函数称为默认构造函数,一个类只能有一个默认构造函数。建立对象时如果选用无参数的构造函数,定义对象语句为 class a;注意!a后不跟括号!!

全部指定了参数默认值构造函数也是默认构造函数。定义了参数全有默认值的默认构造函数之后依然可以定义重载构造函数。

  应该在声明中指定构造函数的默认参数,因为声明中用户可以看到,而放到实现中用户则不能看到(经代码实验必须得放在声明中,否则会报错。)。而在声明中如果指定了参数的默认值,则在实现中不能再次指定(VS会报错,重定义了默认参数)

 

 

  析构函数,作用与构造函数正好相反,当对象的声明周期结束时自动调用析构函数,具体有以下几种情况:①如果在一个函数中定义了一个普通的对象,当函数结束是对象释放,调用。②定义了一个static局部对象,在函数结束是不释放,当main函数结束时或者调用exit函数是才释放。③定义了一个全局的对象时,当程序流程离开其作用域时(如②中的情况),释放。④当用new运算符动态的生成一个对象时,调用delete时释放。

  析构函数的作用不是删除对象,而是在撤销对象占用的内存之前完成一些清理工作,从而使得这部分内存可以被程序分配给新对象使用。

  析构函数没有参数,因此其不能被重载。一个类可以有多个构造函数,但是只有一个析构函数,就是因为其不可被重载。

 

  对象数组,如:

?
1
2
3
4
5
6
Student stu[3] =
    {
        Student(10010,"wg",‘m‘),
        Student(10011,"zf",‘m‘),
        Student(10012,"xl",‘f‘)
    };

 

  这种Student()生成的为临时对象,赋值结束之后就会销毁掉,因此在赋值结束之后就会调用其析构函数。

 

  公用数据的保护。(对象做参数时,会先建立一个新的对象拷贝实参对象,这样子会占用两份内存,开销较大,因此一般用指针做参数。引用不算,引用也是传址

  常对象、常数据成员、常成员函数,常成员函数中不可以调用其他的非const成员函数。常成员函数均可以被非常对象以及常对象调用。而常对象只能调用常成员函数。

  常指针,定义时即进行初始化,如 int * const p1 = address.此后p1的指向不可再改变。

  指向常量的指针,定义如下 const int * p1;const的位置与常指针不同,同时不必定义时即进行初始化。如果一个变量已经声明为常量,则只能用指向常变量的指针指向它而不能用普通的指针指向它。如:

?
1
2
3
4
const int a = 3;
const int * p1;
p1 = &a;//合法的
int *p2 = &a;//非法的

PS:指向常量的指针可以重新指向其他的地址。如:

?
1
2
3
4
5
const int *p1;
int a = 3;
p1 = &a;//*p1 === 3;
int b = 4;
p1 = &b;//*p1 === 4;

  

指向常量的指针变量还可以指向普通变量,但是此时不可以通过指针来改变该普通变量的值。如:

?
1
2
3
4
5
6
const int *p1;
int a = 3;
p1 = &a;
*p1 = 4;//非法,p1是一个指向常量的指针,因此*p1是一个常量,因此不可赋值。
int *p2 = &a;
*p2 = 4;//合法

简单总结下就是:在一个指针可能发生变化的时候,此时这个指针只能是普通指针。在一个指针不会发生变化的时候,这个指针既可以是普通指针也可是一个指向常量的指针。(此话使用于指针做参数时,指针指向变量及对象的原则也可以参照此句)。

 

  友元

  友元函数可以访问类中的私有变量及函数。友元函数的声明(声明在类中friend void fun();),友元函数不仅可以是普通函数也可是其他类中的成员函数。

  类的提前引用声明:class Class1;(无类体,只有类名)注意:此时还不能用Class1来创建对象,必须等到对Class1正式进行声明之后才可以创建,此时只能使用该类名。 基本原则:因为对象的声明是需要占用内存的,因此系统必须知道该对象占用多大的内存,而类未正式声明的话根本不知道其占用内存多大,因此不可以,而使用该类的引用或者指针是,由于引用和指针都是占用固定的内存大小,与对象占用内存的大小无关,因此可以声明该类的指针或者引用

  友元成员函数的调用方法,如:(注意两个类的声明顺序与友元函数声明之间的关系,顺序错误会导致编译错误)

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Class2; //提前引用声明
class Class1 {
  public:
    Class1() {
      num = 3;
    };
    void display(Class2 &);//利用提前引用声明Class2的引用
  private:
    int num;
};
 
class Class2 {
  public:
    Class2() {
      num = 4;
    };
    friend void Class1::display(Class2 &);//声明友元成员函数
  private:
    int num;
};
 
void Class1::display(Class2 &t) {//定义成员函数
  cout<<t.num<<endl;//输出Class2对象的num
  cout<<num<<endl;//输出Class1对象的num
};
 
int main() {
  Class1 myClass1;
  Class2 myClass2;
  myClass1.display(myClass2);//友元成员函数的调用
  system("PAUSE");
  return 0;
};

  一个函数可以被多个类声明为友元函数。

  友元类,声明方法:friend className;声明为友元类之后,友元类中的所有函数就成为该类的友元函数了。注意友元关系是单向的!友元类一般不用。

   

  类模板(又称参数化的类)

  对于功能相同,但是数据类型不同的类可以用模板进行抽象,从而产生类模板,声明方式如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
template <class typeName> //类模板声明,类模板的参数可以有多个,如template <class a,class b>
class compare {
    public:
        compare(typeName a,typeName b) {//类模板的构造函数
            this->a = a;
            this->b = b;
        };
        typeName max();//成员函数,类型为typeName,等待实参来替换
    private:
        typeName a;//成员变量,类型为typeName,等待实参来替换
        typeName b;
};
template <class typeName> //类外定义成员函数应该加上这句
typeName compare <typeName> ::max() {//类外成员函数定义
    return a > b ? a : b;
};
int main() {
  compare <int> com1(1,2);//类模板对象的声明
  cout<<com1.max()<<endl;
  compare <double> com2(1.23,1.33);
  cout<<com2.max()<<endl;
  system("PAUSE");
  return 0;
};