Learning CPP(前10章)--21天学通C++第八版笔记

文章目录

  • 21天学通C++第八版笔记(一)
  • 第三章
  • 第四章
    • 1. C风格字符串
  • 第五章
  • 第七章 函数
    • 1. 用栈理解函数调用
    • 2. lambda函数速览
  • 第八章 指针和引用
    • 1. 指针的大小
    • 2. 动态内存分配
    • 3. 将递增和递减用于指针
    • 4. 将关键字const用于引用
  • 第九章 类和对象
    • 1. 构造函数
    • 2. 析构函数
    • 3. 复制构造函数
    • 4. 避免隐式转换关键字explicit
    • 5. 声明友元
    • 6. 共用体union
    • 7. 对类和结构使用聚合初始化
  • 第10章 实现继承
    • 1 .基类和派生类
    • 2. 私有继承
    • 3. 保护继承
    • 4. 多重继承
    • 5. 使用final禁止继承

21天学通C++第八版笔记(一)

第三章

  1. sizeof返回的是变量的长度,单位为字节。

    cout << sizeof(bool) //返回1 
         << sizeof(char) //返回1
         << sizeof(int)  //返回4
         << sizeof(float) //返回4
         << sizeof(double) //返回8
         << endl;
    //C++11中有int8_t和uint8_t,分别用于存储8位的有符号和无符号整数,当然还有16,32,64的整型
    
  2. 关键字constconstexpr

    const

    如果变量的值不应改变,就应将其声明为常量,这是一种良好的编程习惯。

    constexpr

    通过关键字 constexpr,可让常量声明像函数:

    constexpr double GetPi() {return 22.0/7;}
    

    在一个常量表达式中,可使用另一个常量表达式:

    constexpr double TwicePi() {return 2 * GetPi();}
    

    常量表达式看起来像函数,但在编译器和应用程序看来,它们提供了优化可能性。只要编译器能够从常量表达式计算出常量,就可在语句和表达式中可使用常量的地方使用它。在前面的示例中, TwicePi()是一个常量表达式,它使用了另一个常量表达式 GetPi()。这可能引发编译阶段优化,即编译 器将所有的 TwicePi()都替换为 6.28571,从而避免在代码执行时计算 2×22/7 的值。

    GetPi()和 TwicePi()看起来像函数,但其实不是函数。函数在程序执行期间被调用,但 GetPi()和 TwicePi()是函数表达式,编译器将每个GetPi()都替换成了 3.14286,并将每个 TwicePi()都替换成了 6.28571。通过在编译阶段对 TwicePi()进行解析,程序的执行速度比将这些计算放在函数中时更快。

  3. typedef

    systax: 如将int指定为 myInt

    typedef int myInt
    
  4. #define定义常量

    #define pi 3.14286
    //#define 是一个预处理器宏,让预处理器将随后出现的所有 pi 都替换为 3.14286。预处理器将进行文本替换,而不是智能替换。编译器既不知道也不关心常量的类型。
    

    Warning : 使用#define 定义常量的做法已被摒弃,因此不应采用这种做法。

第四章

1. C风格字符串

  • 字符串与字符数组

    std::cout << "Hello World";
    

    等价于

    char sayHello[] = {'H','e','l','l','o',' ','W','o','r','l','d',
    	                  '\0'};
    std::cout << sayHello << std::endl;
    

    请注意,该数组的最后一个字符为空字符‘\0’。这也被称为字符串结束字符,因为它告诉编译器, 字符串到此结束。这种 C 风格字符串是特殊的字符数组,因为总是在最后一个字符后加上空字符‘\0’。 您在代码中使用字符串字面量时,编译器将负责在它后面添加‘\0’。

    在数组中间插入‘\0’并不会改变数组的长度,而只会导致将该数组作为输入的字符串处理将到这个位置结束。

    ‘\0’看起来像两个字符。使用键盘输入它时,确实需要输入两个字符,但反斜杆是编译器 能够理解的特殊转义编码, \0 表示空,即它让编译器插入空字符或零。不能将其写做‘0’,因为它表示字符 0,其 ASCII 编码为 48

  • 分析C风格字符串中的终止控制符

    char sayHello[] = { 'H','e','l','l','o',' ','W','o','r','l','d','\0' };
    cout << sayHello << endl;
    cout << sizeof(sayHello) << endl;
    
    sayHello[5] = '\0';
    cout << sayHello << endl;
    cout << sizeof(sayHello) << endl;
    

    最后的结果是:

    Hello World
    12
    Hello
    12
    

    将数组sayHello中的下标为5的’ ‘换为’\0’,数组的大小(字节)没有变,但是打印出来的变了。

  • 字符数组的存储大小

    char userInput[21] = {'\0'};
    //userInput最大只能存储20个字符,因为最后一个字符必须是终止符'\0'
    

