C++学习笔记

 1、var1[] 和 *var2 区别在于var1的大小是数组,实际是指针;var2的大小和实际都是指针。在参数传递的时候var1“退化”为一个指针。
   var1[] 和 *var2出了 sizeof 时是不相同的,其他时候完全等价。
   var1[] = " "时也是有 "\0"的,"\0"代表:空字符(null);只要是用“”引起来的结尾都有‘\0’(错误:char d[5]=”hello”; )
   char c[] = "hello world";分配的是局部数组;
   char *c = "hello world";分配的是全局数组;
   数组元素a[i]的地址是&a[i];
   p[i][j] =  *(*(p+i)+j) = *(p[i]+j) = *(p+i)[j]
   int (*p)[5] ;代表一个数组指针指向一个含有5个int型元素的数组;//不加括号与int结合,例如:int *p();    
   int *p[5];是定义了一个指针数组,数组的元素的int型指针。

   
   


2、结构体的长度一定是“最长数据元素的整数倍(无论是数组,结构,联合,类,都是按其最小的单元算)”。在这个前提下,小数据元素可以合并内存存放。用sizeof计算大小时,stastic数据类型不算在内,因为其保存在全局区,这里只计算栈区的大小。
 struct 类型的内存分配是结合内部的所有变量大小和相关的,但不是简单的相加,从上面的例子就可以看出来, struct 类型的分配的内存大小是应该内部最大数据类型的整数倍,不足一倍的部分会自动补全成一倍大小,应该是编译器为了数据整齐,处理速度会快些把。
 union 类型的内存分配,只是和联合体内的最大的一个数据类型的大小相关,而且 union 内的所有数据都在一个单独的内存空间里面。

 union A //这里用union是24,用struct是32.
 {
  int a[5];    //这里虽然是20,但“对其标准”为double的8字节,
  char b;
  double c;
 };

 struct B
 {
  int n;
  A a;          //“最长数据元素的整数倍”所谓最长元素不是指A的大小,而是A中“对其标准”的大小;
  char c[10];
 }
             sizeof(B) = ?

 

3、int和long和float都是4个字节
   double是8个字节
   short是2个字节
   long double是10个字节
   指针*类型是4个字节
   正数的原反补码相同;负数的补码为符号位不变按位取反(负数的符号位是"1");负数的补码为取反“加1”;
   有符号数原反补码真值表示范围:原:-(2)n-1至+(2)n-1;
         反:-(2)n-1至+(2)n-1;
         补:-(2)n至+(2)n-1
   无符号数真值表示范围:0至+(2)n;
   内存里凡是负数用补码表示;计算机只有加法寄存器,减法操作用补码表示后,全部换位加法操作;正数的“原反补”码相同; 


4、“小端模式”是指数据的“低”位保存在内存的“低”地址中。

5、虚函数的作用在于使用同一个父类的指针,可以调用到不同子类的函数,前提是父类指针被赋以不同子类对象的地址。

6、string类的构造,析构,拷贝构造,赋值函数:类的定义中写好四个函数的声明以及“char *m_data”用来保存字符串,在外部写实现。构造和拷贝构造都是三句话,即获取长度,开辟加一空间,和strcpy。
   区别在于构造函数的参数为const char *str,拷贝构造和赋值函数为const string &other。

7、拷贝构造函数和赋值函数的区别在于拷贝构造函数生成新的类对象,不用检查新对象和原对象是否相同,赋值运算中原对象的内存分配需要释放。
  拷贝构造函数的名称必须与类名称一致, 函数的形式参数(形参) 必须是本类型的一个引用类型的变量, 并且是唯一的、不可改变的参数。
 1)拷贝构造函数是一个对象初始化一块内存区域,这块内存就是新对象的内存区。 赋值函数是对一个已经被初始化的对象进行赋值操作。
 2)拷贝构造函数大多数情况下是复制,赋值函数则是引用对象。
 3)拷贝构造函数首先是个构造函数,它调用的时候是通过参数的对象初始化产生一个对象。 赋值函数则是把一个新的对象赋值给一个原来的对象, 所以如果原来的对象中有内存分配要先把内存释放掉,而且还要检查一下两个对象是不是同一个对象,如果是,则不做任何操作。

 
   深拷贝和浅拷贝的定义可以简单理解成:如果一个类拥有资源(堆,或者是其它系统资源),当这个类的对象发生复制过程的时候,这个过程就可以叫做深拷贝,反之对象存在资源但复制过程并未复制资源的情况视为浅拷贝.
   深拷贝:显示定义拷贝构造函数, 使其不但可以复制数据成员,而且可以为对象分配内存空间。
   浅拷贝:对默认拷贝构造函数所实现的数据成员逐一赋值, 如果类中含有指针类型数据,将会产生错误。


