C语言中文网C++教程笔记

C语言中文网C++教程笔记

快速阅读C语言中文网:C++教程,弥补自己对C++的不熟悉的常用语法进行记录,仅记录对自己有帮助的内容或片段。侵删!

1. C/C++

1.11 C++ 内联函数

  • 内联函数的意义
    • 函数调用是有时间和空间开销的
      • 程序在执行一个函数之前需要做一些准备工作,要将实参、局部变量、返回地址以及若干寄存器都压入栈中,然后才能执行函数体中的代码;函数体中的代码执行完毕后还要清理现场,将之前压入栈中的数据都出栈,才能接着执行函数调用位置以后的代码
      • 如果函数体代码比较多,需要较长的执行时间,那么函数调用机制占用的时间可以忽略;如果函数只有一两条语句,那么大部分的时间都会花费在函数调用机制上,这种时间开销就就不容忽视。
      • 为了消除函数调用的时空开销,C++ 提供一种提高效率的方法,即在编译时将函数调用处用函数体替换,类似于C语言中的宏展开。这种在函数调用处直接嵌入函数体的函数称为内联函数(Inline Function),又称内嵌函数或者内置函数
  • inline关键字需要在函数定义时添加,也可以在函数声明处加 inline,不过这样做没有效果,编译器会忽略函数声明处的 inline
  • 内联函数缺点
    使用内联函数的缺点也是非常明显的,编译后的程序会存在多份相同的函数拷贝,如果被声明为内联函数的函数体非常大,那么编译后的程序体积也将会变得很大,所以再次强调,一般只将那些短小的、频繁调用的函数声明为内联函数

2. 类和对象

匿名对象无法回收内存,会导致内存泄露

(new Student("小明", 15, 90)) -> show();

2.2 C++类的成员变量和成员函数

  • 在类体中和类体外定义成员函数的区别
    • 在类体中定义的成员函数会自动成为内联函数,在类体外定义的不会。当然,在类体内部定义的函数也可以加 inline 关键字,但这是多余的,因为类体内部定义的函数默认就是内联函数。
    • 建议在类体内部对成员函数作声明,而在类体外部进行定义

2.6 构造函数初始化列表

  • 使用构造函数初始化列表并没有效率上的优势,仅仅是书写方便,尤其是成员变量较多时,这种写法非常简单明了
  • 成员变量的初始化顺序与初始化列表中列出的变量的顺序无关,它只与成员变量在类中声明的顺序有关
  • 初始化 const 成员变量的唯一方法就是使用初始化列表

2.11 C++ this 指针

  • this 是 C++ 中的一个关键字,也是一个 const 指针,它指向当前对象,通过它可以访问当前对象的所有成员
  • this 实际上是成员函数的一个形参,在调用成员函数时将对象的地址作为实参传递给 this。不过 this 这个形参是隐式的,它并不出现在代码中,而是在编译阶段由编译器默默地将它添加到参数列表中。

2.12 C++ static静态成员变量

  • 初始化
    静态成员变量初始化必须在类声明的外部初始化,具体形式为
    type class::name = value;  // int Student::m_total = 0;
    
    静态成员变量在初始化时不能再加 static,但必须要有数据类型。被 private、protected、public 修饰的静态成员变量都可以用这种方式初始化
    初始化时可以赋初值,也可以不赋值。如果不赋值,那么会被默认初始化为 0。全局数据区的变量都有默认的初始值 0,而动态数据区(堆区、栈区)变量的默认值是不确定的,一般认为是垃圾值
  • 内存分配
    • static 成员变量的内存既不是在声明类时分配,也不是在创建对象时分配,而是在(类外)初始化时分配。反过来说,没有在类外初始化的 static 成员变量不能使用
    • static 成员变量和普通的 static 变量类似,都在内存分区中的全局数据区分配内存
  • 静态成员变量访问
    static 成员变量既可以通过对象来访问,也可以通过类来访问。某个类定义的多个对象的静态成员变量修改时会相互影响
  • 一个类中可以有一个或多个静态成员变量,所有的对象都共享这些静态成员变量,都可以引用它
  • 使用sizeof(是关键字而非函数)计算类的空间时,只能得到非静态成员变量+虚列表/虚指针的大小