第五章

  • int和short的大小(在x64(86位)和x86(32位)下运行,结果一样)

    int 4个字节,32位

    short 2个字节,16位

第七章 函数

1. 用栈理解函数调用

Stack

2. lambda函数速览

lambda 函数是 C++11 引入的,有助于使用 STL 算法对数据进行排序或处理。排序函数要求您提供一个二元谓词,以帮助确定元素的顺序。二元谓词是一个这样的函数,即对两个参数进行比较,并在一个小于另一个时返回 true,否则返回 false。这种谓词通常被实现为类中的运算符,这导致编码工作非常烦琐。使用 lambda 函数可简化谓词的定义

下面给出个例子:

#include 
#include 
#include 

using namespace std;

void DisplayNums(vector<int>& dynArray) {
	for_each(dynArray.begin(), dynArray.end(),
		[](int Element) {cout << Element << " "; });
	cout << endl;
}
int main() {
	vector<int> myNums;
	myNums.push_back(501);
	myNums.push_back(-1);
	myNums.push_back(25);
	myNums.push_back(-35);
	//打印vector
	DisplayNums(myNums);

	cout << "Sorting them in descending order" << endl;
	sort(myNums.begin(), myNums.end(),
		[](int num1, int num2) {return num1 > num2; });
	//打印
	DisplayNums(myNums);
	//Visual Studio中要用system来暂停控制台,这样才能看见输出
	system("pause");
	return 0;
}

第八章 指针和引用

1. 指针的大小

  • 32位的机器指针的大小为: 4字节(4 Bytes)
  • 64位的机器指针的大小为:8字节(8 Bytes)

2. 动态内存分配

  • 使用new和delete动态分配共和释放内存

    通常情况下,如果成功, new 将返回指向一个指针,指向分配的 内存,否则将引发异常。使用 new 时,需要指定要为哪种数据类型分配内存:

    Type* Pointer = new Type; //request memory for one element
    Type* Pointer = new Type[numElements]; //request memory for numElements
    

    eg.

    int* pointToAnInt = new int;
    int* PointToNus = new int[10];
    

    使用 new 分配的内存最终都需使用对应的 delete 进行释放:

    Type* Pointer = new Type;//allocate memory
    delete Pointer; // release memory allocated above
    
    Type* Pointer = new Type[numElements];//allcate a block
    delete[] Pointer; //release block allocated above
    

3. 将递增和递减用于指针

  • delete的时候要将指针归位

    eg.

    #include 
    #include 
    #include 
    
    using namespace std;
    
    int main() {
        
    	cout << "How many integers you wish to enter? ";
    	int numEntries = 0;
    	cin >> numEntries;
    
    	int* pointsToInts = new int[numEntries];
    
    	cout << "Allocated for " << numEntries << " integers" << endl;
    	for (int counter = 0; counter < numEntries; ++counter) {
    		cout << "Enter number " << counter << ": ";
    		cin >> *(pointsToInts + counter);
    	}
    	
    	cout << "Displaying all numbers entered: " << endl;
    	for (int counter = 0; counter < numEntries; ++counter)
    		cout << *(pointsToInts++) << " ";
    	cout << endl;
    //--------------------Hint:this is important----------------//
    	//return pointer to initial position
    	pointsToInts -= numEntries;
    //---------------------------------------------------------//
    	delete[] pointsToInts;
    
    	system("pause");
    	return 0;
    }
    