8、虚基类不同于抽象类。虚基类的概念:如果想使公共基类只产生一个拷贝,则可以将这个基类说明为虚基类(即虚继承)。例如两个派生类同时修改了一个基类的数据成员,使用虚继承可以使
   派生类中公用一份基类的数据成员。

9、1)public:无论在类内部(成员函数的访问)还是外部(类对象的访问)都可以访问   protect:类内部可以访问,外部不可以,派生类内部可以访问  private:只有自己类的内部可以访问
     2)保护继承和私有继承的区别:保护继承之后,派生类的特性为protected、protected、不可访问(private)。对于派生类的对象还是“可以”访问到的(访问的实际上是基类成员,派生类内部自然也是可以访问的);继续往下派生,就只能二级派生类内部访问了,二级派生类的对象是不能访问了。
               私有继承之后,派生类的特性为private、private、不可访问(private)。对于派生类的对象是“不可”访问到的(访问的实际上是基类成员,派生类内部是可以访问的,但是最后一级);继续往下派生,就算是二级派生类内部也不能访问。
            公有继承之后,派生类的特性为不变、不变、不可访问(private)。
  3) 派生类中的静态成员:不管公有派生类还是私有派生类,都不影响派生类对基类的静态成员的访问,派生类对基类静态成员必须显式使用以下形式:类名 :: 成员
  4)派生类的继承方式,可以认为是继承了之后对于自身(派生类)的三种权限,例如基类中为public的数据成员int a,使用private继承,则继承完之后对于子类而言这个a就是自己的一个private成员。
    实例化子类时,调用父类的构造函数的意义是生成一个父类对象,这个父类对象位于子类对象的内部(父类对象包括虚函数表指针),所以子类大小是[父类大小+子类大小]。
    类的大小=类实例化对象的大小。

10、1) 无论一个类中有几个虚函数,只要有一个,那么在这个类和这个类的对象中的首地址中都保存了一个虚函数表指针,这个虚表指针指向一个虚函数表,虚函数表类存放的是虚函数的函数指针(虚函数的入口地址),
  virtual修饰符会被隐含地继承,子类中重写父类虚函数时,virtual修饰符可加可不加。
 2) 在9中说明过,子类对象中保存了父类的的一个实例化复本,这个副本中含有父类的虚函数表指针,然而子类有自己的虚函数表指针
 (子类的这个指针要么是父类有虚函数自己没有从而继承而来,要么是自己有虚函数属于自己,反正类和实例中只有一个vfptr指向属于自己的vftable ),它们指向的虚函数表是怎样的结构呢?
 首先,子类的虚函数表(不是虚表"指针",区别这两个概念)中首先存放的是父类的虚函数入口地址,当子类重写父类虚函数时,这个子类虚函数表中将父类原先的虚函数入口地址改为重写后的子类虚函数入口地址,
 没有重写的父类虚函数将在子类虚表中保持原先的位置。因此,在指向子类对象的父类指针调用虚函数时,查询到的自然是子类重写过的虚函数的入口地址,这个过程是实现多态技术的关键。
 3) 编译器只允许使用基类指针调用基类的成员函数。
 4)  1:指针的可访问性是由指针的定义决定的,比如说用BaseClass定义的指针,可访问的范围就是BaseClass的内存区域      
        2:允许用一个指向基类的指针指向派生类,由于被指向的对象的内存空间大于指针的可访问空间,所以这种向上映射是安全的      
        3:对象在调用虚函数的时候,是调用父类的函数还是调用派生类的函数,是和对象的类型有关的,比如说一个派生类B,其父类是A,则B的对象调用父类中被声明为VIRTUAL的函数时,被B   所OVERRIDE的函数调用的是B里的函数,而B没有OVERRIDE的函数调用的是基类里的函数
 5) C,C++都不是强类型语言,任何两个类型之间都可以强制转换,但要真正说有意义的,只有父类的指针指向子类的实例了.因为子类实例在内存排列上,先是把父类的所有内容排在前面,因此父类的指针指过来时,父类的各成员的偏移地址都是不变的.

