天天看点

C/C++易错知识点记录(一)C/C++易错知识点记录(一)

C/C++易错知识点记录(一)

  1. 用户自定义标识符由字母、数字和下划线组成,只能由字母或下划线开头.
  2. 指针问题:

    例1:

    int **pp,*p,a=10,b=20;
    pp=&p;
    p=&a;
    p=&b;
    printf("%d,%d",*p,**pp)
               

    pp = &p; pp中储存p的地址,就是指针p的地址

    p = &a;p中储存a的地址,就是p指向a,*p=a=10;

    p = &b;p中储存b的地址,就是p指向b,*p=b=20;

    *pp = p

    **pp = *p = b = 20

    例2:

    int main()
    {
     int a[5]  = {1, 2, 3, 4, 5};
     int *ptr = (int*)(&a + 1);
     printf("%d, %d", *(a + 1), *(ptr - 1));
     return;
    }
               

    &a + 1: 取数组a 的首地址,该地址的值加上sizeof(a) 的值,即&a + 5*sizeof(int),也就是下一个数组的首地址,显然当前指针已经越过了数组的界限。(int *)(&a+1): 则是把上一步计算出来的地址,强制转换为int * 类型,赋值给ptr。*(a+1): a,&a 的值是一样的,但意思不一样,a 是数组首元素的首地址,也就是a[0]的首地址,&a 是数组的首地址,a+1 是数组下一元素的首地址,即a[1]的首地址,&a+1 是下一个数组的首地址。所以输出2*(ptr-1): 因为ptr 是指向a[5],并且ptr 是int * 类型,所以*(ptr-1) 是指向a[4] ,输出5。

    例3:

    p++ :表示先访问p,然后p++;

    (p)++: 表示先访问p,然后将*p的值++;p指针不变。

  3. 自增自减:
    #include < stdio. h >
      int d = 1;
      void fun( int p )
      {
           int d=5;
           d += p++;
           printf("%d", d );
       }
       main()
      {
          int a=3;
          fun(a);
          d += a++;
         printf( "%d\n",d);
       }
               

    先看函数里面:

    最终要输出d的值,而p++的意义是1)先执行d=d+p,2)再在执行下一步printf的时候同时执行p++;所以 这个函数关于d只做了一个d=d+p的操作;

    1)a=3;

    2)在局部变量d=5,p=a=3的情况下,执行d=d+p,d的值为8,输出;

    3)在全局变量d=1,a=3的情况下,执行d=d+a,d的值为4,输出;

  4. 函数模板的声明:
    template<typename 数据类型参数标识符>
     <返回类型><函数名>(参数表)
     {
           函数体
       }
               
  5. C++是不是类型安全的?答:不是。

    什么是类型安全?

    类型安全很大程度上可以等价于内存安全,类型安全的代码不会试图访问自己没被授权的内存区域。“类型安全”常被用来形容编程语言,其根据在于该门编程语言是否提供保障类型安全的机制;有的时候也用“类型安全”形容某个程序,判别的标准在于该程序是否隐含类型错误。类型安全的编程语言与类型安全的程序之间,没有必然联系。好的程序员可以使用类型不那么安全的语言写出类型相当安全的程序,相反的,差一点儿的程序员可能使用类型相当安全的语言写出类型不太安全的程序。绝对类型安全的编程语言暂时还没有。

  6. STL中的常用容器包括:顺序性容器(vector、deque、list)、关联容器(map、set)、容器适配器(queue、stac)。STL中一级容器是指, 容器元素本身是基本类型, 非组合类型:vector, deque, list.。
  7. C++算数符优先级:
    优先级 运算符 结合律 助记
    1 :: 从左至右 作用域
    2 a++、a–、type()、type{}、a()、a[]、.、-> 从左至右 后缀自增减、函数风格转型、函数调用、下标、成员访问
    3 !、~、++a、–a、+a、-a、(type)、sizeof、&a、*a、new、 new[]、delete、 delete[] 从右至左 逻辑非、按位非、前缀自增减、正负、C 风格转型、取大小、取址、指针访问、动态内存分配
    4 .*、->* 从左至右 指向成员指针
    5 a*b、a/b、a%b 从左至右 乘除、取模
    6 a+b、a-b 从左至右 加减
    7 <<、>> 从左至右 按位左右移
    8 <、<=、>、>= 从左至右 大小比较
    9 ==、!= 从左至右 等价比较
    10 a&b 从左至右 按位与
    11 ^ 从左至右 按位异或
    12 | 从左至右 按位或
    13 && 从左至右 逻辑与
    14 || 从左至右 逻辑或
    15 a?b:c、=、+=、-=、*=、/=、%=、&=、^=、|=、<<=、>>= 从右至左 三元条件、赋值
    16 , 从左至右 逗号
    例子:
    int a=3;
    printf("%d\n",(a+=a-=a*a)
               
    【解释】a+=a-=a*a等价于a=a+(a=a-a*a),即先计算a=a-a*a,所以此时a的值为3-3*3=-6,再计算-6+(-6)=-12赋值给a,所以a的值为-12;
  8. C语言程序中,%只能用于整数运算。

    C++取模运算支持类型:字符型(不包括负数)、字节型、短整型、整型、长整型、布尔类型(除数不能为false,原因在C++中,true表示1,false表示0,自然除数不能为0;).

  9. .C文件经过编译得到.obj(目标文件),然后进行链接得到.exe(可执行文件)。
  10. 一维数组必须定义数组元素个数;
    int n=5; int a[n][n+2]; //错误定义
               
    数组定义下角标不能为变量;
  11. for循环常用于循环次数已知的循环中,但也可以用于循环次数未知的循环中;for循环是先判断表达式,根据表达式的值来决定是否循环:在for循环中如果要中途退出循环,可以使用brcak语句来实现。
  12. 深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。浅拷贝是指源对象与拷贝对象共用一份实体,仅仅是引用的变量不同(名称不同)。对其中任何一个对象的改动都会影响另外一个对象。
    浅拷贝只是建立原来对象的引用,并不拷贝数据;深拷贝是新建一个对象,把原来对象的数据复制过来。但是有一点要注意,对含有静态成员数据的对象,静态成员属于类,被所有对象共享,所以深拷贝也不会拷贝这一份数据。只会拷贝非静态成员数据。
  13. 基类的公有成员在派生类中权限由派生规则决定;this只对本类的非静态成员有效.
  14. 32位编译器: char :1个字节 char*(即指针变量): 4个字节(32位的寻址空间是2^32, 即32个bit,也就是4个字节;)short int : 2个字节 int: 4个字节 unsigned int : 4个字节 float: 4个字节 double: 8个字节 long: 4个字节 long long: 8个字节 unsigned long: 4个字节

    64位编译器: char :1个字节 char*(即指针变量): 8个字节 short int : 2个字节 int: 4个字节 unsigned int : 4个字节 float: 4个字节 double: 8个字节 long: 8个字节 long long: 8个字节 unsigned long: 8个字节;

    double 型数据在内存中占 8 个字节 ,float 型数据占 4 个字节 ,int 型数据占 2 个字节 ,char 型数据占 1 个字节。

  15. 只有十进制才有负数;
  16. const修饰类的成员函数,则该成员函数不能修改类中任何非const成员函数。一般写在函数的最后来修饰。const跟左边结合 看谁是常量;print( )函数是一个类的常成员函数,它无返回值:
    void print( ) const;
               
  17. 在C中没有逻辑型数据,在逻辑运算结果中以“1”代表“真”,以“0”代表“假”;在逻辑判断中,以“非0”代表“真”,以“0”代表“假”。

    在C++中有逻辑型数据,以“true”代表“真”,以“false”代表“假”。

    C 语言中的基本数据类型包括:整型,实型(单精度实型和双精度实型),字符型。

  18. 对同一个变量赋值,直接看最后一节,因为赋值语句会被不断更新。A?B:C这是一个双目运算符,若语句A为真则执行语句B,否则执行语句C。
  19. 若言int a[10],*p=a;则以下表示中不能代表a[6]的是:p+6;

    明显P是指整形数组a的一个指针(首地址),p+6也只是a[6]的地址而已,本身还是一个指针。

  20. 定义二维数组时,若第一维不确定第二维确定,则数组必须初始化;初始化后数组中元素个数除以第二维大小,若能整除,则商即第一维大小,若不能整除,则商加上1得到第一维大小;若第一二维全部确定,可以不初始化,默认元素全部为0;不允许第二维不定

    int a[][3]={1,2,3,4,5,6,7};

    即为:int a[][3]={{1,2,3},{4,5,6},{7,0,0}};

    所以维数为3.

  21. 在以下四种情况下会掉调用析构函数:

    ①如果在一个函数中定义了一个对象,当这个函数被调用结束时,对象应该释放,在对象释放前会调用析构函数。

    ②静态局部对象在函数调用结束时对象并不释放,只在main函数结束时调用exit函数结束程序时,才调用static局部对象的析构函数。

    ③如果定义了一个全局的对象,则在程序的流程离开其作用域时调用该全局的对象的析构函数。

    ④如果用new运算符动态地建立一个对象,当用delete运算符释放该对象时,先调用该对象的析构函数。

    类的析构函数是一个对象被( 撤销 )时自动调用的。

  22. “算法具有零个或多个输入,至少有一个或多个输出。” ————《大话数据结构》

    算法没有输出就没有意义;

  23. 在C++中,关于设置默认参数值, 在指定了默认值的参数右边,不能出现没有指定默认值的参数;
  24. 循环语句的第二个表达式是一个逻辑表达式,即词表达式的之为真时循环执行,为假时循环结束,此赋值表达式返回值为真.k=1表达式always true…是个坑.
    for(j=0,k=-1;k=1;j++,k++)
     printf("****\n");
               
    设 j 和 k 都是 int 类型,则 for 循环语句(是无限循环)。
  25. 枚举问题enum:
    enum {
               a, b=5, c, d=4, e
          } k;
         k =c;
               
    注意枚举类型的整形赋值的方式。首元素默认为0,后一个元素不赋值的话,默认比前一个元素大1;k=6;
  26. C程序的基本组成单位是 函数 ;
  27. []的运算级别大于*,

    1、char *p[16],根据运算优先级,[]优于*,所以p和[]先结合,因此p是数组;

    2、char (*p)[16],p和*由括号括住,p和*先结合,因此p是 指针 。

  28. static成员变量是在类加载的时候生成的;static成员函数既可以通过类名直接调用,也可以通过对象名进行调用;虚函数是C++中的,虚函数不可能是static的;static成员函数可以访问static成员变量。
  29. 在C++中,类的静态成员(static member)必须在类内声明,在类外初始化,像下面这样:
    class A
         { private: static int count ; // 类内声明 }; int A::count = 0 ; // 类外初始化,不必再加static关键字    
               
  30. 为什么?因为静态成员属于整个类,而不属于某个对象,如果在类内初始化,会导致每个对象都包含该静态成员,这是矛盾的。
  31. 拷贝复制:

    char *strcpy(char* dest, const char *src) 把从src地址开始且含有NULL结束符的字符串复制到以dest开始的 地址空间

    void *memcpy(void dest, const void src, size_t n); 从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中

    void *memmove( void dest, const void src, size_t count ); 由src所指内存区域复制count个字节到dest所指内存区域。

    void *memset(void *s, int ch, size_t n); 将s中当前位置后面的n个字节 (typedef unsigned int size_t )用 ch 替换并返回 s

    (1)memcpy与memmove的目的都是将N个字节的源内存地址的内容拷贝到目标内存地址中。

    (2)但当源内存和目标内存存在重叠时,memcpy会出现错误,而memmove能正确地实施拷贝,但这也增加了一点点开销。

  32. stract( 字符数组1,字符数组2)串连函数;

    strcpy/strncpy (字符数组1,字符数组2)复制函数;

    strcmp( 字符串1,字符串2)字符串比较函数;

    strlen( 字符数组)测字符数组函数;

    例子:

    定义char dog[]=“wang\0miao”;那么sizeof(dog)与strlen(dog)分别是多少:

    sizeof返回数组所占的字节数,‘wang’ ‘miao’共占8字节,显式’\0’占1字节,字符串末尾隐式’\0’占1字节,共10字节。

    strlen返回字符串的长度,以遇到’\0’结束符为准,因此为4。

  33. break:跳出该循坏,不再执行循坏;continue:跳出本次循环,继续执行下一次循环。
  34. 静态局部变量的特点:1. 该变量在全局数据区分配内存;2. 静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的 函数调用 不再进行初始化;3. 静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0。fun(1)时 icount初始化为1,之后不再初始化,执行第一条语句 icount=2; 打印出来是3; fun(2)时 icount=2+2=4,打印的结果是4+2=6.
    void fun(int iinputnum)
     {
          static int icount=1;
          icount=icount+iinputnum;
          print( “ \n icount =%d ” ,icount+iinputnum);
          }
     for(i=1;i<3;i++) fun(i);
               
  35. 内存泄漏也称作“存储渗漏”,用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。(其实说白了就是该内存空间使用完毕之后未回收)即所谓内存泄漏。内存分配函数malloc(size_t)申请的是虚拟内存,并不是物理内存。
  36. # include<>直接从编译器自带的函数库中寻找文件#;

    include" "是先从自定义的文件中找 ,如果找不到在从函数库中寻找文件;

    eg:\ #include "file.h"是指编译器将从当前工作目录上开始查找此文件

  37. 字符集为每个字符分配了唯一的编号,每个字符除了用它本身的实体表示外,还可以用转义字符来表示,转义字符以\开始,如果是\x(注意是小写,C语言是区分大小写的)开头,后面接16进制数,如果以\开头,后面接八进制数。

    转移字符表示的数据范围有限,因此对\x开头的 ,只能是\xxx(后面两位表数据)这种格式的,意思是只能接两位数据,最大值为\x7f; 对\开头的,后面可以接三位数据,即\xxx(后三位均为数),最大值为\177.

    另外,还有其他几个常见的转义字符,\t ,\n,\a,\b,\r,\f,\v

  38. 若要打开A盘上user子目录下名为abc.txt的文本文件进行读、写操作:

    fopen(“A:\\user\\abc.txt”,“r+”)

    r 以只读方式打开,r+以读写方式打开,rb+以读写方式打开二进制文件,rt+以读写方式打开一个文本文件;

  39. C++中为什么用模板类的原因:

    (1)可用来创建动态增长和减小的数据结构 (2)它是类型无关的,因此具有很高的可复用性。 (3)它在编译时而不是运行时检查数据类型,保证了类型安全 (4)它是平台无关的,可移植性 (5)可用于基本数据类型;

  40. 一个抽象类注意一下几点:

    包含纯虚函数的类称为抽象类。由于抽象类包含了没有定义的纯虚函数,所以不能定义抽象类的对象。在C++中,我们可以把只能用于被继承而不能直接创建对象的类设置为抽象类(Abstract Class)。

    1.抽象类只能作为其他类的基类,它不能直接被实例化,而且对抽象类不能使用new操作符。抽象类如果含有变量或值,则他们一定是null类型,要么包含了对非抽象类的实例的引用。

    2.抽象类允许包含抽象成员,但这不是必须的(可以允许一个抽象类中没有没有任何抽象成员);抽象类中可以有非抽象方法.

    3.抽象类不能同时又是final的,理所当然,final类不能同时是抽象类。因为抽象类总希望被继承。

    4.如果一个非抽象类从抽象类中派生,则其必须通过覆盖来实现所有的继承而来的抽象成员。

    5.抽象类可以被抽象类所继承,结果依然是抽象类 。

    6.抽象类允许被声明。

    子类中必须覆盖基类的纯虚函数

  41. do while 是先执行再判断是否进行还是结束循环;while 是先判断是否进行循环再进行语句操作。
  42. 当用二维数组作为形参时,第一维的大小可以省略,但第二维的大小是不能省略的,必须和实参数组第二维的大小一致。

    声明数组是必须包含:首地址,步长,范围! eg:int a[];这样的一维数组定义有语法错误。

  43. ifstream默认以输出方式打开文件,ofstream默认以输出方式打开文件,例如:ifstream file();是以输入方式打开文件;ofstream file();以输出方式打开文件;
  44. 实型字面值常量有两种表示方式:小数形式和指数形式

    小数形式:由最前面的额正负号,数字0-9和小数点组成,不允许有其他符号;

    指数形式;包括指数和尾数两个不可缺少的部分,用符号E(e)分割;E(e)左边是尾数,为十进制整数或小数形式的实数,右边为指数,必须为十进制整数,表示乘以10的多少次方;

    eg:1.234e0.4不能作为合法常量;

  45. 被extern “C”修饰的函数或者变量是按照C语言方式编译和链接的,所以可以用一句话来概括extern “C”的真实目的:实现C++与C的混合编程。
  46. C++中 的虚函数的作用主要是实现了多态的机制。而虚函数是通过虚函数表(V-Table)实现的。

    构造函数不能声明为虚函数,析构函数可以声明为虚函数,而且有时是必须声明为虚函数。

  47. try-catch 语句块的目的是:当程序运行过程中发生异常错误时,就会“抛出异常’,提供一个“ 捕获” 异常的处理器对异常情况进行处理,而避免终止程序执行。

    try-catch 未能抛出捕获异常,程序终止执行。

    catch子句中能用throw语句引发异常,throw关键字的作用,有些错误在jvm看来不是错误(语法正确,但业务上不符,比如年龄为负数)而手动抛出异常。

  48. 多态性包括两种:第一种是静态多态, 通过函数重载、运算符重载和类模板实现;第二种是动态多态,通过继承中的多态(虚函数)实现。
  49. 1.构造函数的调用顺序是:基类>对象>派生类,析构顺序相反;

    2.构造函数里初始化列表初始化顺序由成员变量的声明顺序决定。

  50. c++的一个类中声明一个static成员变量:static是加了访问控制的全局变量,不被继承。

继续阅读