美文网首页
C语言基础

C语言基础

作者: Caesar_62dd | 来源:发表于2019-05-09 11:01 被阅读0次

练习

字母大小写转换

#include <stdio.h>
int main(int argc, const char * argv[]) {
    printf("请输入一个字符回车结束,将会给你的字母自动转换为小写 \n");
    char charValue;
    scanf("%c", &charValue);
    if (charValue >= 'A' || charValue <= 'Z') charValue = charValue + ('a' - 'A' );
    printf("\n%c\n",charValue);
    return 0;
}    

十进制转二进制

#include <stdio.h>
void printBinary(int value);
int main(int argc, const char * argv[]) {
    int num = 9;
    printBinary(num);
    return 0;
}
void printBinary(int value){
    for (int offset = 31; offset >= 0; offset--) {
        int result = (value >> offset) & 1;
        printf("%i",result);
            if (offset % 4 == 0) {
                printf(" ");
            }
        }
     printf("\n");
}

奇偶数

#include <stdio.h>

int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, World!\n");
    
    int num = 10;
    if ((num & 1) == 1)
        printf("奇数\n");
    else printf("偶数\n");
  
    
    if (num & 1)
        printf("奇数\n");
    else printf("偶数\n");
    return 0;
}

两个数交换

#include <stdio.h>

int main(int argc, const char * argv[]) {
    // insert code here...
    printf("Hello, World!\n");
    
    int a = 10 , b = 5;
    printf("交换前: a = %d,  b = %d\n--------------------------\n", a, b);
    //temp变量交换
    printf("temp变量交换:\n");
    int temp = b;
    b = a;
    a = temp;
    printf("a = %d,  b = %d\n--------------------------\n", a, b);
    //加法变换
    printf("加法变换:\n");
    a = a + b;
    b = a - b;
    a = a - b  ;
    printf("a = %d,  b = %d\n--------------------------\n", a, b);
    //异或^ 交换.  一个数 异或 宁外一个数两次等于它本身 a^b^b = a ; b^b = 0; a^0 = a;
    printf("异或^ 交换. \n");
    a = a ^ b;
    b = a ^ b;
    a = a ^ b;
    printf("a = %d,  b = %d\n--------------------------\n", a, b);
    //异或^ 交换方法二
    printf("异或^ 交换方法二\n");
    b = a ^ b;
    a = a ^ b;
    b = a ^ b;
    printf("a = %d,  b = %d\n--------------------------\n", a, b);
        0
    int num = 10, num2 = 88;
    printf("修改前:num = %i,  num2= %i\n", num, num2);
    swap(&num, &num2);
    printf("修改后:num = %i   num2= %i\n ",num, num2);
    return 0;
}
void swap (int *p1, int *p2){
    int temp = *p1;
    *p1 = * p2;
    *p2 = temp;
}

int取值范围

int的取值范围为: -231——231-1,即-2147483648——2147483647

1、对于正数来说,它的补码就是它本身。
2、对于负数来说,它的补码是它对应的正数的二进数所有位取反之后加一。
3、由负数的补码求原码也是相同的操作(所有位取反+1)即为该负数的绝对值int是4个字节,32位,10000000 00000000 00000000 00000000 是补码,第一位为符号位,1表示负数,所以对剩下的位取反,结果为 1111111 11111111 11111111 11111111,加一后为10000000 00000000 00000000 00000000
4、所以原码指的是-2^31=-2147483648
5、int的取值范围为-231——231-1

进制表转换