11、覆盖是为了用基类的对象调用派生类的覆盖函数(名称、参数一致并指定了virtual关键字)
 隐藏是派生类的函数屏蔽了基类的函数,会造成派生类对象无法调用基类已经实现了的函数

 覆盖:
   (1)不同的范围(分别位于派生类与基类)
   (2)函数名字相同,参数相同;
   (4)基类函数必须有virtual关键字
    隐藏:(对子类的对象/指针而言的,对基类的对象/指针无意义)
   (1)不同的范围(分别位于派生类与基类)
   (2)函数名字相同;参数不相同,基类可有可无virtual关键字
   (3)函数名字相同;参数也相同,基类没有virtual关键字
    隐藏只是对子类的对象/指针有意义,对基类的对象/指针毫无意义(主要是区别重载、覆盖、多态与函数隐藏)。

11、引用的声明一定要初始化(语法层面);引用b的地址就是原变量a的地址。

12、指针常量:先*后const,指针指向一个只读对象,指向的内容是一个常量;int * const p1;
 常量指针:先const后*,指针本身是一个常量,指针所指的对象是不能改变的。const int *p1;
 const 限定符必须要初始化,否则之后就没机会了。

13、类的成员函数属于类所有,而成员变量属于类对象所有。例如一个类的指针原先指向一个实例,这个实例释放之后,用这个"野指针"依然可以访问到类的成员函数,前提是这个函数不访问类的成员变量;
 但如果用这个野指针去访问类的成员变量,则会出错。说明了问题所在。

14、栈区:局部变量、函数参数、返回数据、返回地址等;
 堆区:分配方式类似链表,手动回收,OS"可能"自动回收;
 全局区:全局变量、静态数据、常量;sizeof只计算栈区,不计算全局区。
 文字常量区:常量字符串;
 程序代码区:存放函数体(类成员函数与全局函数)的二进制代码。

15、内联函数与宏定义:内联函数,它主要是解决程序的运行效率。
 内联函数的函数体有一些限制:内联函数中不能含有任何循环以及switch和goto语句;内联函数中不能说明数组;递归函数(自己调用自己的函数)不能定义为内联函数。
 内联函数从源代码层看,有函数的结构,而在编译后,却不具备函数的性质。编译时,类似宏替换,使用函数体替换调用处的函数名。
       内联扩展是用来消除函数调用时的时间开销(与普通函数的区别,没有函数入口地址与保护现场恢复现场等操作,简单替换函数体)。它通常用于频繁执行的函数。
       宏只是在预处理的地方把代码展开,不需要额外的空间和时间方面的开销,所以调用一个宏比调用一个函数更有效率。
       1、宏不能访问对象的私有成员。 2、宏的定义很容易产生二意性。
       内联函数要做参数类型检查,宏只是一个简单的替换。在内联函数内不允许用循环语句和开关语句。
       在C++中,在类的内部定义了函数体的函数,被默认为是内联函数。而不管你是否有inline关键字。
      
       Class TableClass
       { Private:   
       Int I,j;   
         Public:   
          Int add() { return I+j;};   
       Inline int dec() { return I-j;}   
       Int GetNum();   
        }
       
       
        #include "iostream.h"
      inline int abs(int x)
      {
       return x<0?-x:x;
      }
      void main()
      {
       int a,b=3,c,d=-4;
       a=abs(b);
       c=abs(d);
       cout<<"a="<<a<<",c="<<c<<endl;
      }


16、extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。
  另外,extern也可用来进行链接指定。

17、定义一个枚举类型:enum open_modes{input , output , append};//枚举成员是常量,默认第一个枚举成员为0,期间改变值,之后以改变为基准加一,枚举类型的对象的初始化或赋值,
                          只能通过其枚举成员或同一枚举类型的其他对象来进行。

