【C++笔记】1. C语言复习

1. C语言复习

1.1 基础部分

  1. C99之后可以用const int来表示常量,初始化后不能再被赋值。

  2. 整数和整数运算只会得到整数。当有浮点数参与运算的时候,就会变成浮点数。

  3. 整数用int(输入输出都是%d),浮点数double(输入%lf,输出%f)。

  4. 运算符优先级:

    优先级 运算符号 运算意义 结合关系
    1 + - 正、负 自右向左
    2 * / % 乘、除、取余 自左向右
    3 + - 加、减 自左向右
    4 = 赋值 自右向左
  5. =赋值语句,也是运算,也是有结果的。
    比如a=b=6在C语言中,自右向左运算a=(b=6),意义是:b=6的结果是6,再赋给a
    虽然可以“嵌套式赋值”(比如int c = 1 + (b=a)),但不建议这么做。

  6. 调试运行的时候,下一步和单步进入,是有区别的。

  7. 复合赋值运算符:+=-=*=/=

  8. 递增递减运算符:++

    表达式 运算 表达式的值
    count++ 给count加1 count原来的值
    ++count 给count加1 count+1的值
    count- - 给count减1 count原来的值
    - -count 给count减1 count-1的值
  9. scanf("%d,%d")scanf("%d %d")scanf("%d, %d")scanf("%d %d\n")这四种方式是不同的。
    scanf()函数用于接收输入进来的东西,返回值是接收了几个东西。
    对于空格和回车,会等待后续输入。
    对于其他非%d数据,scanf()函数接收失败,返回成功接收的个数,但会继续执行下一条语句。

  10. %x为16进制的输入或输出,%o为8进制的输入或输出。

  11. 取余运算的应用:number%10运算可提取出number的个位数字。
    辗转相除法、判断素数等,都用到了取余符号。

  12. 关系运算(<、<=、>、>=、==、!=):自左向右运算,且==!=的运算级低。
    语句a==b==6,实际上就是判断1==6,最终结果是0
    语句6>5>4,实际上就是判断1>4,最终结果是0

  13. if语句中,if(条件){语句},注意分号和大括号。

  14. switch语句中,case只是入口。如果不break,语句会继续执行。

  15. do…while循环,while括号里的都是循环的条件(虽和直觉不符。但和while循环保持了一致)。

  16. continuebreak的差别,continue跳出当前循环,break跳出整个循环。

  17. goto语句在多层循环嵌套时候使用,其他场合不建议使用。

  18. sizeof()函数是静态函数,比如sizeof(a++)语句编译后,a++不执行。

  19. &&与运算会屏蔽0后的语句、||或运算会屏蔽1后的语句。

  20. 补码=反码+1。

  21. 浮点数是有精度的。判断两个浮点数是否相等,应采用fabs(f1-f2)<=1e-12

  22. char型变量其实是一种特殊的int类型:int a = 49和char c ='A'
    按照%d输出时,输出的都是49
    按照%c输出时,输出的都是'1'
    综上,a+'a'-'A'a+'A'-'a'就可以进行大小写转换。

  23. 逗号运算符,,主要是用在for语句里用。

  24. C语言调用函数时,是值传递。

  25. 本地变量、局部变量:定义在大括号(块)里。
    生存期、作用域:大括号(块)里。
    块里定义的变量,块外无效。
    块外定义的便令,块里也有效。
    如果块里定义了与块外同名变量,屏蔽掉块外的变量。

  26. 同一个c文件里,函数声明和定义的头,必须一致,否则会编译出错。

  27. f(a,b)f( (a,b) )不一样,前者传了2个参数,后者传了1个参数。

  28. 函数里面可以声明另一个函数,但不能定义另一个函数。

  29. C99之前,数组元素数量必须是编译时刻确定了的字面量。

  30. 数组一旦创建,长度固定。

  31. 数组创建后,最好要初始化。数组的访问,不能越界(即超过最大下标)。

  32. 数组的集成初始化:int a[]={1,2,3,4,5,6};
    int a[]={[1]=2,4,[5]=6};

  33. 数组长度算法:sizeof(a)/sizeof(a[0]);

  34. 数组变量不能互相赋值:int b[] = a;这种写法是错误的。只能遍历数组,每个元素依次赋值。

  35. 数组作为参数传入函数时,应把数组长度一同传入。
    原因:数组作为参数时,传入的只是地址。

  36. unix系统下,可以使用man sqrt命令,查阅sqrt函数。

  37. 求素数的方法:方法一 顺序往下挑选、方法二 剔除已有素数的倍数。

  38. 二维数组,列数不可以省略,行数可以。int a[][5] = {{1,2,3,4,5},{6,7,8,9,0}};