#include <stdio.h>
void printBinary(int num);
void printOct(int num);
void printHex(int num);
void total (int value, int base, int offset);
int main(int argc, const char * argv[]) {
    int num = 1220;
    printBinary(num);
    printOct(num);
    printHex(num);
    return 0;
}
void printBinary(int num){
    total(num, 1, 1);
}
void printOct(int num){
    total(num, 7, 3);
}
void printHex(int num){
    total(num, 15, 4);
}
void total (int value, int base, int offset){
    //1.定义一个所有取值的表
    char charValue[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
    //2.定义一个表用于存值
    char results[32] = {'0'};
    //2.1 定义存表长度(位置)
    int pos = sizeof(results) / sizeof(results[0]);
    //3.按位与计算
    while (value != 0) {
        //3.1将转换后的索引在取值表charValuew中查找,并倒叙保存到result[]
        results[--pos] = charValue[value & base];
        value = value >> offset;
    }
    for (int i = pos; i < 32; i++) {
        printf("%c",results[i]);
    }
    printf("\n");
}

数组

数组元素作为函数参数

  • 数组元素就是下标变量,它与普通变量并无区别。 因此它作为函数实参使用与普通变量是完全相 同的,在发生函数调用时,把作为实参的数组元素的值传送给形参,实现单向的值传送。
  • 数组的元素作为函数实参,与同类型的简单变量作为实参一样,是单向的值传递,即数组元素的值传给形参,形参的改变不影响实参
  • 用数组元素作函数参数不要求形参也必须是数组元素

数组名作为函数参数

  • 在C语言中,数组名除作为变量的标识符之外,数组名还代表了该数组在内存中的起始地址, 因此,当数组名作函数参数时,实参与形参之间不是"值传递",而是"地址传递",实参数组名将 该数组的起始地址传递给形参数组,两个数组共享一段内存单元,编译系统不再为形参数组分配 存储单元。==注意: &nums = &nums[0] == nums;==
  • 数组的名字作为函数实参,传递的是整个数组,即形参数组和实参数组完全等同,是存放在同一存储空间的同一个数组。这样形参数组修改时,实参数组也同时被修改了。形参数组的元素个数可以省略

gets和puts函数

    char buf2[10];
    printf("请输入一个字符串:\n");
    gets(buf2);
    puts(buf2);

多维数组的初始化

    //1.完全初始化
    char names[2][3] = {
        {'l','m','j'},//二维数组[行数][列数]
        {'x','m','g'}
    };
    //2.先定义后初始化
    int names2[2][3];
    names[0][0] = 'l';
    names[0][1] = 'm';
    names[0][2] = 'j';
    names[1][0] = 'x';
    names[1][1] = 'm';
    names[1][2] = 'g';
    //3.不完全初始化
    int names3[2][3] = {
        {'l', 'n'}
    };
    //4.初始化的省略形式
    char names4[2][3] = {
        'l','m','j',//二维数组[行数][列数]
        'x','m','g'
    };
    //5.省略二维数组的一维数组,系统会根绝每一个一维数组能够存放多少个元素,
    //自动根据初始化值推算出有多少一维数组,
    char names5[][3] = {
        'l','m','j',
        'x','m','g'
    };

字符串

字符串常用方法

    char str1[20] = "Gao";
    char str2[] = "  Xiong";
    char str3[10] = "Peng";
    char str4[] = "   lu";
    //    1.字符串拼接
    //    dest:目标  src: 源
    //    目标字符串字符长度要大于拼接之后的长度 否则报错.
    strcat(str1, str2);
    puts(str1);
    //    1.1 字符串拼接,可选需要拼接的长度.   strncat
    size_t length = sizeof(str3) / sizeof(str3[0]) - strlen(str3) - 1;
    (str3, str4, length);
    // printf("拼接后:%s\n",str3);

    //    2.字符串拷贝
    strcpy(str3, str4);
    puts(str3);
    //    2.1  strncpy  size_t需要拷贝几个,注意,b拷贝操作是逐个替换,b拷贝几个就替换件几个
    strncpy(str1, str2, length-1);
    printf("str2 拷贝 到 str1 后: %s\n",str1);
    //    3.字符串比较
    //  比较两个字符串的ASCII,相等返回0,小于返回负数,大于返回正数.
    char str5[] = "abc";
    char str6[] = "abcd";
    int res = strcmp(str5, str6);
    printf("res = %i\n",res);

查找string中是否含有某个字符

int main(int argc, const char * argv[]) {
    char str[] = "penglu";
    char key = 'u';
    containKey(str, key);
    return 0;
}

void containKey(char str[], char key)
{
    int index = -1;
    while (str[++index] != key && str[index] != '\0');
    int res = str[index] != '\0' ? 1 : 0;
    printf("res = %i\n", res);
}

字符串数组声明和初始化

    char names[3][20] = {
        "lnj",
        "lmj"
    }
    //字符串数组用二维数组表示, 不能省略 '\0'
    char names2[3][20] = {
        {'l','n','j','\0'},
        {'l','m','j','\0'}
    };

指针

#include <stdio.h>
void change (int *p);
int main(int argc, const char * argv[]) {
    int num = 10;
    int *p = NULL;//指针定义中的*没有意义,指针在64位操作系统占8个字节,指针只能存储地址
    //指针类型 不要混淆, 不要访问野指针
    p = &num;//将num的地址存储到指针p中
    
    //p == &num
    printf("num = %p\n", &num);
    printf("p = %p\n", p);
    
    //指针变量前的*号代表访问指针指向的那一块内存
    //*p == num
    printf("修改前:num = %i\n", num);
    change(&num);
    printf("修改后:num = %i\n", num);
    return 0;
}
void change (int *p){
    *p = 55;
    printf("执行了-------------------------\n");
}

指针类型的作用:在取值的时候根据指针类型,确定应该取多少个字节.指针的类型要和所指向的数据类型一致,否则数据错误.

多级指针

int main(int argc, const char * argv[]) {
    char charValue = 'G';
    printf("charValue初始化的值:%c\n", charValue);
    char *charValueP = &charValue;
    char **charValuePP = &charValueP;
    //更改charValue的值,输出
    charValue = 'X';
    printf("直接修改charValue的值:charValue = %c\n", charValue);
    *charValueP = 'Y';
    printf("修改*charValueP的值:*charValue = %c\n", *charValueP);
    **charValuePP = 'Z';
    printf("修改**charValuePP的值:**charValuePP = %c\n", **charValuePP);
    return 0;
}

指针与数组

数组元素指针

  • 一个变量有地址,一个数组包含若干元素,每个数组元素都有相应的地址 指针变量可以指向数组元素(把某一元素的地址放到一个指针变量中) 所谓数组元素的指针就是数组元素的地址
printf(“%p %p”, &(a[0]), a);
输出结果:0x1100, 0x1100
  • 注意: 数组名a不代表整个数组,只代表数组首元素的地址。但是,数组名所所保存的数组的首地址是不可以更改的
  • “p=a;”的作用是“把a数组的首元素的地址赋给指针变量p”,而不是“把数组a各元素的值赋给 p”
    int x[10];
    x++;  //illegal
    int* p = x;
    p++; //legal

在指针指向数组元素时,允许以下运算:

  • 加一个整数(用+或+=),如p+1
  • 减一个整数(用-或-=),如p-1
  • 自加运算,如p++,++p
  • 自减运算,如p--,--p

如果指针变量p已指向数组中的一个元素,则p+1指向同一数组中的下一个元素,p-1指向同 一数组中的上一个元素。

指针变量之间运算

  • 两指针变量相减所得之差是两个指针所指数组元素之间相差的==元素个数==。
    • 实际上是两个指针值(地址)相减之差再除以该数组元素的长度(字节数)。
    • (pointer2地址值 -pointer地址值) / sizeof(所指向数据类型)
  • 注意:
    • ==指针之间可以相减,但不可以相加(相加无意义)==
int main(int argc, const char * argv[]) {
    //利用指针操作数组
    int ages[] = {1, 3, 5};
    int *p = ages;//或者 int *p = &ages[0]    因为 &ages[0] == ages
    p[0] = 299;
    printf("ages[0] = %i\n----------\n", ages[0]);
    //1.指针遍历数组装x写法
    for (int i = 0; i < 3; i++) printf("装X写法:ages[%i] = %i\n", i, i[p]);
    //2.指针遍历数组普通写法
    for (int i = 0; i < 3; i++) printf("普通写法:ages[%i] = %i\n", i, *p++);
    return 0;
}

指针与字符串

    char *string2 = "I love lulu";
    for (int i = 0 ; i < strlen(string2); i++) {
        printf("%c",i[string2]);
    }

​ 保存字符串的两种方式:

char str[] = "gx";

存储的位置: 栈

特点: 相同的字符串会重复的分配存储空间

字符串可以修改

char *str = "gx"

存储的位置: 常量区

特点: 相同的字符串不会重复的分配存储空间

字符串不可以修改

  • 注意:
    • 用指针保存的字符串不可以被修改
    • 指针没有初始化不能随便使用
  • 指针数组:
char *names[] = {
        "gx",
        "pl",
        "sw",
        "xx"
    };
    for (int i = 0; i < 4; i++) {
        printf("name[%i] = %s\n",i ,names[i]);
    }
  • 指针计算字符串长度:
int myLength (char *str);
int main(int argc, const char * argv[]) {
    char *string2 = "I love lulu";
    int size = myLength(string2);
    printf("size = %i\n",size);
    return 0;
}
int myLength (char *str){
    int count = 0;
    while (*str++) count++;
    return count;
}

指针与函数

  • 格式:函数的返回值类型 (*指针变量名)(形参1, 形参2, ...);
  • 技巧:
    • 1.把要指向的函数头拷贝过来
    • 2.把函数名称使用小括号括起来
    • 3.在函数名称前加上*
    • 4.修改函数名称
  • 应用场景:
    • 调用函数
    • 将函数作为参数在函数间传递
    • 团队开发
int sum (int v1, int v2)
{
    return v1 + v2;
}

int minus (int v1, int v2)
{
    return v1 -v2;
}
//新增
int multiple (int v1, int v2)
{
    return v1 * v2;
}
//正价功能时,只需要添加新的方法,不需要修改调用方法demo
int demo (int v1, int v2, int (*function)(int,int))
{
    return function(v1, v2);
}

int main(int argc, const char * argv[]) {
    printf("sum = %i\n",demo(10, 20, sum));
    printf("minus = %i\n",demo(30, 20, minus));
    printf("multiple = %i\n", demo(10, 88, multiple));
    return 0;
}

  • 注意:
    • 由于这类指针中变量存储的是一个函数的入口地址,所以对他们做加减运算(如P++)是无意义的
    • 函数调用中"(指针变量名)"的两边的括号是不可少,其中的不应该理解为求值运算,在此处它只是一种表示符号
  • 练习,将每个单词首字母大写
#include <stdio.h>
void upperCase(char *value);
void transfer(char *string , void (*functionPointer)(char *));
int main(int argc, const char * argv[]) {
    printf("请输入一段英文,以回车结束:\n");
    char string[100];
    gets(string);
    transfer(string, upperCase);
    printf("%s\n",string);
    return 0;
}
void upperCase(char *temp)
{
        if (*temp >= 'a' && *temp <= 'z') {
            *temp = *temp - ('a'-'A');
        }
}
void transfer(char *string , void (*functionPointer)(char *))
{
    functionPointer(string);
    while (*string++) {
        if(*string == ' ')
        functionPointer(++string);
    }
}

结构体

    //定义结构体,但是 并不会分配内存空间
    struct Person {
        char *names;//8
        int ages;//4
        double height;//8
    };
    //匿名定义,无法调用
    struct {
      char *name;
      int ages;
    }dog;
    //1.定义结构体变量,才会开辟存储空间
    struct Person Peng;
    //1.1结构体变量逐个初始化
    Peng.names = "Peng Lu";
    Peng.ages = 20;
    Peng.height = 1.88;
    //2.1定义的同时初始化
    struct Person Gao = {"Gao Xiong", 21, 2.11};
    //3.先定义,再一次性初始化
    struct Person Xiao;
    Xiao = (struct Person) {"Xiao Xiong", 17, 2.11};//强制类型转换
    //4.指定将数据赋值给指定属性
    struct Person WW = {.height = 1.75, .names = "WWWW WWW", .ages = 21};
    //结构体如何分配内存空间
    //结构体会首先找到所有属性中占用内存空间最大的那个属性(为了提高计算机运行速度),然后按照该属性的倍数来分配存储空间
    //会从第0个属性开始分配空间,
    //如果存储空间不够,那么久会把当前属性直接存储到新开辟的内存空间,以前剩余的存储空间就不要了,如例1
    //如果存储空间还有剩余,那么就会把后面的属性的数据存储到剩余的存储空间中,如例2
    printf("size = %lu\n", sizeof(Peng));// 例1 :size = 24
    struct Dog {
        double height;//8
        int ages;//4
        char color;//1
    };
    struct Dog NN = {1.44, 3, 'Y'};
    printf("size = %lu\n", sizeof(NN));//  例2:size = 16
  • 结构类型定义在函数内部的作用域与局部变量的作用域是相同的
  • 定义一个全局结构体,作用域到文件末尾
  • 定义一个局部结构体,在不同作用域,可以有同名结构体

结构体与指针

int main(int argc, const char * argv[]) {
    struct student {
        char *names;
        int ages;
    };
    struct student pl;
    struct student *plPointer = &pl;
    plPointer->ages = 18;
    plPointer->names = "peng Lu";
        //访问结构体成员变量的三种方式
        // 方式1:结构体变量名.成员名
    printf("1st way:names = %s, ages = %i", pl.names, pl.ages);
        // 方式2:(*指针变量名).成员名
    printf("2nd way:names = %s, ages = %i", (*plPointer).names, (*plPointer).ages);
        // 方式3:指针变量名->成员名
    printf("3th way:names = %s, ages = %i", plPointer->names, plPointer->ages);
    return 0;
}

结构体与数组

    struct student {
        char *names;
        int  ages;
    };
    struct student xiaoxue1ban[3] = {
        {"PengLu", 8},
        {"GaoXiong", 9},
        {"WuKong" , 500}
    };
    printf("names = %s, ages = %i\n",xiaoxue1ban[2].names,xiaoxue1ban[2].ages);

结构体嵌套

#include <stdio.h>
struct time{
    int hour;
    int min;
    int sec;
};
struct date {
    int year;
    int month;
    int day;
    struct time time;
};
struct Person {
    char *names;
    int ages;
    struct date birthday;
};

int main(int argc, const char * argv[]) {
    struct Person LL ={
       "Lulu",
        20,
        {
        1998,
        12,
        31,
            {
            20,
            10,
            30
            }
        }
    };
        // 注意: 如果结构体的属性又是一个结构体, 那么可以通过连续.的方式, 访问结构体属性中的属性
        //      如果结构体类型中的属性又是一个结构体, 那么赋值时候通过{}赋值
        //      结构体中的赋值全是逗号
    printf("names: %s,\nages: %i\nbirthday: %i.%i.%i, %i:%i:%i\n", LL.names, LL.ages, LL.birthday.year, LL.birthday.month, LL.birthday.day, LL.birthday.time.hour, LL.birthday.time.min, LL.birthday.time.sec);
    return 0;
}

结构体和函数

1.成员值作为函数的参数

  • 结构体成员属性作为函数的参数就是值传递(成员变量是数组除外)。

2.结构体变量名作为函数的参数

  • 将结构体变量作为函数参数进行传递时,其实传递的是全部成员的值,也就是将实参中成员的值一一赋值给对应的形参成员。因此,形参的改变不会影响到实参。

3.结构指针作为函数的参数

  • 结构体指针作为函数的参数是地址传递时,形参的改变影响到实参
struct student {
    char *names;
    int ages;
};
void changeName (struct student *name);
void changeAge (int ages);
int main(int argc, const char * argv[]) {
    struct student yang = { "yangguo", 100};
    puts(yang.names);
    changeName(&yang);
    puts(yang.names);
    changeAge(yang.ages);
    printf("ages = %i\n",yang.ages);
    return 0;
}
void changeName (struct student *name)//可以修改实参
{
    name->names = "xiaolongnv";
}
void changeAge (int ages) //无法修实参
{
    ages = 1020;
}

枚举

int main(int argc, const char * argv[]) {
    enum Gender {
        kGenderMale,//默认情况下,枚举的第0个取值是0,
        kGenderFemale//第二个取值是1,后面的值依次递增
    };
        //枚举类型变量可以当做整型的变量使用
    enum Gender sex;
    sex = kGenderMale;
    sex = kGenderFemale;
    enum Season {
        kSeasonSpring,
        kSeasonSummer,
        kSeasonAutumn,
        kSeasonWinter
    };
        //枚举类型规范:
        //    1.一班以k开头,后面跟上枚举类型名称,加当前取值含义
        //    2.和结构体一样,枚举类型的首字母大写
    enum Season es = kSeasonAutumn;
    printf("%u\n", es);//2
    return 0;
}

作用域

  • 局部变量

    • 定义在函数内部的变量以及函数的形参称为局部变量~
    • 作用域:从定义哪一行开始直到与其所在的代码块结束
    • 生命周期:从程序运行到定义哪一行开始分配存储空间到程序离开该变量所在的作用域
    • 局部变量没有固定的初始化,里面是随机值.

    特点:

    • 1、相同作用域内不可以定义同名变量
    • 2、不同作用范围可以定义同名变量,内部作用域的变量会覆盖外部作用域的变量
    • 3、局部变量存储在栈中,当作用域结束,系统会自动释放栈中的局部变量
  • 全局变量

    • 定义在函数外边的变量称为全局变量
    • 作用域范围:从定义那行开始直到文件结尾
    • 生命周期:程序已启动就会分配存储空间,直到程序结束
    • 存储位置:静态存储区

    特点:

    • 多个同名的全局变量指向同一块存储空间
    • 如果存在同名的局部变量,那么局部变量会覆盖全局变量(就近原则)
    • 如果全局变量没有初始化,系统默认将全局变量初始化为 0

static和extern关键字

static与extern对局部变量的作用

  • static对局部变量的作用
    • 延长局部变量的生命周期,从程序启动到程序退出,但是未改变其作用域
    • 存储空间从栈转移到静态区
    • 定义变量的代码只会在程序运行过程执行一次
    • 应用场景:当某个方法中调用频率非常高,而该方法中有一些变量的值是固定不变的,那么这个时候可以使用static来修饰该变量,让该变量只开辟一次存储空间 (提高效率和性能)
  • extern用在函数内部
    • 不是定义局部变量,它是用在函数内部是声明一个全局变量

static 全局变量的作用

  • 全局变量分类:
    • 内部变量:只能在本文件中访问的变量
    • 外部变量:可以在其他文件中访问的变量,默认所有的全局变量是外部变量
  • static 对全局变量的作用
    • 声明一个内部变量staic int a;//未分配存储空间
    • 定义一个内部变量static int a = 10;
  • 由于静态全局变量的作用域局限在一个源文件内,只能为该文件内的函数公用,因此可以避免在其他源文件中因其错误

extern 全局变量的作用

  • extern作用
    • 完整声明一个外部变量extern int a;//未分配存储空间
    • 完整定义一个外部变量extern int a = 10;
  • 如果声明的时候没有写extern那系统会自动定义这个变量,并将其初始化为0
  • 如果生命的时候写了extern了,那系统不会自动定//未分配存储空间

static 与 extern对函数的作用

  • 内部函数:只能在本文件中访问的函数
  • 外部函数:可以在本文件中以及其他的文件中访问的函数
  • 默认情况下所有的函数都是外部函数

预处理命令

宏定义

不带参数的宏定义

格式: #defin 标识符 字符串

  • 注意点:
      1. 宏名一般用大写字母,以便与变量名区别开来,但用小写也没有语法错误,宏定义后面不写分号
      1. 对程序中用双引号扩起来的字符串内的字符,不进行宏的替换操作
      1. 在编译预处理用字符串替换宏名时,不作语法检查,只是简单的字符串替换。只有在编译的时候才对已经展开宏名的源程序进行语法检查
      1. 宏名的有效范围是从定义位置到文件结束。如果需要终止宏定义的作用域,可以用#undef命令
      1. 定义一个宏时可以引用已经定义的宏名
      1. 可用宏定义表示数据类型,使书写方便

带参数的宏定义

  • C语言允许宏带有参数。在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数。对带参数的宏,在调用中,不仅要宏展开,而且要用实参去代换形参。

  • 格式: #define 宏名(形参表) 字符串

  • 注意点:

    • 1)宏名和参数列表之间不能有空格,否则空格后面的所有字符串都作为替换的字符串.
    • 2)带参数的宏在展开时,只作简单的字符和参数的替换,不进行任何计算操作。所以在定义宏时,一般用一个小括号括住字符串的参数。
    • 3)计算结果最好也用括号括起来

