• 小甲鱼C++笔记(上)1-24


     OO思想:每个对象都是一个完整的独立的个体,由相关的属性和行为组合与外界分隔

     OO思想的特点:1封装 把对象的属性和方法结合成一个独立的系统单位,并尽可能隐藏内部细节

            2抽象 对一类公共问题进行统一描述的过程

            3继承 子类对象拥有与其基类相同的全部属性和方法

            4多态 在基类定义的属性和行为被子类继承后可以具有不同的数据类型或者表现行为等特性

     考察程序:数组求和

     1 #include<iostream>
     2 using namespace std;
     3 
     4 int add(int array[],int n);
     5 
     6 int main()
     7 {
     8     int a[] = {0,1,2,3,4,5,6,7,8,9};
     9     int size = sizeof(a) / sizeof (a[0]);
    10     cout<<"a:"<<sizeof(a)<<endl;
    11     cout<<"sum:"<<add(a,size)<<endl;    
    12     return 0;
    13 }
    14 
    15 int add(int array[],int n)
    16 {
    17     cout<<"array:"<<sizeof(array)<<endl;
    18     int sum = 0;
    19     for(int i = 0;i < n;i++)
    20     {
    21         sum += array[i];
    22     }
    23     return sum;
    24 }

      运行结果:

     

     这里的运行结果说明当数组名作为实参传递给形参后,数组名就变为指针指向数组的首地址,而在sizeof(a)中数组名仍为求整个数组的大小,什么时候数组名为整个数组,什么时候代表首地址,参见:数组与指针

     于是可以对add函数改写为:

    1 int add(int *p,int n)
    2 {
    3     int sum = 0;
    4     for(int i = 0;i < n;i++)
    5     {
    6         sum += *(p++);
    7     }
    8     return sum;
    9 }

     三

     考察程序:求一串带有空格的整数和

     1 #include<iostream>
     2 using namespace std;
     3 
     4 int main()
     5 {
     6     int sum = 0;
     7     int i;
     8     while(cin>>i)
     9     {
    10         sum += i;
    11         while(cin.peek() == ' ')
    12             cin.get();
    13         if(cin.peek() == '
    ')
    14             break;
    15     }
    16     cout<<"sum:"<<sum<<endl;
    17     return 0;
    18 } 

     运行结果:

     涉及到的几个问题有:

     1.cin如何检查输入:跳过空白(空格,换行,制表),直到遇到非空白符,读取字符。即读取从非空白字符开始,到与目标类型不匹配的第一个字符之间的全部内容。若输入与预期内容不匹配,返回false。程序中当输入3.3时,读取到. ,检查为false,跳出循环。

     2.cin.peek():peek()函数返回输入中的下一个字符,但不抽取输入流中的字符。仅查看,不抽取。

     更多关于输入输出,参见:C++输入与输出

    考察程序:读文件 写文件

     1 #include <iostream>
     2 #include <fstream>
     3 
     4 using namespace std;
     5 
     6 int main()
     7 {
     8     ifstream in("e:\test.txt");
     9 
    10     if(!in)
    11     {
    12         cerr<<"can not open the file"<<endl;
    13         return 0;
    14     }
    15 
    16     char x;
    17     while( in>>x )
    18         cout<<x;
    19 
    20     return 0;
    21 }
     1 #include <iostream>
     2 #include <fstream>
     3 
     4 using namespace std;
     5 
     6 int main()
     7 {
     8     ofstream out("e:\test.txt");
     9 
    10     if(!out.is_open())
    11     {
    12         cerr<<"can not open the file"<<endl;
    13         return 0;
    14     }
    15     
    16     for(int i = 0;i < 100;i++)
    17         out<<i;
    18 
    19     return 0;
    20 }

     涉及到的几个问题有:

    1.读文件用ifstream,写文件用ofstream。读写文件可以用fstream,以上三个类都#include<fstream>中

    2.打开文件用out方法,out方法可以接受两个参数,第二个参数为文件打开的模式,文件路径要用“”。

    3.检查文件是否打开可以直接判断对象,也可以使用is_open()方法,c++ primer plus推荐第二种(P689),检测到的打开错误更具体(如不适合的文件模式)。

    4.用cerr输出错误信息显得更职业,这个流没有缓冲(cout输出是有缓冲的)

    未尽事宜参见:C++文件读写

    考察函数重载(这里仅是参数个数不同)

     1 #include <iostream>
     2 
     3 using namespace std;
     4 
     5 int calc(int);
     6 int calc(int,int);
     7 int calc(int,int,int);
     8 
     9 int main()
    10 {
    11     cout<<"输入:"<<endl;
    12     int x,y,z;
    13     cin>>x>>y>>z;
    14     cout<<calc(x)<<endl;
    15     cout<<calc(x,y)<<endl;
    16     cout<<calc(x,y,z)<<endl;
    17 
    18     return 0;
    19 }
    20 
    21 int calc(int a)
    22 {
    23     return a*a;
    24 }
    25 
    26 int calc(int a,int b)
    27 {
    28     return a*b;
    29 }
    30 
    31 int calc(int a,int b,int c)
    32 {
    33     return a+b+c;
    34 }

      涉及到的几个问题有:

    1.仅当函数基本上执行相同任务,但使用不同形式的数据时才应采用重载

    2.重载要求函数的特征标(函数的参数列表)不同,不同的含义是参数类型不同,或者是参数个数不同,这与变量名的命名是无关的

    3.不能通过返回值不同来重载函数,因为函数调用时不需要返回值,编译器无法知道调用的哪个函数

    1.一定要牢记一个事实:指针所保存的是内存中的一个地址,它并不是指向的数据的值本身。因此务必确保指针对应一个已经存在的变量或者一块已经分配的内存——不要出现野指针

    2.*有两种用途,一是创建指针: int *p = &a;   另外是解引用  *p = 123;

    3.c++允许多个指针指向同一个地址

    4.void *则为“无类型指针”,可以指向任何数据类型。对一个无类型指针进行解引用前必须先将他转换为一个适当的数据类型。

    1 int * pint;
    2 void *pvoid;
    3 pvoid = pint; /* 不过不能 pint= pvoid; */

    如果要将pvoid赋给其他类型指针,则需要强制类型转换如:pint= (int *)pvoid;

    在ANSIC标准中,不允许对void指针进行算术运算如pvoid++或pvoid+=1等,而在GNU中则允许

     十

     1. 以下两句做同样的事情

    int *ptr1 = &myArray[0];

    int *ptr2 = myArray;  数组名相当于数组第一个元素的指针

    2. 数组指针变量存放的是地址,++是按照指向的数组的类型去递增的 +sizeof(类型);特别要注意不能对数组名进行类似++或--这样的运算,但是可以进行算术运算(因为常量是无法进行自加自减,但是可以进行算术运算)。事实上数组名并不是指针,也不是指针常量,只不过很多书里为了方便理解把数组名当成一个指针常量,正确理解是数组名只是一个符号而已,具体详见:数组与指针

    3. 函数模版,注意字符串数组的赋值,另外可见数组名可以作为形参传递给指针变量,指针变量是可以进行自加运算的

     1 #include <iostream>
     2 
     3 using namespace std;
     4 
     5 template<class T>
     6 
     7 void print(T *i, T *j)
     8 {
     9     while( i != j)
    10     {
    11         cout<<*i;
    12         i++;
    13     }
    14 }
    15 
    16 int main()
    17 {
    18     int num[6] = {1,2,3,4,5,6};
    19     char chatter[6] = {'h','e','l','l','o',''};
    20     print(num,num+6);
    21     cout<<endl;
    22     print(chatter,chatter+5);
    23 
    24     return 0;
    25 }

     十二

    传值、传地址、和传引用

     1 #include <iostream>
     2 
     3 using namespace std;
     4 
     5 void swap1(int a,int b)
     6 {
     7     int temp = a;
     8     b = a;
     9     a = temp;
    10     cout<<"a:"<<a<<"    b:"<<b<<endl;
    11 }
    12 
    13 void swap2(int *a,int *b)
    14 {
    15     int temp = *a;
    16     *a = *b;
    17     *b = temp;
    18     cout<<"a:"<<*a<<"    b:"<<*b<<endl;
    19 }
    20 
    21 void swap3(int &a,int &b)
    22 {
    23     a ^= b;
    24     b ^= a;
    25     a ^= b;
    26     cout<<"a:"<<a<<"    b:"<<b<<endl;
    27 }
    28 
    29 int main()
    30 {
    31     int x=3,y=5;
    32     swap1(x,y); //传值
    33     cout<<"x:"<<x<<"    y:"<<y<<endl;
    34     swap2(&x,&y);   //传地址
    35     cout<<"x:"<<x<<"    y:"<<y<<endl;
    36     swap3(x,y);     //传引用
    37     cout<<"x:"<<x<<"    y:"<<y<<endl;
    38 
    39     return 0;
    40 }

    注意这里在传引用的时候采用了异或运算交换数据,这样可以少用一个变量

    十三

    枚举中不需要引号,因为枚举值不是字符串,编译器会按照各个枚举值在定义时出现的先后顺序把它们从0——n-1分别对应起来

    使用枚举有两个好处:

    对变量的可选值加以限制

    可以用作switch条件语句的case标号(因为字符串是不能用作标号的)

    注意:

    枚举元素不是字符常量也不是字符串常量,不能再程序用用赋值语句在对它赋值

    只能把枚举值赋予枚举变量,不能把元素数值直接赋予枚举变量

    枚举变量接收输入只能用这种形式输入数字:

    enum weekdays {Monday,Tuesday,Wednesday,Thursday,Friday};
    weekdays day;
    cin>>(int&)day;

    十六

    this指针是在类成员函数中使用的,当存在二义性隐患(比如成员函数的参数与类中属性重名时)才使用:this->a = a

    this指针可以指向属性或方法

    静态成员函数没有this

    十七  理解继承中的构造器和析构器

    构造器和析构器不需要返回类型,构造函数和析构函数要放在public中

    程序调用步骤:基类构造器->子类构造器->子类析构器->基类析构器,通过以下程序可以看出

     1 #include <iostream>
     2 #include <string>
     3 
     4 using namespace std;
     5 
     6 class FatherClass
     7 {
     8 public:
     9     FatherClass();
    10     ~FatherClass();
    11 };
    12 
    13 FatherClass::FatherClass()
    14 {
    15     cout << "this is FatherClass constructor"<<endl;
    16 }
    17 
    18 FatherClass::~FatherClass()
    19 {
    20     cout << "this is FatherClass finalizer"<<endl;
    21 }
    22 
    23 class ChildClass:public FatherClass
    24 {
    25 public:
    26     ChildClass();
    27     ~ChildClass();
    28 };
    29 
    30 ChildClass::ChildClass()
    31 {
    32     cout << "this is ChlidClass constructor"<<endl;
    33 }
    34 
    35 ChildClass::~ChildClass()
    36 {
    37     cout << "this is ChildClass finalizer"<<endl;
    38 }
    39 
    40 int main()
    41 {
    42     ChildClass a;
    43     cout << "finished"<<endl;
    44     return 0;
    45 }

    执行结果:

    十八

    基类和子类之间的关系应该自然和清晰

    构造器的设计设计越简单越好

    public:任何人都可以访问

    protected:这个类和它的子类可以访问

    private:只有这个类本身可以访问

    定义类代码时,应该从public:开始写起,然后是protected:最后是private:

    类的继承也有public protected  private ,一般都用public

    类中的方法可以重载,但是不要对从基类继承来的方法重载

    十九

    构造方法用来初始化类的对象,与父类的其它成员不同,它不能被子类继承(子类可以继承父类所有的成员变量和成员方法,但不继承父类的构造方法)。因此,在创建子类对象时,为了初始化从父类继承来的数据成员,系统需要调用其父类的构造方法。如果没有显式的构造函数,编译器会给一个默认的构造函数,并且该默认的构造函数仅仅在没有显式地声明构造函数情况下创建。

    构造原则如下:

    1. 如果子类没有定义构造方法,则调用父类的无参数的构造方法。
    2. 如果子类定义了构造方法,不论是无参数还是带参数,在创建子类的对象的时候,首先执行父类无参数的构造方法,然后执行自己的构造方法。
    3. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数,则会调用父类的默认无参构造函数。
    4. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类自己提供了无参构造函数,则会调用父类自己的无参构造函数。
    5. 在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类只定义了自己的有参构造函数,则会出错(如果父类只有有参数的构造方法,则子类必须显示调用此带参构造方法)。
    6. 如果子类调用父类带参数的构造方法,需要用初始化父类成员对象的方式,如下:

     1 #include <iostream>
     2 #include <string>
     3 
     4 using namespace std;
     5 
     6 class Animals
     7 {
     8 public:
     9     string name;
    10     string color;
    11     Animals(string,string);
    12     void eat();//父类的一个方法
    13     void eat(int);//对父类eat()方法的一个重载
    14     void output();
    15 };
    16 
    17 Animals::Animals(string name,string color)
    18 {
    19     this->name = name;
    20     this->color = color;
    21 }
    22 
    23 void Animals::eat()
    24 {
    25     cout<<"Animals is eating"<<endl;
    26 }
    27 
    28 void Animals::eat(int n)
    29 {
    30     cout<<"Animals eat "<<n<<" appales "<<endl;
    31 }
    32 
    33 void Animals::output()
    34 {
    35     cout<<"name:"<<name<<"
    color:"<<color<<endl;
    36 }
    37 
    38 class Pig : public Animals
    39 {
    40 public:
    41     Pig(string,string);
    42     void eat();//对父类eat()的一个覆盖
    43 };
    44 
    45 //这里是对父类带参数构造函数的一个调用,注意不是继承,构造函数是无法继承的
    46 Pig::Pig(string tname,string tcolor) : Animals(tname,tcolor)
    47 {
    48 }
    49 
    50 void Pig::eat()
    51 {
    52     cout<<"Pig is eating"<<endl;
    53 }
    54 
    55 int main()
    56 {
    57     Animals a("dog","black");
    58     a.output();
    59     a.eat();
    60     a.eat(15);//重载
    61     Pig b("xiaohua","white");
    62     b.output();
    63     b.eat();//覆盖
    64 
    65     //b.eat(15);
    66     //注意这里不能再使用父类中的重载了,否则出错,因为在子类中已经对父类中的eat()覆盖了
    67 
    68     return 0;
    69 }

    执行结果:

    注意:

    1. 程序在基类中对构造函数进行重载,在子类中对构造函数进行重构

    2. 子类的构造函数是对父类构造函数的一个调用,调用用:表示,格式:子类构造函数:父类构造函数

    参数从子类的构造函数传递到基类的参数,然后有基类的构造函数处理,若子类还有其他变量,可以在子类的构造函数中进行处理

    3. 在创建对象后括号用的参数传递给构造函数(等价与调用含参的构造函数)

    二十

    友元机制允许一个类将对其非公有成员的访问授权指定的函数或者类,友元的声明从关键字friend开始。它只能出现在类定义的内部。友元声明可以出现在类中任何地方,将友元声明成组地放在类定义的开始或结尾是个好主意。下面分友元函数和友元类讲解:

    友元函数:

    结合着类的特性和类中一般成员函数,我们可以这样理解:类具有封装和信息隐藏的特性。只有类的成员函数才能访问类的私有成员,程序中的其他函数是无法访问私有成员的。非成员函数可以访问类中的公有成员,但是如果将数据成员都定义为公有的,这又破坏了隐藏的特性。另外,应该看到在某些情况下,特别是在对某些成员函数多次调用时,由于参数传递,类型检查和安全性检查等都需要时间开销,而影响程序的运行效率。

    为了解决上述问题,提出一种使用友元的方案。友元是一种定义在类外部的普通函数,但它需要在类体内进行说明,为了与该类的成员函数加以区别,在说明时前面加以关键字friend。友元不是成员函数,但是它可以访问类中的私有成员。友元的作用在于提高程序的运行效率,但是,它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员。

    自己总结友元函数的特点:只是程序中普通的函数,在类中声明,类外定义(不要以为在类中声明就当成该类的成员函数)

    友元类:

    友元除了前面讲过的函数以外,友元还可以是类,即一个类可以作另一个类的友元。当一个类作为另一个类的友元时,这就意味着这个类的所有成员函数都是另一个类的友元函数。

    使用友元类时注意:

    (1) 友元关系不能被继承。

    (2) 友元关系是单向的,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。

    (3) 友元关系不具有传递性。若类B是类A的友元,类C是B的友元,类C不一定是类A的友元,同样要看类中是否有相应的申明

    总结起来:

    (1)友元关系不可以继承,但对已有的方法来说访问权限不改变。

    (2)如果改写基类的方法则访问权限改变

    (3)友元关系不具有传递性

    若类B是类A的友元,类C是B的友元,类C不一定是类A的友元。

    举一个简单的友元函数友元类的例子:

     1 #include <iostream>
     2 
     3 using namespace std;
     4 
     5 class A
     6 {
     7 public:
     8     A(int,int);
     9 private:
    10     int x;
    11     int y;
    12     friend class B;//友元类
    13     friend swap(A *);
    14 };
    15 
    16 A::A(int x,int y)
    17 {
    18     this->x = x;
    19     this->y = y;
    20 }
    21 
    22 class B
    23 {
    24 public:
    25     void output(A *);
    26 };
    27 
    28 void B::output(A *q)
    29 {
    30     cout<<"友元类"<<endl;
    31     cout<<"a.x:"<<q->x<<endl;
    32     cout<<"a.y:"<<q->y<<endl;
    33 }
    34 
    35 swap(A *q)
    36 {
    37     cout<<"友元函数swap"<<endl;
    38     int temp;
    39     temp = q->x;
    40     q->x = q->y;
    41     q->y = temp;
    42     cout<<"a.x:"<<q->x<<endl;
    43     cout<<"a.y:"<<q->y<<endl;
    44 }
    45 
    46 int main()
    47 {
    48     A a(3,5);
    49     B b;
    50     b.output(&a);
    51     swap(&a);
    52     return 0;
    53 }

    二十一  二十二  静态成员和静态方法

    【转载】C/C++中static关键字作用总结:http://www.cnblogs.com/biyeymyhjob/archive/2012/07/19/2598815.html

    【转载】C++内存分配方式详解——堆、栈、自由存储区、全局/静态存储区和常量存储区:http://www.cnblogs.com/daocaoren/archive/2011/06/29/2092957.html

    static作用简单来说:

    局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期,把全局变量改变为静态变量后是改变了它的作用域

    对于类

    静态成员是所有对象共享的,是类的成员而不是某个对象的成员,所以:

    1. 静态方法只能访问静态成员,而不能访问非静态成员;而非静态方法可以访问静态成员

    2. this指针指向的是一个对象的地址,因为静态成员和静态函数属于整个类而不是属于对象,故不能用this指针

    3. 静态方法用类的名字来调用而不要用对象的方式来调用(尽管可以)  classname::methodname()

    4. 静态数据成员是静态存储的,所以必须对它进行初始化

      静态成员初始化与一般数据成员初始化不同:

        初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆;

        初始化时不加该成员的访问权限控制符private,public等; 

        初始化时使用作用域运算符来标明它所属类;

        所以我们得出静态数据成员初始化的格式:

        <数据类型><类名>::<静态数据成员名>=<值>

    5. 不能将静态成员函数定义为虚函数

    6. static并没有增加程序的时空开销,相反她还缩短了子类对父类静态成员的访问时间,节省了子类的内存空间

     1 #include <iostream>
     2 #include <string>
     3 
     4 using namespace std;
     5 
     6 class Stu
     7 {
     8 public:
     9     Stu(string);
    10     ~Stu();
    11     static get_count();
    12 private:
    13     string name;
    14     static int count;
    15 };
    16 
    17 int Stu::count = 0; //静态成员必须在类外初始化
    18 
    19 Stu::Stu(string name)
    20 {
    21     this->name = name;
    22     count++;
    23 }
    24 
    25 Stu::~Stu()
    26 {
    27     count--;
    28 }
    29 
    30 int Stu::get_count()
    31 {
    32     return count;   //静态函数只能调用静态成员
    33 }
    34 
    35 int main()
    36 {
    37     Stu s1("zhao");
    38     cout<<"The number of student is "<<Stu::get_count()<<endl;//对静态方法用类名来调用而不是用对象
    39     Stu s2("qian");
    40     cout<<"The number of student is "<<Stu::get_count()<<endl;
    41     {
    42         Stu s3("sun");
    43         cout<<"The number of student is "<<Stu::get_count()<<endl;
    44         Stu s4("li");
    45         cout<<"The number of student is "<<Stu::get_count()<<endl;
    46     }
    47     cout<<"The number of student is "<<Stu::get_count()<<endl;
    48 
    49     return 0;
    50 }

    从这个程序中可以可看出构造函数和析构函数其做用的范围是对象的的作用域{}

    二十三  二十四  虚函数和纯虚函数

    1. 虚函数和纯虚函数可以定义在同一个类(class)中,含有纯虚函数的类被称为抽象类(abstract class),而只含有虚函数的类(class)不能被称为抽象类(abstract class)。 

    2. 虚函数可以被直接使用,也可以被子类(sub class)重载以后以多态的形式调用,而纯虚函数必须在子类(sub class)中实现该函数才可以使用,因为纯虚函数在基类(base class)只有声明而没有定义。

    3. 虚函数和纯虚函数都可以在子类(sub class)中被重载,以多态的形式被调用。

    4. 虚函数和纯虚函数通常存在于抽象基类(abstract base class -ABC)之中,被继承的子类重载,目的是提供一个统一的接口。

    5. 虚函数的定义形式:virtual {method body}

        纯虚函数的定义形式:virtual { } = 0;

    在虚函数和纯虚函数的定义中不能有static标识符,原因很简单,被static修饰的函数在编译时候要求前期bind,然而虚函数却是动态绑定(run-time bind),而且被两者修饰的函数生命周期(life recycle)也不一样。

    6. 虚函数必须实现,如果不实现,编译器将报错,错误提示为:

    error LNK****: unresolved external symbol "public: virtual void __thiscall
    ClassName::virtualFunctionName(void)"

    7. 对于虚函数来说,父类和子类都有各自的版本。由多态方式调用的时候动态绑定。

    8. 实现了纯虚函数的子类,该纯虚函数在子类中就编程了虚函数,子类的子类即孙子类可以覆盖该虚函数,由多态方式调用的时候动态绑定。

    9. 虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数

    10. 多态性指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。

    a.编译时多态性:通过重载函数实现
    b 运行时多态性:通过虚函数实现。

    11. 如果一个类中含有纯虚函数,那么任何试图对该类进行实例化的语句都将导致错误的产生,因为抽象基类(ABC)是不能被直接调用的。必须被子类继承重载以后,根据要求调用其子类的方法。

    具体例子参见:多态,虚函数,纯虚函数

  • 相关阅读:
    kuryr环境搭建
    使用docker搭建kafka环境
    记一次解决curl https证书问题
    一篇精彩的洗地文
    个人知识管理利器wiz
    Markdown写作
    用Bottle开发web程序(二)
    用Bottle开发web程序(一)
    Magnum Kubernetes源码分析(二)
    DVWA(九):File Upload 全等级文件上传
  • 原文地址:https://www.cnblogs.com/raichen/p/4156873.html
Copyright © 2020-2023  润新知