4. 将关键字const用于引用

  • 看例子

    int original = 30;
    const int& constRef = original;
    constRef = 40; //Not allowed: constRef cannot change value in original
    int& ref2 = constRef; //Not allowed: ref2 is not const
    const int& constRef2 = constRef; //OK
    

第九章 类和对象

1. 构造函数

  • 没有默认构造函数的类

    本来类会有默认构造函数,当有了重载的构造函数后,默认构造函数就没了:

    #include 
    #include 
    
    using namespace std;
    
    class Human {
    private:
    	string name;
    	int age;
    
    public:
    	Human(string humanName, int humanAge) {
    		name = humanName;
    		age = humanAge;
    	}
    	void IntroduceSelf() {
    		cout << "I am " + name << " and am ";
    		cout << age << " years old" << endl;
    	}
    };
    int main(){
        Human firstMan("Adam", 25);
        Human secondMan;//不存在默认构造函数,error
    
    	firstMan.IntroduceSelf();
    	
    	system("pause");
    	return 0;
    }
    
  • 构造函数包含初始化列表

    class Human {
    private:
    	string name;
    	int age;
    
    public:
        Human(string humanName, int humanAge)
            : name(humanName), age(humanAge){}
        ...
    };
    

2. 析构函数

  • 温馨提示:

    使用 char*缓冲区时,您必须自己管理内存分配和释放,因此本书建议不要使用它们,而使用 std::string。 std::string 等工具都是类,它们充分利用了构造函数和析构函数,还有将在第 12 章介绍的 运算符,让您无需考虑分配和释放等内存管理工作。

  • 析构函数例子(用C字符串风格写的,类似于string的封装)

    #define _CRT_SECURE_NO_WARNINGS //VS要加这个,因为不用这个的话strcpy会提示不安全,编译不通过
    #include 
    #include 
    using namespace std;
    
    class MyString {
    private:
    	char* buffer;
    public:
    	MyString(const char* initString) {
    		if (initString != NULL) {
    			buffer = new char[strlen(initString) + 1];//为了存放'\0'
    			strcpy(buffer, initString);
    		}
    		else
    			buffer = NULL;
    	}
        //析构函数
    	~MyString() {
    		cout << "Invoking destructor, clearing up" << endl;
    		if (buffer != NULL)
    			delete[] buffer;
            //如果在vs中想要看到这个打印,可以加下面代码
            //system("pause");
    	}
    	int GetLength() {
    		return strlen(buffer);
    	}
    	const char* GetString() {
    		return buffer;
    	}
    };
    
    int main(){
        
        MyString sayHello("Hello from String Class");
    	cout << "String buffer in sayHello is " << sayHello.GetLength();
    	cout << " characters long" << endl;
    	
    	cout << "Buffer contains: " << sayHello.GetString() << endl;
    
    	system("pause");
    	return 0; 
    }
    

    输出结果:

    String buffer in sayHello is 23 characters long
    Buffer contains: Hello from String Class
    Invoking destructor, clearing up
    

3. 复制构造函数

  • 语法

    class MyString{
        Mystring(const MyString& copySource); //copy constructor
    };
    MyString::MyString(const MyString& conpySource){
        //Copy constructor implementation code
    }
    
  • 通过在复制构造函数声明中使用 const,可确保复制构造函数不会修改指向的源对象。另外,复制构造函数的参数必须按引用传递,否则复制构造函数将不断调用自己,直到耗 尽系统的内存为止

  • 类包含原始指针成员(char *等)时,务必编写复制构造函数和复制赋值运算符。

    除非万不得已,不要类成员声明为原始指针

