C++学习笔记

来源:互联网 发布:js 数值比大小 编辑:程序博客网 时间:2024/05/16 07:10

C++入门课程学习笔记

从一个小程序说起

  1. 数组名就相当于数组的首地址;
  2. 将函数的参数声明为数组和声明为指针是一样的;
  3. .h是C92标准;

4从另一个小程序接着说

  1. main函数有两个参数,整型变量argc和字符指针数组argv[];argc含义是程序的参数数量,包含本身,argv[]的每个指针指向命令行的一个字符串;

5输出输入小结

  1. cin对象有几个专门用来报告其工作情况的成员函数,它们返回一个真/假值来表明cin的状态;
    • eof():如果达到文件(或输入)末尾,返回true;
    • fail()如果cin无法工作,返回true;
    • bad()如果cin因为比较严重的原因(例如内存不足)而无法工作,返回true;

6函数的重载

  1. 只能通过不同的参数进行重载的,但不能通过不同的返回值;
  2. 函数重载的目的是为了方便对不同数据类型进行同样的处理。

8复杂数据类型-指针

  1. 指针是专门用来存放地址的特殊类型变量;
  2. C++支持无类型(void)指针,void *vPointer,注意:对一个无类型指针进行解引用前,必须把它转换为一种适当的数据类

10指针和数组

  1. 数组名就是一个指针(指向数组的基地址,就是第一个元素的地址),一下两句意思一样:
    int *ptr1=&myArray[0];
    int *ptr2=myArray

12类型别名

  • typedef int* intPointer

13介绍对象

  1. 类名的第一个字母采用大写是一种习惯;类声明末尾必须有一个分号;
  2. 有些程序员喜欢把类的声明和类的定义分别存入两个不同的文件,前者存入.h头文件,后者存入相应的.cpp文件。
  3. C++ 允许在类中声明常量,但不允许对它进行赋值。

15构造器和析构器

  1. 构造器特点:
    • 构造器的名字必须和它所在的类的名字一样;
    • 系统在创建某个类的实例时会第一时间自动调用这个类的构造器;
    • 构造器永远不会返回任何值;
  2. 创建构造器,需要先把它的声明添加到类中。(注意大小写与类名保持一致)
    class Car
    {
    Car(void);
    }

  3. 析构器的特点:

    • 析构器永远不返回任何值;
    • 析构器是不带参数的,所以析构器的声明永远是如下格式:~ClassName();
    • 在比较复杂的类中,析构器很重要(可能引起内存泄漏)

this指针和类的继承

  1. 使用this指针的基本原则是:如果代码不存在二义性隐患,就不需要使用!
  2. class SubClass : public SuperClass{ }

2017/4/2 9:50:00

18访问控制

  1. protected 允许这个类和它的子类访问;
  2. privite 只有这个类本身
  3. class Pig : public Animal{}
    • public 是在告诉编译器,继承的方法和属性的访问级别不发生任何改变,即public仍可以被所有代码访问,protected 只能由基类的子类访问,private 只能由基类本身访问。
    • protected 这将使得这个子类外部的代码无法通过子类去访问基类中的 public
    • private 只有这个子类可以使用它从基类继承来的元素
    • !!!一般都只用 public

19 覆盖和重载

  1. 对方法进行重载一定要有的放矢,重载的方法越多,程序就越不容易看懂;
  2. 只要输入参数和返回值与原来不一样,它就是重载;

20 友元关系

  1. 友元关系是类之间的一个特殊关系,这种关系不仅允许友元类访问对方的 public 方法和属性,还允许友元访问对方的 protected 和 private 方法和属性。

2017/4/3 10:05:25

22 静态属性和方法

  1. 静态成员是所有对象共享的,所以不能在静态方法里访问非静态的元素;
  2. 非静态方法可以访问类的静态成员,也可以访问类的非静态成员;
  3. 再论this指针
    • 每当我们调用一个方法的时候,this指针都会随着你提供的输入参数被秘密的传递给那个方法;
    • 正因为如此,我们才能在方法里像使用一个局部变量那样使用this指针;
    • 因为静态方法不是属于某个特定的对象,而是由全体对象共享的,这就意味着它们无法访问this指针。所以无法在静态方法里访问非静态的类成员。
  4. 在使用静态属性的时候,不要忘记为他们分配内存。具体做法:只要在类声明的外部对静态属性做出声明即可。
  5. 静态方法可以使用一个普通方法的调用语法来调用,但建议不要这么做!!
    • 坚持用:ClassName::methodName();
    • 不要使用:objectName.methodName();

23 虚方法

  1. 虚方法是继承的,一旦在基类里把某个方法声明为虚方法,在子类里就不可能再把它声明为非虚方法;
  2. TIPS
    • 如果拿不准要不要把某个方法申明为虚方法,那么就把他申明为虚方法好了;
    • 在基类里把所有的方法都声明为虚方法会让最终生成的可执行代码的速度变得稍慢一些,但好处可以一劳永逸地确保程序的行为符合你的预期;
    • 在实现一个多层次的类继承关系的时候,最顶级的基类应该只有虚方法;
    • 析构器都是虚方法!从编译的角度看:它们只是普通的方法。如果它们不是虚方法,编译器就会根据它们在编译时的类型而调用那个在基类里定义的版本(构造器),那样往往会导致内存泄漏!