1.2 指针和数组

  1. 运算符&:对变量取地址,不能是表达式,如&(i+p)或&(i++)等做法是错误的。

  2. printf()函数里用%p来输出地址。

  3. 对于一个数组a[]来说,a&a&a[0]都是相同的,都是a的首地址。

  4. 作为参数的指针,在函数里面可以通过这个指针访问外面这个i
    如果只是传值,那么调用的函数无法改变外面的i
    但是传了i的地址,那么就能对地址里面的东西进行改动,也就是把i进行改动。

  5. 赋值号的左边不一定是个变量,也有可能是个运算表达式的结果,这就叫左值。
    *p就是p取内容的运算,但是可以被赋值,所以*p是左值。
    a[0]就是a取0位置内容的运算,但是可以被赋值,所以a[0]是左值。

  6. 指针的应用,使得被调用的函数能够更改原来函数的变量!

  7. 一般情况下,函数使用return返回运行状态,使用指针返回值。

  8. 指针必须进行初始化,即指向某个变量的地址。否则将会是一个“野指针”,很危险。

  9. 传数组,实际上就是在传地址。所以说,被调用的函数可以改外部函数的数组元素
    sizeof(a)在内外却不等,因为外面的sizeof(a)求的是整个数组地址所占长度,里面的sizeof(a)求的是首地址的长度。

  10. 数组变量本身表达地址,因此int *p = a;无需用&来取地址。
    数组名就是地址指针,因此a等价于&a[0]
    []运算符可以对数组做,也可以对指针做:p[0]等价于a[0]
    *运算符也可以对数组做:*a

  11. 实质:数组就是常量指针,所以不能被赋值。
    int a[]等价于int * const a

  12. 指针与const

    int i;
    const int* p1 = &i;   // *p1是const,即不能通过*p1修改i
    int const* p2 = &i;   // *p2是const,即不能通过*p2修改i
    int *const p3 = &i;   //  p3是const,即p3只能指向i,等价于数组
    
  13. void f(const int* x);,表示f()不会改动传来的指针的指向的值。

  14. const数组:const int a[]={1,2,3,4,5,6,},这样的话,内部的函数不会修改外部数组了。

  15. 指针pq可以递增/递减,即进行p++p--p-q运算。
    p=p+1,实际上指的是,将p指到下一个单元。
    而数组实际上就是特殊的指针,因此:
    *p等价于a[0]
    *(p+1)等价于a[1]

  16. *p++:取出p的东西,顺便把指针向后移动一次。举例:

    for( int i=0; i< sizeof(a)/sizeof(a[0]) ; i++ ){
        printf("%d\n",a[i]);
    }
    // 可以改写成:
    char *p = a[];
    while( *p != -1 ){
        printf("%d\n", *p++);
    }
    
  17. 指针是可以比较的,比如数组中的单元的地址就是线性递增的。

  18. 不建议使用0地址,但可以初始化为NULL

  19. 类型不同的指针不能互相赋值。除非进行强制类型转换。

  20. void*表示不知道指向什么东西的指针。经常在比较底层的情况下应用到。

  21. 在C99之前(ANSI C)时,如何用变量定义数组的大小呢?
    使用stdlib.h中的函数malloc()
    int *a = (int*) malloc(n*sizeof(int));
    原因:malloc()函数申请到的是一块(void*)类型的地址空间,以字节为单位,需要强制类型转化为(int*)
    接下来,把a当作数组来用。如a[0]
    最后,用了malloc()就要释放掉,即free(a)

  22. 但是系统空间是有限的,malloc()不一定总能成功的申请到需要的空间。
    malloc()返回值为1代表申请成功,返回值为0代表申请失败。

  23. free()函数只能释放掉malloc()来的地址。
    常见问题:
    (1)malloc了没free,会导致运行内存逐渐下降。
    (2)free过了再去free。
    (3)地址变了,直接去free。