条件编译

  • 在很多情况下,我们希望程序的其中一部分代码只有在满足一定条件时才进行编译,否则不参与编译(只有参与编译的代码最终才能被执行),这就是条件编译。
  • 为什么要使用条件编译
    • 1)按不同的条件去编译不同的程序部分,因而产生不同的目标代码文件。有利于程序的移植和调试。
    • 2)条件编译当然也可以用条件语句来实现。 但是用条件语句将会对整个源程序进行编译,生成 的目标代码程序很长,而采用条件编译,则根据条件只编译其中的程序段1或程序段2,生成的目 标程序较短

#if-#else 条件编译指令

  • 第一种格式:
#if 常量表达式
  ..code1...
#else
  ..code2...
#endif
  • 它的功能是,如常量表达式的值为真(非0),则对code1 进行编译,否则对code2进行编译。因此可以使程序在不同条件下,完成不同的功能。

  • 注意:条件编译后面的条件表达式中不能识别变量,它里面只能识别常量宏定义

  • 第二种格式:
#if 条件1
  ...code1...
 #elif 条件2
  ...code2...
 #else
  ...code3...
 #endif
  • 注意,条件编译结束后,要在最后面加一个#endif
  • #if#elif后面的条件一般是判断宏定义而不是判断变量,因为条件编译是在编译之前做的判断,宏定义也是编译之前定义的,而变量是在运行时才产生的、才有使用的意义