24抽象方法

  1. 把某个方法申明为一个抽象方法等于告诉编译器这个方法必不可少,但我现在(在这个基类里)还不能为他提供一个实现;
  2. 抽象方法的语法:在声明一个虚方法的基础上,在原型的末尾加上“=0”。(告诉编译器不用浪费时间在这个类里寻找这个方法的实现)
  3. 多态性
    • 多态性是指用一个名字定义不同的函数,调用同一个名字的函数,却执行不同的操作,从而实现“一个接口,多种方法”
  4. 多态如何实现绑定?
    • 编译时的多态:重载
    • 运行时:虚函数
  5. 编译时的多态性特点是运行速度快,运行时的特点是高度灵活性和抽象;
  6. 析构函数解析 析构函数都是虚方法是为了当一个基类的指针删除一个派生类的对象时,派生类的析构函数可以被正确调用;
    • 当类里面有虚函数的时候,编译器会给类添加一个虚函数表,里边存放着虚函数指针。为了节省资源,只有当一个类被用来作为基类的时候,我们才把析构函数写成虚函数!

25运算符重载

  1. 函数类型 operator 运算符名称(形参表列)
    {
    对运算符重载的处理
    }
  2. C++不允许用户自己定义新的运算符,只能对已有的C++运算符进行重载;
  3. 以下5个运算符不允许重载:
    • . 成员访问运算符
    • .* 成员指针访问运算符
    • :: 域运算符
    • sizeof 尺寸运算符
    • ?: 条件运算符
  4. 重载不能改变运算符运算对象(操作数)个数
  5. 重载不能改变运算符优先级别;
  6. 重载不能改变运算符的结合性;
  7. 重载运算符的函数不能有默认的参数;
  8. 重载的运算符必须和用户定义的自定义类型的对象一起使用,其参数至少应该有一个是类对象或类对象的引用。(也就是说,参数不能全部都是C++标准类型,这样约定是为了防止用户修改用于标准类型结构的运算符性质)。

2017/4/4 10:08:24

29虚继承

  1. 通过虚继承某个基类,就是在告诉编译器:从当前这个类再派生出来的子类只能拥有那个基类的一个实例;
  2. 语法class Teacher : virtual public Person {}

2017/4/5 11:08:00

32assert函数和捕获异常

  1. 使用异常的基本原则:应该只用它们处理确实可能不正常的情况;
  2. 在构造器和析构器里不应该使用异常;
  3. 如果try语句块无法找到一个与之匹配的catch语句块,它抛出的异常将中止程序的执行;
  4. C++标准库里有一个名为 exception的文件,该文件声明了一个exception 的基类,可以用这个基类来创建个人的子类以管理异常;
  5. 有经验的程序员常常这么做,而如此抛出和捕获的是exception类或其子类的对象;
  6. 以“值传递”方式抛出异常,以“引用传递”方式捕获异常对象;

33动态内存管理

  1. 动态内存由一些没有名字、只有地址的内存块构成,那些内存块是在程序运行期间动态分配的;
  2. 如果没有足够的可用内存空间,那么new 语句将抛出std::bad_alloc 异常!
  3. 在用完内存块之后,要用 delete语句把它还给内存池。另外作为一种附加的保险措施,在释放内存块之后还应该把与之关联的指针设置为 NULL;
  4. 注意 静态内存这个术语与C++保留字static没有任何关系。静态内存是指内存块的长度在程序编译时被设定为一个固定的值,而这个值在程序运行时是无法改变的。

34动态数组

  1. 新建一个动态数组 int *x=new int[10]
  2. 可以像对待一个数组那样使用指针变量x: x[1]=45
  3. 删除动态数组,因为用来保存数组地址的变量只是一个简单的指针,所以需要明确地告诉编译器它应该删除一个数组!
  4. delete []x;

35从函数或方法返回内存

  1. 动态内存的另一个常见用途是:让函数申请并返回一个指向内存块的指针。 掌握这个技巧很重要,尤其是在你打算使用由别人编写的库文件时;
  2. 思路:在函数里调用new语句为某种对象或某种基本数据类型分配一块内存,再把那块内存的地址返回给程序的主代码,主代码将使用那块内存并在完成有关操作后立刻释放;
  3. 为什么不应该让函数返回一个指向局部变量的指针?
    • 任何一个函数都不应该把它自己的局部变量的指针作为他的返回值!因为局部变量在栈里,函数结束自动会释放。
  4. 如果想让一个函数在不会留下任何隐患的情况下返回一个指针,那它只能是一个动态分配的内存块的基地址。
  5. 函数指针、指针函数
    • 指向函数首地址的指针变量称为函数指针

2017/4/6 22:01:46

37高级强制类型转换

  1. 语法:dynamic_cast<MyClass*>(value) ,用来把一种类型的对象指针安全地强制转换为另一种类型的对象指针。如果value 的类型不是一个MyClass类(或者MyClass的子类)的指针,这个操作符将返回NULL。