1.3 字符串

  1. 字符串以'\0'结束,但它不是字符串的一部分。计算这个字符串长度的时候不包括这个'\0'

  2. 字符串以数组形式存在,以指针形式访问。
    因此,可以通过遍历数组的方式来遍历字符串。

  3. 对字符串进行操作时,需要#include

  4. 字符串的定义形式:
    由双引号括起来的东西Hello,叫字符串的字面量.
    字符数组长度为6,因为结尾还有'\0'

    char *str = "Hello";
    char word[] = "Hello";
    char line[10] = "Hello";
    
  5. char *s = "Hello, world!"中:
    这种写法,会把"Hello, world!"写在常量空间里,实际上sconst char *s,不允许写入操作,赋值只能靠初始化。
    s是个指针,初始化为指向一个字符串常量
    试图对s所指的字符串做写入会导致严重后果。

  6. char s[] = "Hello, world!"中:
    这种写法,会把"Hello, world!"写在变量空间里。
    在程序运行结束后,会被自动回收。

  7. 这两种写法如何选择?
    数组形式:作为本地变量,空间自动回收。
    指针形式:只读、处理参数、动态分配空间。
    如果要处理一个字符串,用数组。
    如果要构造一个字符串,用指针。

  8. char*不一定是字符串,它本意是“指向char型单个变量的指针”。

  9. 符串赋值,实质上是让指针s指向了指针t所指的字符串,任何对s的操作就是对t做的,因为对于字符串,赋值符号赋的是地址。

    char *t = "Hello";
    char *s;
    s = t;
    
  10. 使用scanf()来读字符串时,读到空格、tab、回车为止。
    scanf("%7s",string);表示读入字符串最多7位

  11. 常见错误:

    char * string;
    scanf("%s", string);
    // 指针string很有可能指向了一个危险的地方。
    // 改正方法:
    // 1. 赋初值,但以后无法更改
    // 2. 数组形式定义字符串变量
    char buffer[100] = "";
    // 这是一个空字符串,buffer[0]=='\0'
    char buffer[] = "";
    // 这个数组长度只有1,放不下任何东西
    
  12. char **achar a[][10]char *a[],这三者是不同的。
    char **a:表示a是一个指针,指向另一个指针,那个指针指向一个字符(串)
    char a[][10]:表示a是一个二维数组变量,每一个元素都是一个含有10个元素的一维数组。
    char *a[]:表示a是一个存放指针的一维数组,每个指针都指向一个char型变量。

  13. main(int argc, char const *argv[])
    这里的*argv[]表示:main()函数调用的参数。
    常常在命令行中使用到。

  14. int putchar(int c)函数:返回写了几个字符,EOF(-1)表示写失败。

  15. int getchar(void)函数:返回类型是int,是为了返回EOF(-1)

  16. windows环境下Ctrl+Z表示输入结束;Unix环境下Ctrl+D表示输入结束。

  17. 通过下面的例子,可以感受到 shell 的存在和工作机制。
    输入设备和用户程序之间有缓冲区,这个缓冲区会在不同编译环境下有不同的工作机制。
    另外,搭建 VScode的 C++ 开发环境时,win 下使用 powershell 作为终端,ubuntu下默认的终端。
    这二者对于cout函数的处理是不一样的。
    前者cout输出不一定要有endl,后者cout输出一定要有endl才会输出。

    int main( int argc, char const *argv[])
    {
        int ch;
        while ( ( ch = getchar() ) != EOF ){
            putchar( ch ) ;
        }
        printf("EOF\n");
        return 0;
        // getchar() 是用 int 型变量来接收 char。
        // putchar() 是把 int 型变量输出为 char。
        // 这段代码,输入什么就输出什么
        // 按下Ctrl+C会停止程序
        // 输入Ctrl+Z会输出EOF
    }
    
  18. 字符串头文件string.h里常用的函数:strlen()
    形式:size_t strlen(const char *s
    功能:返回s的字符串长度(不包括结尾的\0
    备注:一般情况下,等于sizeof()-1

  19. 字符串头文件string.h里常用的函数:strcmp()
    形式:int strcmp(const char *s1, const char *s2)
    功能:比较两个字符串并返回0、正数、负数。0表示相等,正数表示s1大,负数表示s2
    比较原则:从第一个字符开始比较,相同则继续,不同则相减。任一串读完则跳出。
    备注:s1==s2是warning而非error,因为s1==s2比较的是地址,而非字符串内容。

  20. 字符串头文件string.h里常用的函数:strcpy()
    形式:char *strcpy(char *restrict dst, const char *restrict src):
    功能:把src的字符串拷贝到dst,并返回dst(最后的\0也拷贝)
    备注:restrict表示这两个字符串在存储上不能重叠。

  21. 复制字符串的套路:

    char *dst = (char*)malloc (strlen(src)+1);
    // 不能用sizeof,src还有可能是指针、数组
    // malloc后最后要free
    strcpy(dst,src);
    
  22. 字符串头文件string.h里常用的函数:strcat()
    形式:char *strcat(char *restrict s1, const char *restrict s2)
    功能:把s2拷贝到s1后面,接成一个长的字符串,返回s1
    原理:把s2的第一个char写在s1\0的位置
    备注:s1必须有足够的空间

  23. strcpy()strcat()的安全问题:如果目的地没有足够空间会产生安全问题。
    因此,尽量使用安全版本。
    char *strcpy(char *restrict dst, const char *restrict src, size_t n)
    char *strcat(char *restrict s1, const char *restrict s2, size_t n)
    int strcmp(const char *s1, const char *s2, size_t n)
    其中,strcmp()函数的n表示,只比较前n个字符。

  24. 字符串中找字符函数strchr(),分为从左和从右两个版本。
    利用字符串找字符函数,可以截取字符串中的某段。

  25. 字符串中找字符串函数strstr(),分为不忽略和忽略大小写两个版本。

1.4 枚举

  1. 程序中如果有数字,尽量采用符号来表示,比如枚举。
    enum COLOR {RED, YELLOW, BLUE, NumCOLOR};
    数据类型enum COLOR,实际上就是int,可以用%d进行输出。
    技巧:枚举最后一个元素的值,实际上就是枚举元素总数。
  2. 当然,也可以不按顺序进行枚举声明,比如:
    enum COLOR {RED=1, YELLOW, BLUE=5};

1.5 结构体

  1. 结构体声明、定义一个结构体变量时,最好放在main()函数外边。
    结尾一定要记得写分号;

    // 方式一:先声明,在定义。结构体类型名:struct date。
    struct date{
        int month;
        int day;
    };
    struct date today, tommorow;
    
    // 方式二:直接定义。结构体类型没有名字。
    struct{
        int month;
        int day;
    } today, tommorow;
    
    //方式三:同时声明和定义变量。结构体类型名:struct date。
    struct date{
        int month;
        int day;
    } today, tommorow;
    
  2. 结构变量的初始化:struct date today = {.month=4, .day=19}

  3. 结构变量的访问:today.month

  4. 结构变量的赋值:
    方式一(成员分别赋值):
    today = (struct date){4, 19};
    方式二(变量整体赋值):
    today = tomorrow;

  5. 结构变量的名字就是变量本身,并非地址(与数组和字符串不同)。
    因此对结构变量取地址,&today
    当然也可以对结构变量的成员取地址,&today.month

  6. 结构体是可以作为函数参数、返回值的。

  7. 结构体不能直接printf和scanf,但可以自己做类似的函数。
    编写时,最好使用指针方式。

  8. 指向结构体的指针,最好使用->

    struct date *ptoday = &today;
    (*ptoday).month = 12;
    ptoday->month = 12;
    
  9. 视频11.2 结构与函数在15分22秒处的例子,把结构体指针做为参数,又把结构体指针作为函数返回值。因此各个函数之间可以互相调用、赋值。

  10. 结构体中有数组:直接套就行了。
    数组中有结构体:直接套就行了。
    结构体中有结构体:直接套就行了。
    但注意:成员是变量时用.,成员是指针时用->

1.6 自定义数据类型

  1. 普通的数据类型:typedef int Length;

  2. 定义结构体:

    typedef struct ADate{
        int month;
        int day;
    } Date;
    // 或者省略`ADate`:
    typedef struct{
        int month;
        int day;
    } Date;
    // 定义变量时,可以直接写
    Date today = {4, 19};
    

1.7 联合

  1. 表面上看起来和struct一样,但实际上所有成员占用相同的内存空间。
    如果发生重叠,会“冲掉”原来的值。

    union AnElt{
        int i;
        char c[4];
    } elt1;
    // 当然此处也可以用typedef
    elt1.c[0] = 'a';
    elt1.i = 0xDEADBEEF;
    // 此时,后填写的`i`会把先填写的`c`冲掉
    
  2. 本例中union的作用:查看int变量中每个字节的情况。
    当然,x86的CPU采用小端形式进行存储数据。
    因此,如果对上边的c数组进行遍历读取的话,会得到:
    c[0]等于0xEF
    c[1]等于0xBE
    c[2]等于0xAD
    c[3]等于0xDE

1.8 全局变量

  1. 定义在函数外边的变量,就是全局变量。

  2. 字符串__func__,输出时自动变为当前函数名。

    int main(){
        printf("%s", __func__);
        return 0;
    }
    
  3. 全局变量在没有初始化时,会得到0值(指针得到NULL)。

  4. 全局变量必须使用已知值来初始化。

  5. 不能将一个全局变量赋值给另一个全局变量。

  6. 本地变量会屏蔽外部的全局变量。

  7. 静态本地变量,本质上就是全局变量。
    原因:在程序进行编译时,紧挨着放在放在同一块区域中。
    static int i = 1;
    函数第二次被调用时会保留函数第一次执行后的值。

  8. 总结:
    本地变量:本地作用域,本地生存期。
    全局变量:全局作用域,全局生存期。
    静态本地变量:本地作用域,全局生存期

  9. 函数返回指针,不能返回本地变量的地址。

  10. 过多使用全局变量和静态本地变量,在多线程时是不安全的。
    因此,应尽量不要使用全局变量和静态本地变量。

1.9 宏定义

  1. 编译预处理指令:#开头的那一行。
    它们不是C语言的部分,但C语言离不开它们。
  2. #define PI 3.14159,其中PI是宏的名字,3.14159是宏的值。
  3. #define:只是文本的替换,经常需要加括号()
  4. #define:可以使用//进行注释。
  5. build的过程:源代码文件(.c) -> 中间结果文件(.i) -> 汇编代码文件(.s) -> 目标代码文件(.o) -> 可执行文件(.out)
  6. #define:可以定义没有值的宏。
  7. 预定义的宏: __DATE____TIME__等等。
  8. 带参数的宏,原则:整体括号,参数有括号。
    #define cube(x) ((x)*(x)*(x))
    #define MIN(a,b) ((a)>(b)?(b),(a))
  9. 宏定义中不建议加分号;
  10. 带参数的宏在大型程序中非常普遍。
  11. 宏的使用容易出错。

1.10 多文件项目

  1. 将各个函数分开放在不同的(.c)文件中。

  2. 编译(compile):对单个源代码文件的编译。
    构建(build):对整个项目做链接。

  3. 如果没有头文件(.h),编译器遇到main()里陌生的函数会乱猜。
    因此,头文件里包括了函数的声明,用来作为不同源文件之间的桥梁。

  4. #include用来插入对应的头文件中的文本内容。

  5. #include后的<>用于自带头文件,""用于用户头文件。

  6. 一般来说,任何c文件都有对应的h文件。

  7. 头文件中还包括了,在源文件中的全局变量的声明
    extern int AllVariable;

  8. 头文件中,建议只放声明。

  9. 同一个编译单元里,同名的结构不能被重复声明。

  10. 因此,有标准头文件结构。

    #ifnedf _MAX_H_
    #define _MAX_H_
    
    #endif
    

你可能感兴趣的:(C++笔记,c++,开发语言)