#ifndef 条件编译指令

#ifndef 标识符 程序段1
#else 程序段2
#endif
  • 它的功能是,如果标识符未被#define命令 定义过则对程序段1进行编译,否则对程序段2进行编译。这与第二种形式的功能正相反。
#include <stdio.h>
#define DEBUG1 0  // 0是调试阶段,1是发布阶段
#if DEBUG1 == 0 //调试
#define Log(format,...) printf(format,## __VA_ARGS__)
#else // 发布
#define Log(format,...)
#endif
void test(){
    Log("正在调试\n");
}
int main(int argc, const char * argv[]) {
    test();
    return 0;
}
#include <stdio.h>
#define SCORE 100
int main(int argc, const char * argv[]) {
#ifdef SCORE //判断是否定义Macro宏
    printf("score;\n");
#elif COUNT
    printf("count;\n");
#else
    printf("Others;\n");
#endif
    return 0;
}

typedef关键字

  • 概念:允许由用户为数据类型取“别名”

  • 格式: typedef 原类型名 新类型名; typedef int myInteger;

  • 也可以在别名的基础上再起一个别名 typedef myInteger Num;

  • 用typedef定义数组、指针、结构等类型将带来很大的方便,不仅使程序书写简单而且使意义更为 明确,因而增强了可读性。

    • 数组

      typedef char NAME[20];
      NAME a;//等价于 char a[20];
      
    • 结构体类型

      • 第一种形式(先定义结构体,再给类型起别名)

        struct Person{
          int age;
          char *name;
          double height;
        };
        typedef struct Person MyPerson;
        
        
      • 第二种形式(定义结构体的同时,给结构体起别名)

        typedef struct Person{
          int age;
          char *name;
          double height;
        }MyPerson;
        
        
      • 第三种形式(定义结构体的同时,给结构体起别名,同时省略掉原有类型名称)

        typedef struct {
          int age;
          char *name;
          double height;
        }MyPerson;
        
        
    • 枚举类型

      • 第一种形式(先定义枚举类型,再起别名)

        enum Gender{
          KGenderMale,
          KGenderFemal
        };
        typedef enum Gender SEX;
        
        
      • 第二种形式(定义的同时起别名)

        typedef enum Gender{
          KGenderMale,
          KGenderFemal
        }SEX;
        
        
      • 第三种形式(定义的同时起别名,省略原类型名称)

        typedef enum Gender{
          KGenderMale,
          KGenderFemal
        }SEX;
        
        
    • 函数指针 (十分重要)

    ​ 注意:如果是给指向函数的指针起别名,那么指向函数的指针的指针就是它的别名

    int minus (int v1, int v2)
    {
        return v1 - v2;
    }
    int sum (int v1, int v2)
    {
        return v1 + v2;
    }
    typedef int (*functionP) (int, int);
    
    int main(int argc, const char * argv[]) {
    //    int (*sumP)(int, int)  = sum;
        functionP sumP = sum;
    //    int (*minusP)(int, int)= minus;
        functionP minusP = minus;
        printf("sum = %i\n", sumP(30,30));
        printf("minus = %i\n", minusP(98,10));
        return 0;
    }
    
    