38避免内存泄漏

  1. 内存作用域
    • 动态内存不存在作用域的问题,一旦被分配,内存块就可以在程序的任何地方使用;
    • 因为动态内存没有作用域,所以必须由程序员来跟踪它们的使用情况,并在不需要用到它们的时候把它们及时归还给系统;
    • 虽然动态分配的内存块没有作用域,但用来保存其地址的指针变量是受作用域影响的。

2017/4/7 10:28:55

39命名空间和模块化编程

  1. 虽说头文件可以用来保存任意代码片段,但典型的做法是只用它们来保存函数声明、用户自定义类型数据(结构和类)、模板和全局性的常量;
  2. 如果你有一个程序需要多次调用一个或一组函数,或是你有一个或一组函数需要在多个程序调用,就应该把它们的声明拿出来放到一个头文件里。
  3. 头文件应该只包含最必要的代码,比如只声明一个类或者只包含一组彼此相关的函数;

42链接和作用域

  1. 存储类
    • 每个变量都有一个存储类,它决定着程序将把变量的值存储在计算机上的什么地方、如何存储,以及变量应该有着怎样的作用域。
    • 默认的存储类是auto,但你不会经常看到这个关键字;
    • 自动变量存储在栈(stack)的临时内存里并有着最小的作用域,当程序执行到语句块或函数末尾的右花括号时,它们将被系统回收(栈回收);
    • 与auto不同的是static,它在程序的生命周期内将一直保有它的值而不会消亡,因为它们是存储在静态存储区,生命周期从申请到程序退出(和全局变量一样);一个static变量可以有external 或 internal 链接;
    • 第3种存储类是extern,它在有多个翻译单元时非常重要。这个关键字用来把另一个翻译单元里的某个变量声明为本翻译单元里的一个同名全局变量;(编译器不会为extern 变量分配内存,因为在其他地方已经为它分配过)
  2. 链接分为3种情况,凡是有名字的东西(函数、类、常量、变量、模板、命名空间、等等)必然属于其中之一:外链接(external),内链接(internal),无链接(none);
  3. 外链接的意思是每个翻译单元都可以访问这个东西(前提是只要它知道有这么 个东西存在)。
  4. 内链接含义:在某个翻译单元里定义的东西只能在翻译单元里使用,在任何函数以外定义的静态变量都有内链接:
    • this.cpp static int d=8;
    • that.cpp static int d=9;
      -这两个文件各有一个同名的变量,但它们是毫不相干的两样东西;
  5. 在函数里定义的变量只存在于该函数的内部,根本没有任何链接(none);

44函数模板

  1. 语法,定义一个名为foo()的函数

    template<class T>

    void foo(T param){ }   
  2. 在创建模板时,还可以用 template来代替template ,它们的含义是一样的;
  3. 注意 template 中的class 并不意味着T只能是一个类;
  4. 不要把函数模板分成原型和实现两个部分。如果编译器看不到模板的完整代码,它就无法正确生成代码。
  5. 为了明确地表明swap()是一个函数模板,还可以使用swap(i1,i2)语法来调用这个函数。这将明确地告诉编译器它应该使用哪一种类型。
  6. 如果某个函数对所有数据类型都将进行同样的处理,就应该把它编写为一个模板
  7. 如果某个函数对不同的数据类型将进行不同的处理,就应该对它进行重载。

类模板

  1. 类模板与函数模板非常相似:同样是先由你编写一个类的模板,再由编译器在你第一次使用这个模板时生成实际代码;
  2. template<class T>

    class MyClass {       MyClass();    void swap(T &a, T &b);}
  3. 构造器的实现:

    MyClass<T>::MyClass(){    //初始化操作}

    因为MyClass是一个类模板,所以不能只写出MyClass::MyClass(),编译器需要你在这里给出一种与MyClass() 配合使用的数据类型,必须在尖括号里提供它。因为没有确定的数据类型可以提供,所以使用一个 T 作为占位符即可;

45内联函数

  1. 引入内联函数的目的是为了解决程序中函数调用的效率问题;
  2. 内联函数从源代码层看,有函数的结构,而在编译后却不具备函数的性质。编译时,类似宏替换,使用函数体替换调用处的函数名;
  3. 一般在代码中 用 inline 修饰,但能否形成内联函数,需要看编译器对该函数定义的具体;

46内联模板

  1. 在创建类模板时,避免类声明和类定义相分离的一个好办法是使用内联方法;
  2. 在类里,内联方法的基本含义是在声明该方法的同时还对它进行定义;
  3. 除了可以更好地帮助编译器处理类模板之外,使用内联方法还有一个很好地作用:可以让你少打字并且使源代码的可读性更好;
  4. 注意 C++并没有限制只能使用一个类型占位符,如果类模板需要一种以上的类型,根据实际多使用几个占位符即可。

    template <class T,class U>class MyClass{}

    在实例化时,我们只需要这么做:
    MyClass<int,float>myClass;

原创粉丝点击