2.13 C++ static静态成员函数

  • 静态成员函数与普通成员函数的区别
    静态成员函数与普通成员函数的根本区别在于:普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)
  • 静态成员函数只能访问静态成员
    普通成员函数可以访问所有成员(包括成员变量和成员函数),静态成员函数只能访问静态成员。
  • 静态成员函数的this问题
    编译器在编译一个普通成员函数时,会隐式地增加一个形参 this,并把当前对象的地址赋值给 this,所以普通成员函数只能在创建对象后通过对象来调用,因为它需要当前对象的地址。而静态成员函数可以通过类来直接调用,编译器不会为它增加形参 this,它不需要当前对象的地址,所以不管有没有创建对象,都可以调用静态成员函数
  • 内存问题
    普通成员变量占用对象的内存,静态成员函数没有 this 指针,不知道指向哪个对象,无法访问对象的成员变量,也就是说静态成员函数不能访问普通成员变量,只能访问静态成员变量。
  • 调用
    和静态成员变量类似,静态成员函数在声明时要加 static,在定义时不能加 static。静态成员函数可以通过类来调用(一般都是这样做),也可以通过对象来调用

2.14 C++ const成员变量和成员函数

  • const成员变量的初始化只能通过初始化列表的方式进程初始化或声明时就设置
  • const 成员函数可以使用类中的所有成员变量,但是不能修改它们的值。
    在声明和定义的时候在函数头部的结尾加上 const 关键字。必须在成员函数的声明和定义处同时加上 const 关键字

    char *getname() constchar *getname()是两个不同的函数原型,如果只在一个地方加 const 会导致声明和定义处的函数原型冲突

  • 函数声明时const位置的问题
    • 函数开头的 const 用来修饰函数的返回值,表示返回值是 const 类型,也就是不能被修改,例如const char * getname()
    • 函数头部的结尾加上 const 表示常成员函数,这种函数只能读取成员变量的值,而不能修改成员变量的值,例如char * getname() const

2.15 C++ const对象(常对象)

在 C++ 中,const 也可以用来修饰对象,称为常对象。一旦将对象定义为常对象之后,就只能调用类的 const 成员(包括 const 成员变量和 const 成员函数)了

2.16 C++友元函数和友元类

  • 友元函数
    • 在当前类以外定义的、不属于当前类的函数也可以在类中声明,但要在前面加 friend 关键字,这样就构成了友元函数。友元函数可以是不属于任何类的非成员函数/全局范围内的非成员函数,也可以是其他类的成员函数
    • 友元函数可以访问当前类中的所有成员,包括 public、protected、private 属性的
    //非成员函数(全局函数)
    void show(Student *pstu){
        cout<<pstu->m_name<<"的年龄是 "<<pstu->m_age<<",成绩是 "<<pstu->m_score<<endl;
    }
    
    // 其他类成员函数
    // 在Student类中,声明(并实现)成员函数show(Address *addr)
    // 在Address类中,将Student类中的成员函数show(Address *addr)声明为友元函数,说明该方法可以获取到本类的成员
    void Student::show(Address *addr)
    {
    cout<<m_name<<"的年龄是 "<<m_age<<",成绩是 "<<m_score<<endl;
    cout<<"家庭住址:"<<addr->m_province<<"省"<<addr->m_city<<"市"<<addr->m_district<<"区"<<endl;
    }
    
  • 友元类
    友元类中的所有成员函数都是另外一个类的友元函数
    //声明Address类
    class Address{
    public:
        //将Student类声明为Address类的友元类
        friend class Student;
    }
    
  • 友元的关系是单向的而不是双向的。如果声明了类 B 是类 A 的友元类,不等于类 A 是类 B 的友元类,类 A 中的成员函数不能访问类 B 中的 private 成员。
  • 友元的关系不能传递。如果类 B 是类 A 的友元类,类 C 是类 B 的友元类,不等于类 C 是类 A 的友元类。

除非有必要,一般不建议把整个类声明为友元类,而只将某些成员函数声明为友元函数,这样更安全一些。

2.18 C++ class和struct的区别

  • 使用 class 时,类中的成员默认都是 private 属性的;而使用 struct 时,结构体中的成员默认都是 public 属性的。
  • class 继承默认是 private 继承,而 struct 继承默认是 public 继承
  • class 可以使用模板,而 struct 不能

3. C++引用

3.1 C++引用入门

  • 引用
    C++提供的一种比指针更加便捷的传递聚合类型数据(数组、结构体、类(对象)等由基本类型组合而成的类型)的方式,那就是引用(Reference)。用来减少参数传递时需要的内容拷贝耗时,实际上是实参的别名,个人感觉类似于typedef
    // type &name = data; name和data的地址相同
    int a = 99;
    int &r = a;
    
    引用必须在定义的同时初始化,并且以后也要从一而终,不能再引用其它数据,这有点类似于常量(const 变量)
  • C++引用作为函数参数
  • C++引用作为函数返回值
    在将引用作为函数返回值时应该注意一个小问题,就是不能返回局部数据(例如局部变量、局部对象、局部数组等)的引用,因为当函数调用完成后局部数据就会被销毁,有可能在下次使用时数据就不存在了,C++ 编译器检测到该行为时也会给出警告。