宏定义与函数和typedef区别

  • 宏定义与函数的本质区别

    • 宏定义不涉及存储空间的分配、参数类型匹配、参数传递、返回值问题
    • 函数调用在程序运行时执行,而宏替换只在编译预处理阶段进行。所以带参数的宏比函数具有更高的执行效率
  • typedef和#define的区别

    • 宏定义只是简单的字符串替换,在预处理玩成
    • typedef是在编译时处理的,它不是坐简单的代换,而是对类型说明符重新命名
    • 被命名的标识符具有定义说明的功能
  • 补充:程序编译过程.

    源代码 --> 预处理 -->汇编 -->二进制 -->可执行程序

const关键字

1.const是一个类型修饰符

  • 使用const修饰变量则可以让变量的值不能改变
  • 常类型是指使用类型修饰符const说明的类型,常类型的变量或对象的值是不能被更新的。

2.const有什么主要的作用?

  • (1)可以定义const常量,具有不可变性

    const int Max=100;
    int Array[Max];
    
    
  • (2)便于进行类型检查,使编译器对处理内容有更多了解,消除了一些隐患。

    void f(const int i){......} //编译器就会知道i是一个常量,不允许修改
    
    
  • (3)可以避免意义模糊的数字出现,同样可以很方便地进行参数的调整和修改。 同宏定义一样,可以做到不变则已,一变都变!如(1)中,如果想修改Max的内容,只需要:const int Max=you want;即可!

  • (4)可以保护被修饰的东西,防止意外的修改,增强程序的健壮性。 还是上面的例子,如果在 函数体内修改了i,编译器就会报错;

  • (5) 可以节省空间,避免不必要的内存分配。

    #define PI 3.14159 //常量宏
    const doulbe Pi=3.14159; //此时并未将Pi放入ROM中  
    double i=Pi; //此时为Pi分配内存,以后不再分配!
    double I=PI; //编译期间进行宏替换,分配内存
    double j=Pi; //没有内存分配
    double J=PI; //再进行宏替换,又一次分配内存! const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存 中有若干个拷贝。
    
    
  • (6) 􏰀高了效率。编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表 中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。

  • (5)修饰常指针

    • const int *A; //const修饰指针,A可变,A指向的值不能被修改
    • int const *A; //const修饰指向的对象,A可变,A指向的对象不可变
    • int *const A; //const修饰指针A, A不可变,A指向的对象可变
    • const int *const A;//指针A和A指向的对象都不可变
  • 技巧

    先看“*”的位置
    如果const 在 *的左侧 表示值不能修改,但是指向可以改。
    如果const 在 *的右侧 表示指向不能改,但是值可以改
    如果在“*”的两侧都有const 标识指向和值都不能改。
    
    

相关文章

网友评论

      本文标题:C语言基础

      本文链接:https://www.haomeiwen.com/subject/jsoioqtx.html