4. 避免隐式转换关键字explicit

  • 为避免隐式转换,可在声明构造函数时使用关键字 explicit:

    class Human{
        int age;
    public:
        explicit Human(int humanAge):age(humanAge){}
    };
    

5. 声明友元

  • 友元有:友元函数和友元类

    #include 
    #include 
    using namespace std;
    
    class Human
    {
    private:
       //声明友元类
       friend class Utility;
       //声明友元函数
       friend void DisplayAge(const Human& person);
       string name;
       int age;
    
    public:
       Human(string humansName, int humansAge) 
       {
          name = humansName;
          age = humansAge;
       }
    };
    //友元函数的定义
    void DisplayAge(const Human& person)
    {
       cout << person.age << endl;
    }
    //友元类
    class Utility
    {
    public:
       static void DisplayAge(const Human& person)
       {
          //在友元类中可以访问到Human的私有成员变量
          cout << person.age << endl;
       }
    };
       
    int main()
    {
       Human firstMan("Adam", 25);
       cout << "Accessing private member age via friend class: ";
       Utility::DisplayAge(firstMan);
    
       return 0;
    }
    

6. 共用体union

  • syntax:

    union UnionName{
        Type1 menber1;
        Type2 menber2;
        ...
        TypeN menberN;
    };
    UnionName unionObject;
    unionObeject.menber2 = value;//choose menber2 as the active menber
    
    • 与结构类似,共用体的成员默认也是公有的,但不同的是,共用体不能继承。
      另外,将 sizeof()用于共用体时,结果总是为共用体最大成员的长度,即便该成员并不处于活动状态

  • enum的用法

    enum day{
        sun,
        mon,
        tue,
        wed,
        thu,
        fri,
        sat
    };
    day d = sun; //d只能等于"Sun,Mon,Tue,Wed,Thu,Fri,Sat"中的一个,没有其它值!
    
    //enum枚举值对应着一个整型数,通常情况下如果其中的枚举常量没有定义数值,那么第一个枚举值对应着常量值0,然后依次递增,如果第一个枚举常量定义了数值,那么其后的值将随之递增,其中每个常量之间用“,”隔开,而不是“;”,最后一个数值不用符号。
    
  • 下面给出一个实例(值得琢磨)

    //定义共用体
    union SimpleUnion {
    	int num;
    	char alphabet;
    };
    //定义一个复杂类
    struct ComplexType {
        //枚举变量,别名为Type
    	enum DataType {
    		Int,
    		Char
    	} Type;
        //共用体,别名为value
    	union Value {
    		int num;
    		char alphabet;
    	} value;
    };
    void DisplayComplexType(const ComplexType& obj) {
    	switch (obj.Type) {
         //注意:这里ComplexType是可以直接访问到枚举变量里面的Int的,下一个case同理
    	case ComplexType::Int:
    		cout << "Union contains number: " << obj.value.num << endl;
    		break;
    
    	case ComplexType::Char:
    		cout << "Union contains charactor: " << obj.value.alphabet << endl;
    		break;
    	}
    }
    
    int main() {
    	
    	SimpleUnion u1, u2;
    	u1.num = 2100;
    	u2.alphabet = 'c';
    	cout << "Sizeof(u1): " << sizeof(u1) << endl;
    	cout << "Sizeof(u2): " << sizeof(u2) << endl;
    
    	ComplexType myData1, myData2;
        //枚举变量只能取一个,用ComplexType::Int来赋值给Type
    	myData1.Type = ComplexType::Int;
        //共用体则不同,要访问到里面的成员变量,然后赋值
    	myData1.value.num = 2018;
    
    	myData2.Type = ComplexType::Char;
    	myData2.value.alphabet = 'x';
    
    	DisplayComplexType(myData1);
    	DisplayComplexType(myData2);
    
    	system("pause");
    	return 0;
    }
    

    输出结果:

    Sizeof(u1): 4
    Sizeof(u2): 4
    Union contains number: 2018
    Union contains charactor: x
    

