zoukankan      html  css  js  c++  java
  • C语言基础复习总结

    C语言基础复习总结

    大一学的C++,不过后来一直没用,大多还给老师了,最近看传智李明杰老师的ios课程的C语言入门部分,用了一周,每晚上看大概两小时左右,效果真是顶一学期的课,也许是因为有开发经验吧,废话少说,直接把总结贴出来了~

    #include <stdio.h>

    int main(int argc, const char * argv[])

    {

        printf("Hello, World! ");

        return 0;

    }

    #include是预处理指令,在编译前把尖括号里的内容原封不动地拷贝到对应位置。.h是头文件,里面是库函数的声明(不是实现)。尖括号表明是系统自带的,会去系统目录找,双引号是自己的文件,会先在源程序当前目录找,找不到就去操作系统的path路径找,还找不到才去C函数库里找。

    包含关系允许嵌套包含但是不允许递归包含(死循环)。

    C语言语法不严格,main函数可以不写返回值,默认返回int,参数可以不要,可以不return。

     

    C语言程序运行的过程:

    1.把源代码翻译成目标代码。Xcode是64位编译器。编译成功后生成同名.obj文件(多个c文件对应多个obj文件)。

    2.把C语言源文件之间的调用依赖以及C语言函数库链接进来,成为可执行的机器代码。在xcode下生成的是unix可执行文件。

     

    在java当中方法定义没有顺序限制,但是在标准c当中只能后面的函数调用前面的,因为C是从上往下编译的,如果想放在前面,需要声明,声明可以省略参数名,只要类型:

    #include <stdio.h>

    int sum(int, int);

    int main(int argc, const char * argv[])

    {

        int c = sum(10,3);

        printf("%d ", c);

        return 0;

    }

    int sum(int a, int b)

    {

        return a+b;

    }

     

    一般来说,会把函数声明和定义放在不同文件当中,比如把sum函数的声明放在test.h里,实现放在test.c里,然后在main函数之前引入:

    #include "test.h"

     

    反复引用同一个文件是没关系的,可以使用预编译指令做检查机制。但是不要导入.c文件,以免在链接时出现函数重复而报错,C不是面向对象的,函数名不能重复。

     

    printf输出需要使用百分号占位符,.2f是保留两位小数,不是四舍五入。

        // My age is 26, height is 1.55, name is 李明杰,sex is 'A'

    printf("My age is %d, height is %.2f, name is %s,sex is ‘%c’ ", 26,1.55f,"李明杰",'A');

     

    scanf是阻塞性的函数,等待标准设备输入,需要传变量的地址。

    #include <stdio.h>

    int main(int argc, const char * argv[])

    {

        printf("请输入两个整数,用逗号隔开:");

        int a,b;

        scanf("%d,%d", &a, &b);  //传a的地址

        printf("%d ", a+b);

        return 0;

    }

     

    C语言的类型分四类,一种是空类型viod,一种是int,float,double,char,一种是构造类型,比如数组,struct,union(基本没用),enum,另一种是指针类型void*。C是强类型语言,指定类型是为了分配适当大小的空间。Char类型不论多少位的编译器,都占一个字节。

     

    C与java不同,局部变量没有初始化使用也不会报错,但默认值不一定是0,是随机数,所以不要不初始化。全局变量则会被默认初始化。

     

    Char类型范围是-128到127,最好不用ascii码的值,直接用’a’,它不是unicode的,也不支持字符串。

     

    类型修饰符:short,long,signed,unsigned,最常用的是修饰int,被修饰的int可以省略,比如只写long,与java不同,这并不代表这是long类型,而是int。不管什么编译器,int至少2个字节。

     

    C语言里没有boolean,关系判断返回1或0的int值,没有-1!任何非0值都位真,只有0才是假,比如if(9)为真。

     

    C语言可以用逗号连接多个表达式,它的返回值是最后一个表达式的值,下面的代码输出12。

    #include <stdio.h>

    int main(int argc, const char * argv[])

    {

        int a=9;

        int b=10;

        int c;

        c = (a=a+1,b=3*4);

        printf("%d",c);

    }

     

    对于一个变量,变量存储单元的“第一个字节”的地址就是该变量的地址,取地址用&,返回数字,习惯用16进制。

    #include <stdio.h>

    int main(int argc, const char * argv[])

    {

        char a = 'A';

        int b = 66;

        printf("%x ", &a);

        printf("%x", &b);

    }

     

    数组用来存放“同一种”类型的变量,不能用变量做长度,应该用常量,但是xcode不会报错。系统为数组分配的空间是连续的。

        int ages[5];

    printf("%d",sizeof(ages));

    得到的长度是20,C语言数组名就代表数组地址,所以ages就是个常量,不能赋值。取数组地址的方法有:

        printf("%d ",&ages[0]);

        printf("%d ",ages);

    数组可以初始化,比如

    int a[2] = {8, 10};

    放在后面的元素可以省略,但是可读性不好,比如

    int a[2] = {8, };

    如果后面已经定义了全部元素则长度可省略,比如

    int a[] = {8, 10};

     

    C语言传参是传值,但是传数组(指针)的话则不是,实际传的是地址,所以内容会被改变。

    #include <stdio.h>

    //接受数组参数,可以不写长度

    void test(int array[])

    {

        array[0] = 9;

    }

    int main(int argc, const char * argv[])

    {

        int a[3];

        a[0] = 10;

        printf("%d ",a[0]);

        test(a);    //数组名代表地址,传的是指针

        printf("%d ",a[0]);

    }

     

    二维数组是一维数组的集合,是由一维数组组成的一维数组。它在内存中是按行存储的,比如a[0][0]->a[0][1]->a[0][2]->a[1][0]。

    取地址的方式有a,a[0],&a[0][0]。

    初始化可以按行:

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

    也可以都写出来:

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

    可以部分省略,默认为0:

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

    可以省略行数,不能省略列数:

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

     

    C语言没有String类型,多个字符用字符数组存储,为了和普通字符数组区分,字符串数组用’’结尾,必须写,否则可能会内存溢出,它是一个ascii码为0的字符,是空操作符,表示什么也不干,所以“mj”的长度是3,不是2。通常用下面第二种初始化:

    char s1[] = { 'm','j','' };

    char s2[] = "mj";

    打印的方式:

    printf("%s ", s2);

    puts(s2);

    放在尾部是因为输出过程会从字符串地址开始向后找第一个,所以必须要有结尾,没有结尾就会一直找下去,输出错乱的东西。

     

    字符串输入的过程会自动在尾巴加,例子:

        char s[20];

        scanf("%s", s); //s就是地址,不需要&s

    printf("%s", s);

    但是gets是不安全的,原理同上,会把另一个字符串s2里的内容冲掉,例如:

        char s2[] = "mj";

        char s1[2];

        gets(s1);

        printf("%s ", s1);

    printf("%s ", s2);

     

    字符串本身就是数组,如果要存储多个字符串,则需要使用二维数组,比如char names[15][20]表示可以存15个名字。代码:

    char names[2][20] = {{"jay"},{"jim"}};

     

    字符串处理的两个方法:

        //输出到控制台

        putchar('a');

        //等待用户输入

        char c;

    c = getchar();

     

    字符串处理函数声明在string.h当中。

    测量字符串的字符长度:

        int len = strlen("李明杰");

    printf("%d ", len);

    输出9,strlen返回字符串的字符数,不是长度,中文是3个字符。

    字符串拷贝:

        char left[10];

        strcpy(left, "itcast");

    printf("%s", left);

    从右边的常量拷贝给左边的变量并且自动加。

    拼接字符串:

        char left[10] = {'m','j',''};

        strcat(left, "ios");

    printf("%s", left);

    把右边的字符串,接在左边的后面,会去掉左边的“第一个”,但是要保证左边字符串的长度足够,否则会内存溢出。

    字符串比较:

        int delta = strcmp("abc","ABC");

    printf("%d", delta);

    返回左边减右边的差,一位一位地比ascii码,就是0.

     

    指针变量用来保存一个特定类型的变量的地址,如:

        char a;

        char *b = &a;

        *b = 'a';

    printf("%c",a);

    b是一个char*类变量指向a的地址,第三行*b当中的*是指针运算符,*b表示访问b的值(a的地址)所对应的存储空间。

    指针类型所占用的空间只和编译器有关。

    其中第二句可以拆分成两句:

        char *b;

    b = &a;

    第二句不能写*b,*是访问符。

     

    指针操作的两个错误:

        //错误1:不要直接使用未分配的指针

        char *p;

        *p=10;

        //错误2:不要给指针变量直接赋地址

    p = 100;

     

    交换a和b的例子:

    void swap(int *v1, int *v2)

    {

        int temp = *v1;

        *v1 = *v2;

        *v2 = temp;

    }

    int main(int argc, const char * argv[])

    {

        int a = 10;

        int b = 9;

        swap(&a,&b);

        printf("%d %d ",a,b);

    }

    因为默认会传临时变量,所以要传地址,参数表用指针类型,调用的时候就要传地址进去。交换的时候也要用星号来取指针变量里的地址对应的值。

     

    用指针可以实现函数多返回值,类似c#的out参数。比如:

    int sumAndMinus(int v1, int v2, int *p) {

        *p = v1 - v2;

        return v1 + v2;

    }

    int main(int argc, const char * argv[])

    {

        int a = 10;

        int b = 4;  

        int sum;

        int minus;

        sum = sumAndMinus(a,b,&minus);

        printf("%d %d",sum,minus);

    }

     

    数组的名字就是它的地址,所以把指针指向数组的时候,后面两行代码是等价的:

        int a[2];

        int *p;

        p = &a[0];

    p = a;

     

    可以用指针遍历数组,如下:

        int a[3] = {1,2,3};

        int *p = a;

        for (int i=0; i<3; i++) {

            printf("a[%d]=%d ",i,*(p+i));

    }

    对于指针变量来说,p+i当中的i要看p指向的类型,比如指向一个两个字节的类型,i就是两个字节,不是纯粹加一个数字。上面的做法不会改变p所指向的内容,但是如果写*(p++)则会改。

    另外,既然数组名就是p所指的,所以*(a+i)也可以,但是*(a++)不可以,数组的首地址是常量不能改。

    像下面这样也可以,只是p最终位置改变了:

        int *p = a;

        for (int i=0; p < a + 3; i++, p++) {

            printf("a[%d]=%d ",i,*p);

    }

     

    如果参数表是数组,那么传数组名或指针都可以:

    void change(char c[]) {

        c[0] = 1;

    }

    int main(int argc, const char * argv[])

    {

        char a[3];

        change(a);

    }

    如果参数表是指针,那么也可以传指针:

    void change(char *c) {

        *c = 1;

    }

    int main(int argc, const char * argv[])

    {

        char a[3];

        change(a);

        printf("%d ", a[0]);

    }

    总之,如果形参是数组或指针,则可以传递数组名或指针。

     

    对于一个字符串,遍历的方式有:

        char s[7] = "itcast";

        for (int i=0; s[i]!=''; i++) {

            printf("%c ", s[i]);

        }

    比较好理解的方法是:

        char *p = "itcast";

        for (; *p!=''; p++) {

            printf("%c ", *p);

    }

    第一种方式利用数组定义的是字符串变量,但是第二种方式用指针定义的是字符串常量,所以,第二种方法一旦用下面的方式来改写就错了:

        char *p = "lmj";

    *p = 'f';

    总之,char a[] = “lmj”是变量,char *p = “lmj”是常量,严格来说前面应该加上const。

     

    一个函数可以返回一个指针,比如:

    char * test() {

        return "itcast";

    }

     

    函数的名称就代表函数的地址,可以定义指向函数的指针,比如:

    #include <stdio.h>

    int sum(int a, int b) {

        return a+b;

    }

    int main(int argc, const char * argv[])

    {

        //定义一个特定返回值和参数表的指针p

        //需要占位的是函数名,并且指向sum函数

        int (*p)(int, int);

        p = sum;

        //利用指针变量p取出所指的函数,间接调用

        int result = (*p)(1,2);

        //也可以直接调用

        int result2 = p(5,6);

        printf("%d %d", result,result2);

    }

     

    可以把函数指针当做参数来使用,类似c#当中传递lambda表达式:

    #include <stdio.h>

    int calculate(int a, int b, int (*p)(int,int)) {

        return p(a,b);

    }

    int sum(int a,int b){

        return a+b;

    }

    int main(int argc, const char * argv[])

    {

        int result = calculate(1,2, sum);

        printf("%d", result);

    }

     

    预处理指令以#开头,是在编译之前执行的,三种常用的预处理指令分别是宏定义、文件包含和条件编译。

    宏定义的功能是字符串替换,通常用来定义常量:

    #define NUM 6

    int main(int argc, const char * argv[])

    {

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

    }

    也可以使用带有参数的宏定义:

    #define mul(a,b) ((a)*(b))

    int main(int argc, const char * argv[])

    {

        int a = mul(1,2);

        printf("%d",a);

    }

    定义带有参数的宏最好把参数带上括号,因为它的实质是字符串替换。最外层最好也加一个括号,因为宏替换之后,它的整体还会和其他代码进行数学运算。

    宏定义没有内存检测,纯粹是字符串替换,所以一些简单的计算,执行起来性能比函数要好。

     

    条件编译指,某段代码,只让它在满足某种条件时才编译。

    #include <stdio.h>

    #define NUM 10

    int main(int argc, const char * argv[])

    {

    #if NUM > 0

        printf("NUM大于0");

    #elif NUM == 0

        printf("NUM等于0");

    #else

        printf("NUM小于0");

    #endif

        return 0;

    }

    注意预处理指令里的宏NUM只能用预处理指令里定义的。

    另外,可以根据有没有定义过宏来判断:

    #define NUM 10

    int main(int argc, const char * argv[])

    {

    #ifdef NUM

        printf("定义了");

    #endif

       

    #ifndef NUM

        pringf("没定义");

    #endif

        return 0;

    }

     

    C的变量有不同的存储类型、生命周期和作用域。

    局部变量只在函数内有效。全局变量被其他函数共享,从定义的位置到源代码结尾有效。

    存储类型:有三个地方可以存变量:运行时堆栈、普通内存和硬件寄存器,它决定了变量的生命周期。

    三个地方分别对应自动变量、静态变量和寄存器变量。

    被关键字auto修饰的“局部”变量是自动变量,默认所有局部变量都是自动变量,基本不写auto。当执行到自动变量所在的函数时,自动变量会创建,离开函数时会销毁。

    静态变量会在程序运行时创建,在程序结束后销毁。所有全局变量都是静态变量。另外有一种被static修饰的局部变量也是静态变量,这种修饰改变了它的生命周期,但是没改变作用域,它的创建时间也是在函数调用的时候。函数内的static变量可以在函数内部进行计数。

    存储在硬件寄存器内的变量叫寄存器变量,它被register修饰,性能最高。只有自动变量才能存储在寄存器中,只限存储int、char和指针类型。如果寄存器已满,在运行时会自动转化为自动变量处理。寄存器变量通常是一些频繁使用的变量。寄存器变量的生命周期和自动变量一致。

     

    允许被其他源文件调用的函数是外部函数,函数默认是外部函数,不允许有同名的外部函数,否则会有链接错误。

    定义或者提前声明一个外部函数需要用extern,但是默认就是外部的,所以这个关键字一般不写。在C99标准当中,如果一个源文件里没有提前声明外部函数会报编译错误,但是xcode里不会出错。

    不允许其他文件访问的函数是内部函数,不同源文件里允许有同名的内部函数。C的static和java中完全不一样,内部函数需要用static关键字修饰,这样在链接的时候其他源文件就无法找到这个static函数了。在同一个源文件中,声明一个static函数也需要加这个关键字。

     

    C语言中一个函数不能使用在函数后面声明的变量,除非在函数前面声明,声明变量的关键字是extern,这个extern其实又可以省略:

    extern int a;

    int main(int argc, const char * argv[])

    {

        a = 10;

        return 0;

    }

    int a;

    在一个函数内,如果用extern声明了一个变量,那么它使用的还是外部的变量,extern本来就是外部的意思,证明:

    #include <stdio.h>

    void test();

    int main(int argc, const char * argv[])

    {

        extern int a;

        a=10;

        test();

    }

    int a;

    void test()

    {

        printf("%d", a);

    }

    在C语言中在不同的源文件当中存在同名的全局变量,则这些变量都代表同一个变量。允许被其他文件访问的变量叫外部变量,默认情况下定义的变量都是外部变量。想使用其他文件里的外部变量,需要在前面声明(extern可省)。注意,extern后面的变量,一定是声明,而不是定义!

    用static修饰的全局变量叫做内部变量,外部访问不到这个变量(外部的extern声明语句无法看到内部的static变量)。如果说两个文件中的同名变量有一个或者都加了static,则它们不是一个变量。

     

    结构体可以定义在方法内部或外部,如果在内部,则只能在函数内使用这个结构体:

    void main(int argc, const char * argv[])

    {

        //定义结构体类型

        struct Student {

            int age;

            char *name;

            float height;

        };

       

        //定义结构体变量

        struct Student stu = { 27, "mj", 180.3};

        printf("%d", stu.age);

    }

    也可以同时定义结构体并定义变量赋值,在这种情况下结构体的名字可以省略,因为没有意义,类似java的匿名方法:

        struct {

            int age;

            char *name;

            float height;

    } stu = { 27, "mj", 1.8f};

    结构体内可以包含别的结构体,但是不允许包含自己导致递归。需要注意的是,结构体的定义和初始化要写成一句,如果拆成两句会报错。

     

    可以定义指针指向结构体,通过指针有两种方式取结构体内的值:

        struct Student stu = { 27, "mj", 180.3};

        struct Student *p = &stu;

        int a =(*p).age;

        int b = p->age;

     

    把结构体当做参数传给函数,传的是值,不是地址,传地址需要指针:

    struct Student {

        int age;

    };

    void change(struct Student* stu) {

        stu->age = 1;

    }

    void main(int argc, const char * argv[])

    {

        struct Student stu = { 27, "mj", 180.3};

        struct Student *p = &stu;

        change(p);

        printf("%d", stu.age);

    }

     

    C会把枚举当做整形常量,从0开始,定义枚举:

        enum Season { spring, summer, autumn, winter };

    enum Season s = spring;

    也可以同时定义枚举变量并赋值,和结构体一样,枚举的名称也可以省略。

     

    typedef关键字的作用是给数据类型定义一个别名,比如:

    typedef int Integer;

    typedef char* String;

    void main(int argc, const char * argv[])

    {

        Integer a = 10;

        String name = "itcast";

    }

    可以给结构体起别名,这样定义变量的时候就能省略struct关键字了,这时建议用匿名的,因为没用:

    void main(int argc, const char * argv[])

    {

        typedef struct {

            float x;

            float y;

        } CGPoint;

       

        CGPoint p = {10, 10};

    }

    例子:给指向结构体的指针重命名,此时也可以匿名:

        typedef struct Point{

            float x;

            float y;

        } * PPoint;

        struct Point point = {10,10};

        PPoint pp = &point;

    事实上最常用的定义结构体和枚举的方式是这样,表意非常明确——给一个匿名的结构体命名:

        typedef struct {

            float x;

            float y;

    } Point;

     

    还可以给一个指向函数的指针一个别名:

    int sum(int a, int b){

        return a+b;

    }

    void main(int argc, const char * argv[])

    {

        typedef int (*SumPoint)(int,int);

        SumPoint p = sum;

        p(1,2);

    }

    在这里SumPoint就是别名,不用在后面再起别名。

     

    不建议用宏定义来其别名,它纯粹是字符串替换,如果连续声明了两个变量就会有问题了。

     
     
    分类: IOS开发
  • 相关阅读:
    vue 移动端商城搭建 (一)
    关于mantisBT2.22安装插件Inline column configuration 2.0.0时提示缺少依赖jQuery UI Library 1.8
    mantis2.22.1中添加管理员密码修改框
    mantisbt2.22.1 中使用自带的phpmailer发送邮件(实测可用)
    Mac配置Jenkins(构建Allure模板报告)
    ATX插件机制-学习学习
    查看包名和Activity的小工具
    pytest跳过指定的测试或模块
    pytest常用命令参数
    关于pytest使用allure生成报告时,报一堆警告和缺少XX模块
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3436605.html
Copyright © 2011-2022 走看看