18、静态类型转换:double money;   static_cast<int>(money);
    常量转换:const double x=100; const_cast<double&> (x)=99; //把常量x转换为变量类型
    不同类型指针的转换:  int *ptr=0;  reinterpret_cast<double *> (ptr);
    动态类型转换:dynamic_cast<子类*>(父类地址)
  a;               //A是父类,b是A的子类
  B b;
  A *aPtr=&b; //aPtr 指向子类的地址
  *bPtr=&b;
  bPtr=dynamic_cast<B *>(aPtr);

19、switch 多选择语句
    switch(判断表达式) //
    {
   case key : 执行内容;break;// break不能少!
   ...
   ...
   ...
   default : 执行内容;break;//默认流程不能少!
    }

20、continue语句和break语句的区别是:continue语句只结束本次循环,而不是终止整个循环的执行。而break语句则是结束本层循环,不再进行条件判断。
 break只能跳出一层循环,不能跳出多层!!

21、    文件包含(#include)  条件编译(#ifdef , #else,#endif等,意思同if...else...无需改变)  布局控制(#progma)  宏替换(#define)
      方式一:
    #ifndef __SOMEFILE_H__
    #define __SOMEFILE_H__
    ... ... // 一些声明语句
   #endif
   方式二:
    #pragma once
   ... ... // 一些声明语句
    方式一由语言支持所以移植性好,方式二 可以避免名字冲突

22、对于变量生命周期和作用域的理解:静态局部变量的作用域与普通局部变量相同,但拥有全局生命周期;
   作用域是指能被访问到的地方,生命周期是指何时销毁它。
   static修饰函数,此函数的作用域变为其所声明的文件内;static修饰类的成员,被修饰的成员变量变为类作用域,拥有全局声明周期,不属于任何对象,这个类的所有对象都可以访问;被修饰的成员函数变为类作用域,不属于任何对象,调用时不会传递隐含的this指针。

23、const关键字至少有下列n个作用:

  (1)欲阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;

  (2)对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const;

  (3)在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;

  (4)对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量;

24、   BOOL型变量:if(!var) //如果var是假的;
   int型变量: if(var==0)
   float型变量:
   const float EPSINON = 0.00001;
   if ((x >= - EPSINON) && (x <= EPSINON) //      - 0.00001<= EPSINON <=  0.00001
   指针变量:  if(var==NULL)

25、默认值的定义必须遵守从右到左的顺序,如果某个形参没有默认值,则它左边的参数就不能有默认值。
    如:
  void func1(int a, double b=4.5, int c=3); //合法
  void func1(int a=1, double b, int c=3);   //不合法
  
   在进行函数调用时,实参与形参按从左到右的顺序进行匹配,当实参的数目少于形参时,如果对应位置形参又没有设定默认值,就会产生编译错误;如果设定了默认值,编译器将为那些没有对应实参的形参取默认值。

26、名字空间的用法:定义了一群名字的“含义”,使用不同的名字空间能够找到不同的含义。using std::cout, using std::cin

  #include <iostream>
  using namespace std;
  
  namespace test
  {
            using std::cout;
            using std::endl;
            void fun()
            {
                cout << " fun in test" << endl;
            }
   }

  namespace first //将namespace看做一个class
  {
    int x = 5;
    int y = 10;
  }

  namespace second
  {
    double x = 3.1416;
    double y = 2.7183;
  }
  
  int main ()
   {
    using namespace test;
    test :: fun();
    using first::x; //在类名first前面加上using
    using second::y;
    cout << x << endl;
    cout << y << endl;
    cout << first::y << endl;
    cout << second::x << endl;
    return 0;
  }

27、对于某种数据类型的指针p来说:
   p+n的实际操作是:(p)+ n*sizeof(数据类型);
   p-n的实际操作是:(p)- n*sizeof(数据类型);

28、引用与指针的区别:(1)指针表示的是一个对象变量的地址,而引用则表示一个对象变量的别名。
            (2) 指针是可变的,它可以指向变量a,也可以指向变量b,而引用则只能在建立时一次确定(固定绑定在某一个变量上),不可改变。
         (3)引用本身不是一个独立的变量,它没有自己的值和地址空间。

29、new/delete 和 malloc/free 比较:
    1)  new 和 delete 在实现上其实调用了malloc和free函数, 但new运算符除了分配内存, 还要调用构造函数; delete会调用析构函数。
    malloc 函数只是负责分配内存,不会进行初始化类成语的工作。同样,free也不会调用析构函数。
    2)使用malloc函数进行内存分配必须指明要分配空间的具体大小: 而每次使用new进行内存申请时, 它能自动计算要分配类型的大小, 这样就简化了使用,避免了错误。
    3)malloc 函数对类型缺乏检查和限制, 它总是返回一个void 指针, 但new 创建出来的指针是直接带类型信息的, 这使得new比使用malloc函数更加可靠。