7. 对类和结构使用聚合初始化

  • 简单理解(以后再填坑)

    struct Aggregate1{
        int num;
        double pi;
    };
    Aggregate1 a1{2017, 3.14};
    

第10章 实现继承

1 .基类和派生类

  • syntax:

    //base class
    class Fish{
        //...Fish's members
    };
    
    //derived class
    class Carp:public Fish{
        //...Carp's members
    };
    

    基类也被称为超类;从基类派生而来的类称为派生类,也叫子类

  • protected关键字

    与 public 和 private 一样, protected 也是一个访问限定符。将属性声明为protected 的时,相当于允许派生类和友元类访问它,但禁止在继承层次结构外部(包括 main( ))访问它。

    eg.

    #include 
    using namespace std; 
    
    class Fish
    {
    //基类的成员变量声明为protected,允许派生类和友元类访问
    protected:
       bool isFreshWaterFish; // accessible only to derived classes
    
    public:
      void Swim()
       {
          if (isFreshWaterFish)
             cout << "Swims in lake" << endl;
          else
             cout << "Swims in sea" << endl;
       }
    };
    
    class Tuna: public Fish
    {
    public:
       Tuna()
       {
          isFreshWaterFish = false; // set base class protected member
       }
    };
    
    class Carp: public Fish
    {
    public:
       Carp()
       {
          isFreshWaterFish = false;
       }
    };
    
    int main()
    {
       Carp myLunch;
       Tuna myDinner;
    
       cout << "Getting my food to swim" << endl;
    
       cout << "Lunch: ";
       myLunch.Swim();
    
       cout << "Dinner: ";
       myDinner.Swim();
    
       // 因为基类的成员变量声明为protected,在main函数访问不了
       // myLunch.isFreshWaterFish = false; 
    
       return 0;
    }
    
  • 基类初始化–向基类传递参数

    eg.

    class Base{
    public;
        Base(int someNumber){
            //use someNumber
        }
    };
    class Derived:public Base{
    public:
        Deriverd() : Base(25){  //instantiate Base with argument 25
            //deriverd class constructor code
        }
    }
    
  • 在派生类中覆盖基类的方法

    #include 
    using namespace std; 
    
    class Fish
    {
    private:
       bool isFreshWaterFish;
    
    public:
       // Fish constructor
       Fish(bool IsFreshWater) : isFreshWaterFish(IsFreshWater){}
    
       void Swim()
       {
          if (isFreshWaterFish)
             cout << "Swims in lake" << endl;
          else
             cout << "Swims in sea" << endl;
       }
    };
    
    class Tuna: public Fish
    {
    public:
       Tuna(): Fish(false) {}
    
       void Swim()
       {
          cout << "Tuna swims real fast" << endl;
       }
    };
    
    class Carp: public Fish
    {
    public:
       Carp(): Fish(true) {}
    
       void Swim()
       {
          cout << "Carp swims real slow" << endl;
       }
    };
    
    int main()
    {
       Carp myLunch;
       Tuna myDinner;
    
       cout << "Getting my food to swim" << endl;
    
       cout << "Lunch: ";
       //调用的是派生类中的Swim方法
       myLunch.Swim();
    
       cout << "Dinner: ";
       //调用的是派生类中的Swim方法
       myDinner.Swim();
    
       return 0;
    }
    

    输出结果:

    About my food
    Lunch: Carp swims real slow
    Dinner: Tuna swims real fast
    
    • 如果想要调用基类的方法,在上面的程序中:

      myDinner.Fish::Swim();//注意语法
      
    • 如果想在派生类中调用基类的方法,则

      class Carp: public Fish{
      public:
          Carp(): Fish(true){}
          
          void Swim(){
              cout << "Carp swims real slow" << endl;
              Fish::Swim(); //调用基类的Swim()方法
          }
      };
      
  • 基类和派生类的构造顺序

    如果Tuna 类是从 Fish 类派生而来的,创建Tuna 对象时,先调用Tuna 的构造函数还是Fish 的构造函数?另外,实例化对象时,成员属性(如Fish::isFreshWaterFish)是调用构造函数之前还是之后实例化?好在实例化顺序已标准化,基类对象在派生类对象之前被实例化。因此,首先构造 Tuna 对象的Fish 部分,这样实例化 Tuna 部分时,成员属性(具体地说是 Fish 的保护和公有属性)已准备就绪,可以使用了。实例化 Fish 部分和 Tuna 部分时,先实例化成员属性(如 Fish::isFreshWaterFish),再调用构造函数,确保成员属性准备就绪,可供构造函数使用。这也适用于Tuna::Tuna( )