4. 继承与派生

4.2 C++ 三种继承方式

  • 访问权限
    public > protected > private
  • 继承方式
    • 基类成员在派生类中的访问权限不得高于继承方式中指定的权限
    • 不管继承方式如何,基类中的 private 成员在派生类中始终不能使用

注意,这里说的是基类的 private 成员不能在派生类中使用,并没有说基类的 private 成员不能被继承。实际上,基类的 private 成员是能够被继承的,并且(成员变量)会占用派生类对象的内存,它只是在派生类中不可见,导致无法使用罢了。private 成员的这种特性,能够很好的对派生类隐藏基类的实现,以体现面向对象的封装性。
C语言中文网C++教程笔记_第1张图片

  • 改变访问权限
    使用 using 关键字可以改变基类成员在派生类中的访问权限。但不能讲修改基类的private权限,因为基类中 private 成员在派生类中是不可见的,根本不能使用。
    class Student : public People {
    private:
    	using People::show;  //将public改为private
    };
    

4.3 C++继承时的名字遮蔽问题

  • 派生类的成员与基类成员重名时,派生类的成员会将基类的成员遮蔽,派生类对象调用重名成员时使用的是派生类中的成员,而非基类成员。若想调用基类成员,需要加上类名和域解析符。
    //使用的是派生类新增的成员函数,而不是从基类继承的
    stu.show();
    //使用的是从基类继承来的成员函数
    stu.People::show();
    
  • 基类成员函数和派生类成员函数不构成重载
    基类成员函数和派生类成员函数不会构成重载,如果派生类有同名函数,那么就会遮蔽基类中的所有同名函数,不管它们的参数是否一样

4.4 C++类继承时的作用域嵌套,破解C++继承的一切秘密

  • 派生类的作用域位于基类作用域之内
    B 继承自 A,C继承自 B,它们作用域的嵌套关系如下图所示:C语言中文网C++教程笔记_第2张图片
    obj 是 C 类的对象,通过 obj 访问成员变量 n 时,在 C 类的作用域中就能够找到了 n 这个名字。虽然 A 类和 B 类都有名字 n,但编译器不会到它们的作用域中查找,所以是不可见的,也即派生类中的 n 遮蔽了基类中的 n。如果一个名字在派生类的作用域内无法找到,编译器会继续到外层的基类作用域中查找改名字的定义。

4.5 C++继承时的对象内存模型

  • 派生类的内存模型:基类成员变量 + 新增成员变量;成员函数仍然存储在代码区,由所有对象共享
    C语言中文网C++教程笔记_第3张图片

4.6 C++基类和派生类的构造函数

  • 普通成员函数可以被继承,类的构造函数不能被继承

  • 构造函数初始化时,可以调用基类的构造函数初始化基类的成员变量。只能将基类构造函数的调用放在函数头部,不能放在函数体中。

    // 写的时候顺序可以换,但是派生类构造函数总是先调用基类构造函数再执行其他代码(包括参数初始化表以及函数体中的代码)
    Student::Student(char *name, int age, float score): People(name, age), m_score(score){ }
    
  • 派生类构造函数中只能调用直接基类的构造函数,不能调用间接基类的。

    A类构造函数 --> B类构造函数 --> C类构造函数

4.7 C++基类和派生类的析构函数

  • 析构函数也不能被继承
  • 析构时,先执行派生类析构函数,再执行基类析构函数

4.14 C++将派生类赋值给基类(向上转型)

  • 将派生类对象赋值给基类对象
    • 赋值的本质是将现有的数据写入已分配好的内存中,对象的内存只包含了成员变量,所以对象之间的赋值是成员变量的赋值,成员函数不存在赋值问题

      虽然有a=b;这样的赋值过程,但是 a.display() 始终调用的都是 A 类的 display() 函数。换句话说, 对象之间的赋值不会影响成员函数,也不会影响 this 指针。

C语言中文网C++教程笔记_第4张图片

  • 将派生类指针赋值给基类指针
    • 与对象变量之间的赋值不同的是,对象指针之间的赋值并没有拷贝对象的成员,也没有修改对象本身的数据,仅仅是改变了指针的指向
    • 编译器通过指针来访问成员变量,指针指向哪个对象就使用哪个对象的数据;编译器通过指针的类型来访问成员函数,指针属于哪个类的类型就使用哪个类的函数
  • 将派生类引用赋值给基类引用
    基类的引用也可以指向派生类的对象,并且它的表现和指针是类似的

需要注意的是,向上转型后通过基类的对象、指针、引用只能访问从基类继承过去的成员(包括成员变量和成员函数),不能访问派生类新增的成员

你可能感兴趣的:(c/c++,c语言,c++,开发语言)