30、   <memory. h>头文件中
   1. memset
  void * memset(void *buf, int ch, size_t count);//函数作用: 将已经开辟内存空间buf的前count个字节的值置为ch, 并返回指向buff的指针。
  2、memcpy: 进行内存拷贝的,用户可以使用它来拷贝任何数据的对象。
  void * memcpy(void *dst, void *src, size_t count);

31、1)静态数据成员是类的所有对象中共享的成员,而不是某个对象的成员,因此可以实现多个对象间的数据共享。
    静态数据成员不从属于任何一个具体对象,所以必须对它初始化,且对它的初始化不能在构造函数中进行。
   class  Class1
  {
   int a;
    static int b;
     //…
  }c1,c2;
  int Class1::b = 0;
  2)常数据成员。如果在一个类中说明了常数据成员,那么构造函数就只能通过初始化列表对该数据成员进行初始化。
  3 )对于常对象成员需要注意以下几点:
  (1)const是函数类型的一个组成部分,因此在实现部分也要带const关键词。
  (2)常成员函数不更新对象的数据成员,也不能调用该类中没有用const修饰的成员函数。
  (3)如果将一个对象说明为常对象,则通过该常对象只能调用它的常成员函数,而不能调用其他成员函数。
  (4)const关键词可以参与区分重载函数。例如,如果在类中有说明:
   void print();
   void print() const;


32、友元函数说明的位置可在类的任何部位,既可在public区,也可在protected区,意义完全一样。友元函数定义则在类的外部,一般与类的成员函数定义放在一起。
    友元关系不存在传递性。友元关系不可以被继承。不存在“友元的友元”这种关系。
    友元函数可以访问类的私有成员。


33、 1)图的深度优先搜索法是树的先根遍历的推广,它的基本思想是:从图G的某个顶点v0出发,访问v0,然后选择一个与v0相邻且没被访问过的顶点vi访问,再从vi出发选择一个与vi相邻且未被访问的顶点vj进行访问,
  依次继续。如果当前被访问过的顶点的所有邻接顶点都已被访问,则退回到已被访问的顶点序列中最后一个拥有未被访问的相邻顶点的顶点w,从w出发按同样的方法向前遍历,直到图中所有顶点都被访问。
       2)图的广度优先搜索是树的按层次遍历的推广,它的基本思想是:首先访问初始点vi,并将其标记为已访问过,接着访问vi的所有未被访问过的邻接点vi1,vi2, …, vi t,并均标记已访问过,
    然后再按照vi1,vi2, …, vi t的次序,访问每一个顶点的所有未被访问过的邻接点,并均标记为已访问过,依次类推,直到图中所有和初始点vi有路径相通的顶点都被访问过为止。


34、广度优先遍历二叉树。

广度优先周游二叉树(层序遍历)是用队列来实现的,从二叉树的第一层(根结点)开始,自上至下逐层遍历;在同一层中,按照从左到右的顺序对结点逐一访问。

按照从根结点至叶结点、从左子树至右子树的次序访问二叉树的结点。算法:

    1初始化一个队列,并把根结点入列队;

    2当队列为非空时,循环执行步骤3到步骤5,否则执行6;

    3出队列取得一个结点,访问该结点;

    4若该结点的左子树为非空,则将该结点的左子树入队列;

    5若该结点的右子树为非空,则将该结点的右子树入队列;

    6结束。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值