2. 私有继承

  • 1中主要讲的是共有继承(关键字public),私有继承用关键字private:

    class Base{
        //...
    };
    class Derived : private Base{
        //...
    };
    

    私有继承意味着在派生类的实例中, 基类的所有公有成员和方法都是私有的—不能从外部访问。
    换句话说,即便是 Base 类的公有成员和方法,也只能被 Derived 类使用,而无法通过 Derived 实例来使用它们

  • 例子

    #include 
    using namespace std;
    //发动机类
    class Motor
    {
    public:
       //点火开关
       void SwitchIgnition()
       {
          cout << "Ignition ON" << endl;
       }
       //喷油...
       void PumpFuel()
       {
          cout << "Fuel in cylinders" << endl;
       }
       //点火
       void FireCylinders()
       {
          cout << "Vroooom" << endl;
       }
    };
    //发动机派生类:汽车
    class Car:private Motor
    {
    public:
       void Move()
       {
          SwitchIgnition();
          PumpFuel();
          FireCylinders();
       }
    };
    
    int main()
    {
       Car myDreamCar;
       myDreamCar.Move();
    
       return 0;
    }
    

    输出

    Ignition ON
    Fuel in cylinders
    Vroooom
    

    Tips:如果有一个 RaceCar 类,它继承了 Car 类,则不管 RaceCar 和 Car 之间的继承关系是什么样的, RaceCar 都不能访问基类 Motor 的公有成员和方法。这是因为 Car 和 Motor 之间是私有继承关系,这意味着除 Car 外,其他所有实体都不能访问基类 Motor 的公有或保护成员。

3. 保护继承

  • 在保护继承层次结构中,子类的子类(即 Derived2)能够访问 Base 类的公有和保护成员。

  • 更好的办法 : 将 Motor 对象作为 Car 类的私有成员被称为组合(composition)或聚合(aggergation),这样的 Car 类类似于下面这样:

    class Car{
    private:
        Motor heartOfCar;
    public:
        void Move(){
            heartOfCar.SwitchIgnition();
            heartOfCar.PumpFuel();
            heartOfCar.FireCylinders();
        }
    };
    

4. 多重继承

  • sytax:

    class Derived: access-specifier Base1, acess-specifier Base2{
        //...
    };
    
    eg.
        class Platypus: public Mammal, public Reptile, public Bird{
            //...
        };
    

5. 使用final禁止继承

  • 从 C++11 起,编译器支持限定符 final。被声明为 final 的类不能用作基类。

    例如, Platypus(鸭嘴兽) 类表示一种进化得很好的物种,因此您可能想将其声明为 final 的,从而禁止继承它。对于 Platypus 类,要将其声明为 final 的,可像下面这样做:

    class Platypus final: public Mammal, public Bird, public Reptile
    {
    public:
    	void Swim()
    	{
    		cout << "Platypus: Voila, I can swim!" << endl;
    	}
    };
    
    • 书中说:

      要建立 is-a 关系, 务必创建公有继承层次结构。
      要建立 has-a 关系,务必创建私有或保护继承层次结构。

      我不是很懂

  • 务必牢记,无论继承关系是什么,派生类都不能访问基类的私有成员。

    类的私有成员,除了类的方法和类的友元函数和友元类能访问,其他都不可以。

你可能感兴趣的:(C++,C++)