C语言学习记录(一):关键的关键字

来源:互联网 发布:域名需要几天 编辑:程序博客网 时间:2024/06/14 08:45

摘要: 本文介绍了C语言的一些关键字的用法和注意事项,包括:基本数据类型分析,属性-auto,register,static,条件语句-if,分支语句-switch,循环-do,while,for,goto,void,extern,sizeof,const和volatile,struct和union,enum和typedef。


有些事,虽然遥不可及,但并不是不能实现,只要我足够的强!
                              ——盖聂

wallhaven-851.jpg-355.2kB


基本数据类型

什么是数据类型?

数据类型可以理解为“固定内存大小+所限制存放的数据”的别名,是创建变量的模子。

  • char类型是“占一个字节+存放字符数据”的别名,是创建char变量的模子;
  • int是“4个字节+存放整形数据”别名,是创建int变量的模子。
  • unsigned int就表示“占4个字节,存放无符号整形数据”的别名。
  • ……

如果用typedef关键字重定义一个数据类型typedef int INT,就相当于新起了一个“占4个字节+存放整形数据”的别名INT

变量本质

  • 变量是一段实际连续存储空间的别名
  • 程序中通过变量来申请并命名存储空间
  • 通过变量的名字可以使用存储空间
  • 指针变量是在实际连续存储空间里存储其他变量的地址

示例

示例1

#include <stdio.h>int main(){    char c = 0;    short s = 0;    int i = 0;    printf("%d, %d\n", sizeof(char), sizeof(c));    printf("%d, %d\n", sizeof(short), sizeof(s));    printf("%d, %d\n", sizeof(int), sizeof(i));    return 0;}$gcc ctest.c $./a.out 1, 12, 24, 4$

示例2
证明数据类型和变量的本质
1. 自定义类型(typedef定义类型)
2. 创建变量(TYPE name
3. 证明本质(printf

#include<stdio.h>typedef INT32;  //默认是重定义int类型,相当于typedef int INT32typedef unsigned char BYTE;typedef struct _demo {    short s;    BYTE b1;    BYTE b2;    INT32 i;} DEMO;int main(){    INT32 i32;    BYTE byte;    DEMO d;    printf("%d, %d\n",sizeof(INT32),sizeof(i32));    printf("%d, %d\n",sizeof(BYTE),sizeof(byte));    printf("%d, %d\n",sizeof(DEMO),sizeof(d));    return 0;}$gcc ctest.c $./a.out 4, 41, 18, 8$

上面程序中struct定义了DEMO,包括四个域,通过这四个域我们可以分开使用8个字节的内容。
但一个类型的数据强制转换赋值时它的类型是不会改变的。例如:
示例3

#include<stdio.h>int main(){    int i = 9;    char p = (char)i;    printf("%d\n", sizeof(i));}$gcc ctest3.c $./a.out 4$

注意强制类型转换不能作为左值,如(char)i = 'a'的写法是不对的。

临时变量

C99标准以前的C标准是不支持临时变量在for循环中定义的。
像for(int i=0; i<10; i++)这样的表达式是编译不过的,C99标准就支持这样写。但是目前有些编译器并不怎么愿意支持C99标准,或者默认以C89模式进行编译执行。 如果你用的是Visual C++6.0的话,那没办法,那货早停止更新了,不可能支持C99了。 如果用的是gcc的编译器(v3.0以上)的话,默认按C89编译。手动编译时加上-std=c99就能按C99标准编译,上面那样写就没问题了。比如:
gcc -std=c99 tset.c -o test
如果用的是使用gcc编译器的CodeBlocks的话,
settings -> compiler and debugger…
打开编译调试设置面板,在Global compiler settings项里面的Other options里添加-std=c99就行了。

其他使用gcc的IDE应该都能设置这个的。
不过建议还是按C89的写法写吧,很多地方写成C99标准的都会出错,谁叫他们不买C99的帐呢。


属性-auto,register,static

概述

C语言中的变量可以有自己的属性,在定义变量的时候可以加上“ 属性” 关键字,“ 属性” 关键字指明变量的特有意义。

auto

auto即C语言中局部变量的默认属性,编译器默认所有的局部变量都是auto的,auto只能修饰局部变量,不能修饰全局变量。

  • C语言可以声明多个同名的全局变量。

static

  • static关键字指明变量的“ 静态” 属性
  • static关键同时具有“ 作用域限定符” 的意义
  • static修饰的局部变量存储在程序静态区
  • static的另一个意义是文件作用域标示符

  • static修饰的全局变量作用域只是声明的文件中

  • static修饰的函数作用域只是声明的文件中

register

  • register关键字指明将变量存储于寄存器中
  • register只是请求寄存器变量, 但不一定请求成功
  • register变量的值必须是CPU寄存器可以接受的值

  • 不能用&运算符获取register变量的地址,因为register变量的地址不在内存中。

  • register不能修饰全局变量

示例

示例1

#include<stdio.h>void f1(){    int i = 0;    i++;    printf("%d\n",i);}void f2(){    static    int i = 0;    i++;    printf("%d\n",i);}int main(){    int i;    for(i=0;i<5;i++) {        f1();    }    for(i=0;i<5;i++) {        f2();    }    return 0;}$gcc ctest.c $./a.out 1111112345$

f1()中的i是默认auto属性的,每次都重新初始化,而f2()中的i是static属性的,在静态数据区,只能被初始化一次。

示例2

#include<stdio.h>extern int test2_d;void main(){    printf("%d\n",test2_d);}
int test2_d =  1;
$gcc ctest.c ctest_ex.c $./a.out 1$

如果给test2.c的test2_d加上static属性,即static int test2_d = 1; 那么这个数据就只能被这个文件使用,test.c引用它会报错,不过我们可以这么改写:

示例3

#include<stdio.h>extern int test2_func();void main(){    printf("%d\n",test2_func());}
static int test2_d = 1;int test2_func(){return test2_d;}
$gcc ctest.c ctest_ex.c $./a.out 1$

如果给函数func也加上static属性的话那这个函数就不能被外部访问了。我们可以再改写:

示例4

#include<stdio.h>extern int test2_func2();void main(){    printf("%d\n",test2_func2());}
static int test2_d = 1;static int test2_func(){return test2_d;}int test2_func2(){return test2_func();}
$gcc ctest.c ctest_ex.c $./a.out 1$

这个就相当于java里面的get方法。


条件语句-if

语句形式

  • if 语句用于根据条件选择执行语句
  • else 不能独立存在且总是与它最近的 if 相匹配
  • else 语句后可以接连其他if语句

语句形式1:

if (condition) {    //statement1} 

语句形式2:

if (condition) {    //statement1} else {    //statement2}

语句形式3:

if (condition) {    //statement1}else if {    //statement2}...else if {    //statement(n-1)} else {    //statement(n)}

if语句比较的注意点:

bool真假比较

C语言的c99标准才有bool类型,c89的标准没有(要使用的话需要stdbool.h头文件),所以我们通常会这么做:

typedef int BOOL#define TRUE 1#define FALSE 0

这是最常见的写法,能被任何C语言编译器认可。如果要使用bool关键字,则需把编译方式改为c99标准,一般加上-std=c99参数就可以了。

C语言判断真假是零和非零,C语言只规定了0为假。所以一般不要和bool型变量进行比较,bool型变量应直接出现于条件中。
像这样:

BOOL b = TRUE;if (b) {    //statement1} else {    //statement2}

0值比较

float型变量不能直接进行0值比较值比较, 需要定义精度(因为float和double在计算机内存中的值是离散表示的,虽然现在很多情况下可以直接比较,但那是编译器进行了优化,并不适用于所有情况,特别是嵌入式的一些场合,编译器都是老式的。)代码形式如下(EPSILON表示一个小的阀值,表示希腊的第五个字母,写作ϵ或ε):

#define EPSILON 0.0000000.1float f = 0.0;if ((-EPSILON =< f) && (f <= EPSILON)) {    //statement1} else {    //statement2}
#define EPSILON 0.00000001float f = 6.0;if ((6 - EPSILON <= f) && (f <= 6 + EPSILON)) {    printf("OK\n");} else {    printf("ERROR\n");}

普通变量和0值比较时, 0 值应该出现在比较符号左边(并不是强制性的,只是这样容易防止把等于号写成赋值符号的错误,因为常数不能被赋值,这条经验也可用于普通变量与其他常数比较。)
个人并不很喜欢这种写法(思路历程如下):
形如这样的语句

if(i == 0) {    return 0;}

为了防止把==敲成=的手误,有些人这样写

if(0 == i) {    return 0;}

这样的话即使不小心把==写成了=编译器也会报错,不得不说这是一个很好的办法,但是有一个小问题(对我来说是大问题),这会造成 代码的可读性和美观性有一点折损,尤其在这样的语句很多的时候,其实我们没必要这么做的,现在的编译器对诸如if(i = 0)这样的语句有了警告,例如在gcc里我们在编译的时候只要加上 -Wall参数就可以了,这样即不影响美观可读又避免的粗心的错误,何乐而不为呢?

后来发现这种想法是不全面的,因为警告信息是
warning: suggest parentheses around assignment used as truth value
意思是作为真值赋值的时候建议加上括号,也就是建议你if((i = 0)),这么写,虽然这样能够找出一些把==写成=的情况,但是如果是

if((i = 0) && (j = 0)) {    return 0;}

这种情况编译器就不会有警告信息了,所以==判断时把常数写在左边还是很有道理的,但个人比较文青,还是喜欢美观一点,所以只好coding时注意不要把==写成=了。


分支语句-switch

  • switch语句对应单个条件多个分值的情形
  • 每个case语句分支要有break,否则会导致分支重叠
  • default语句有必要加上, 以处理特殊情况

代码形式如下:

switch (表达式) {    case 常量:        代码块1    case 常量:        代码块2        ...    default:        代码块n}
  • case语句中的值只能是整型,字符型或枚举型。

case语句排列顺序:
按字母或数字顺序排列各条语句
default语句只用于处理真正的默认情况

switch与if的区别用一个程序体现如下:

#include <stdio.h>void f1(int i){    //这里不能用switch实现    if (i < 6) {        printf("Failed!\n");    }    else if ((6 <= i) && (i <= 8)) {        printf("Good!\n");    } else {        printf("Perfect!\n");    }}void f2(char i){    switch (i) {        case 'c':            printf("Compile\n");            break;               case 'd':            printf("Debug\n");            break;              case 'o':            printf("Object\n");            break;                case 'r':            printf("Run\n");            break;                default:            printf("Unknown\n");            break;                }}int main(void){    f1(5);    f1(9);    f2('o');    f2('d');    f2('e');}$gcc ctest.c $./a.out Failed!Perfect!ObjectDebugUnknown$

switch与if对比:

  • if语句实用于需要“ 按片” 进行判断的情形中
  • switch语句实用于需要对各个离散值进行分别判断的情形中
  • if语句可以安全从功能上代替switch语句,但switch语句无法代替if语句
  • switch语句对于多分支判断的情形更加简洁

switch能否用continue关键字?
不能,continue只能用在循环语句中,否则编译不过。除非switch包含在循环语句中,但switch中能用break。


循环-do,while,for

循环语句的基本工作方式:
通过条件表达式判定是否执行循环体,条件表达式遵循if语句表达式的原则

三种循环对比

do, while, for的区别:
* do语句先执行后判断, 循环体至少执行一次
* while语句先判断后执行, 循环体可能不执行
* for语句先判断后执行, 相比while更简洁

三种循环语句使用对比(累加自然数)

#include <stdio.h>int f1(int n){    int ret = 0;    int i = 0;    for (i=1; i<=n; i++) {        ret += i;    }    return ret;}int f2(int n){    int ret = 0;       while ((n > 0) && (ret += n--));    return ret;}int f3(int n){    int ret = 0;    if (n > 0) {        do {            ret += n--;        } while(n);    }    return ret;}int main(){    printf("%d\n", f1(10));    printf("%d\n", f2(10));    printf("%d\n", f3(10));}$gcc ctest.c $./a.out 555555$

当传入的值大于等于0时,第20行while( (n > 0) && (ret += n--) );可以改写为while( n && (ret += n--) );但当传入的值为负数时这样写就会陷入死循环,所以要写成n>0。
当传入的值为负数时,如都传入-10,结果如下:

$gcc ctest.c $./a.out 000$

switch与continue

break表示终止循环的执行
continue表示终止本次循环体,进入下次循环执行

do和break的妙用

一般函数设计分析
1441198683274d7fa291d-78bf-4c44-b7ee-e9e97287c7b5.jpg-17.4kB

#include <stdio.h>#include <malloc.h>int func(int n){    int i = 0;    int ret = 0;    int* p = (int*)malloc(sizeof(int) * n);    do {        if (NULL == p) break;        if (n < 0) break;        for (i=0; i<n; i++) {            p[i] = i;            printf("%d\n", p[i]);        }        ret = 1;    } while(0);    free(p);    return ret;}int main(){    if (func(10)) {        printf("OK");    } else {        printf("ERROR");    }}$gcc ctest.c $./a.out 0123456789OK$

如果把第10-23行去掉do-while循环改写成如下情况:

        if (NULL == p) return 0;        if (n < 0) return 0;        for (i=0; i<n; i++) {            p[i] = i;            printf("%d\n", p[i]);        }        ret = 1;

这样会产生内存泄漏,因为p申请了空间,当n<0的时候p的空间并没有释放,这样每调用一次函数就会产生一次内存泄漏。
改进如下:

        if (NULL == p) return 0;        if (n < 0) {             free(p);            return 0;        }        for (i=0; i<n; i++) {            p[i] = i;            printf("%d\n", p[i]);        }        ret = 1;

这样虽然解决了内存泄漏的问题,但当函数func参数增加时,比如int func(int n, int j, int l),这样就要多写两次free(p)语句,如果参数更多,声明的资源更多,那free函数就会更多,这样函数的可读性就很差而且容易出错。而这些问题用do-while就能轻松解决了。


goto,void,extern,sizeof分析

遭人遗弃的goto

  高手潜规则: 禁用goto
  项目经验: 程序质量与goto的出现次数成反比
  最后的判决: 将goto打入冷宫

一个程序如果goto越多,证明程序越烂。

goto副作用分析

#include <stdio.h>void func(int n){    int* p = NULL    if (n < 0) {        goto STATUS;    }    p = malloc(sizeof(int) * n);STATUS:    p[0] = n;   }int main(){     f(1);    f(-1);    return 0;}

  这个程序但n>0时没有什么问题,但当n<0时就会跳过为指针p分配空间的步骤而直接将n赋值给空指针p,会造成程序的崩溃。p = malloc(sizeof(int) * n);这条语句在函数func中并没有条件判断,它是必须执行的,而goto就有可能跳过类似的语句造成无法预知的后果,破坏了结构化程序设计的规则。

void的意义

  • void修饰函数返回值和参数
  • 如果函数没有返回值, 那么应该将其声明为void型
  • 如果函数没有参数, 应该声明其参数为void(现在的编译器不需要了)

void修饰函数返回值和参数仅为了表示无

C语言中并不存在void型变量,因为C语言没有定义void究竟是多大内存的别名,而没有void的标尺,就无法在内存中裁剪出void对应的变量。

但是如果你这样实验一番:

#include<stdio.h>int main(){        printf("%d\n",sizeof(void));    return 0;}

当你用gcc编译时会发现结果是1!为什么结果会是void类型大小为1字节?这是因为制作编译器时处于某些原因给void定义的,而标准c并没有定义,如果换成更严格的g++编译器的话就会报错:

$g++ ctest.c ctest.c: In function 'int main()':ctest.c:4: warning: invalid application of 'sizeof' to a void type$

但是几乎所有编译器时不允许定义void型变量的。例如void c这样的写法是不允许的。但是可以定义void型指针。
这里如果g++编译的警告信息是中文的,可以使用alias g++='LANG=C g++'使得编译信息为英文。

void指针的意义

C语言规定只有相同类型的指针才可以相互赋值
void *指针作为左值用于“ 接收” 任意类型的指针
void *指针作为右值赋值给其它指针时需要强制类型转换
例如int *PI = (int *)malloc(sizeof(int));
因为malloc的返回值是void型,所以我们用malloc在堆上面声明一段内存的时候需要用(int *)将返回值强制转换为PI指针的对应类型int *

int *PI = (int *)malloc(sizeof(int));char *PC = (char *)malloc(sizeof(char));void *p = NULL;int *pni = NULL;char *pnc = NULL;p = PI;     //okpni = P;    //oops!p = PC;     //okpnc = p;    //oops!

void *指针的使用

实现memset函数:
61e15e84-38e8-46b1-9375-fd2afcde3f63.jpg-17.3kB

#include<stdio.h>void *my_memset(voi *p, char v, int size){    void *ret = p;    char *dest = (char*)p;    int i = 0;    for(i=0; i<size; i++) {        dest[i] = v;    }    return ret;}int main(){    int a[5] = {1, 2, 3, 4, 5};    int i = 0;    fo (i = 0; i<5; i++) {        printf("%d\n",a[i]);       }    my_memset(a, 0, sizeof(a));    for (i = 0; i<5; i++) {        printf("%d\n",a[i]);       }    return 0;}$gcc ctest.c $./a.out 1234500000$

因为my_memset函数使用void *参数,所以它能接受任意类型的地址值,例如变量,我们可以做如下改写:

#include<stdio.h>void* my_memset(void* p, char v, int size){    void* ret = p;    char* dest = (char*)p;    int i = 0;    for(i=0; i<size; i++)    {        dest[i] = v;    }    return ret;}int main(){    long l = 1234;    my_memset(&l, 0, sizeof(l));           printf("%d\n",l);      return 0;}$gcc ctest.c $./a.out 0$

由此可见空指针不仅可以接受数组首地址还能接收变量的地址。

extern中隐藏的意义

extern用于声明外部定义的变量和函数
extern用于“ 告诉” 编译器用C方式编译
C++编译器和一些变种C编译器默认会按“ 自己” 的方式编译函数和变量, 通过extern关键可以命令编译器“ 以标准C方式进行编译”

extern使用分析

基本的用法

#include <stdio.h>extern int g;extern int get_min(int a, int b);int main(){     printf("%d\n",g);    printf("%d\n",get_min(2, 7));    return 0;}
int g = 100;int get_min(int a, int b){    return (a < b) ? a : b;}
$gcc ctest.c ctest_ex.c $./a.out 1002$

按标准c编译用法

#include <stdio.h>extern "C"{    int add(int a, int b)    {        return a + b;    }}extern int g;extern int get_min(int a, int b);int main(){     printf("%d\n",g);    printf("%d\n",get_min(2, 7));    return 0;}

这个时候用gcc是编译不过的,用更高级的g++则可以按标准c的规则来编译:

$gcc ctest.c ctest_ex.c ctest.c:3: error: expected identifier or '(' before string constant$g++ ctest.c ctest_ex.c $./a.out 1002$

为sizeof正名

sizeof是编译器的内置指示符, 不是函数
sizeof用于“ 计算” 相应实体所占的内存大小
sizeof的值在编译期就已经确定
sizeof使用分析

#include <stdio.h>int main(){     int a;    printf("%d\n", sizeof(a));    printf("%d\n", sizeof a);    printf("%d\n", sizeof(int));    return 0;}$gcc ctest.c $./a.out 444$

由此可见sizeof绝不是一个函数。


const和volatile分析

const修饰变量

  • 在C语言中const修饰的变量是只读的, 其本质还是变量 (所以const并不是真的常量)
  • const修饰的变量会在内存占用空间(所以可以通过取地址符来获得地址,然后就可以修改值了)
  • 本质上const只对编译器有用, 在运行时无用(在运行时还是可以通过指针来改变变量的值)
#include<stdio.h>int main(){    const int cc = 2;    printf("%d\n",cc);    cc = 3;    printf("%d\n",cc);    return 0;}$gcc ctest.c ctest.c: In function 'main':ctest.c:6: error: assignment of read-only variable 'cc'$

但是

#include<stdio.h>int main(){    const int cc = 2;    int* p = (int*)&cc;    printf("%d\n",cc);    *p = 3;    printf("%d\n",cc);    return 0;}$gcc ctest.c $./a.out 23$

const修饰数组

在C语言中const修饰的数组是只读的,const修饰的数组空间不可被改变。

const int array[3] = {1, 2, 3};int *p = (int *)array;int i = 0;for (i=0; i<3; i++) {    p[i] = 3 - i;   //oops!}

const修饰指针

const int *p;   //p可变, p指向的内容不可变int const *p;   //p可变, p指向的内容不可变int * const p;  //p不可变, p指向的内容可变const int * const p;    //p和p指向的内容都不可变

口诀: 左数右指

当const出现在 * 号左边时指针指向的数据为常量
当const出现在 * 号右边时指针本身为常量

const修饰函数参数和返回值

const修饰函数参数表示在函数体内不希望改变参数的值
const修饰函数返回值表示返回值不可改变, 多用于返回指针的情形

const int *func(){    static int count = 0;    count++;    return &count;}

const int *p = func();这样是对的,但int *p = func();就是错的

深藏不漏的volatile

  • volatile可理解为“ 编译器警告指示字”
  • volatile用于告诉编译器必须每次去内存中取变量值
  • volatile主要修饰可能被多个线程访问的变量
  • volatile也可以修饰可能被未知因数更改的变量
int obj = 10;int a = 0;int b = 0;a = obj;sleep(1000);b = obj;

上述代码在编译期间编译器会自动将obj替换成10,因为obj没有被当作左值。但如果在1000毫秒内发生了硬件中断使得obj的值变成了100,但由于编译期间b = obj的obj被替换成了10,所以b的值还是10,程序就会出错。在这里编译器做了自以为是的优化。这里obj就可以使用volatile来修饰。

const和volatile是否可以同时修饰一个变量?
一个值可以同时是vonst和volatile。例如,硬件时钟一般设定为不能由程序改变,这一点使他成为const; 但它被程序以外的代理改变,这使它成为volatile的。只需在声明中同时使用这两个限定词,如下所示,顺序并不重要:

volatile const int ioc; const volatile int *ploc;

struct和union分析

空结构体

C语言并没有说明空结构体占多大内存。
各编译器的处理可能不一样,但没有对错之分,只有更合理之说。

#include<stdio.h>#include<malloc.h>struct _null{};int main(){    struct _null n1;    struct _null n2;    printf("%d\n",sizeof(struct _null));    printf("%d, %0x\n",sizeof(n1), &n1);    printf("%d, %0x\n",sizeof(n2), &n2);    return 0;}$gcc ctest.c $./a.out 00, bf8838500, bf883850$g++ ctest.c $./a.out 11, bff35a6f1, bff35a6e$

看起来g++更合理些,因为gcc中n1和n2的地址是一样的。
所以对空结构体来说占1个字节是最合理的(最接近于0,而计算机是按字节为单位存取数据的,所以取1最好)

柔性数组

柔性数组即数组大小待定的数组
C语言中结构体的最后一个元素可以是大小未知的数组,所以C语言中可以由结构体产生柔性数组。

#include <stdio.h>#include <malloc.h>typedef struct _soft_array {    int len;    int array[];} SoftArray;int main(){      int i = 0;    SoftArray *sa = (SoftArray *)malloc(sizeof(SoftArray) + sizeof(int)*10);    sa->len = 10;    for (i=0; i<sa->len; i++) {        sa->array[i] = i + 1;    }    for (i=0; i<sa->len; i++) {        printf("%d\n", sa->array[i]);       }    free(sa);    return 0;}$gcc ctest.c $./a.out 12345678910$

结构体初始化:
利用上面的结构体,我们声明一个结构体变量,SoftArray b = {8, 1,2,3,4};这样就完成了对结构体变量的初始化。
对于结构体数组也类似:SoftArray b[] = {{8, 1,2,3,4},{6, 4,3,2,1}};

打印菲薄拉起数列前100项

#include <stdio.h>#include <malloc.h>typedef struct _soft_array {    int len;    int array[];}SoftArray;SoftArray *create_soft_array(int size){    SoftArray *ret = NULL;    if( size > 0 ) {        ret = (SoftArray*)malloc(sizeof(*ret) + sizeof(*(ret->array)) * size);        ret->len = size;    }    return ret;}void fac(SoftArray *sa){    int i = 0;    if( NULL != sa ) {        if( 1 == sa->len ) {           sa->array[0] = 1;        } else {            sa->array[0] = 1;            sa->array[1] = 1;            for(i=2; i<sa->len; i++) {                sa->array[i] = sa->array[i-1] + sa->array[i-2];            }        }    } }void delete_soft_array(SoftArray *sa){    free(sa);}int main(){    int i = 0;    SoftArray *sa = create_soft_array(100);    fac(sa);    for(i=0; i<sa->len; i++) {        printf("%llu ", (unsigned long long)sa->array[i]);     }    delete_soft_array(sa);    return 0;}$gcc ctest.c $./a.out 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465 14930352 24157817 39088169 63245986 102334155 165580141 267914296 433494437 701408733 1134903170 1836311903 18446744072385799393 512559680 18446744072898359073 18446744073410918753 18446744072599726210 18446744072301093347 1776683621 368225352 2144908973 18446744071927718645 363076002 18446744072290794647 18446744072653870649 1820529360 764848393 18446744071999962073 18446744072764810466 1640636603 695895453 18446744071751116376 18446744072447011829 1073992269 18446744073521004098 885444751 696897233 1582341984 18446744071693823537 18446744073276165521 1845853122 1412467027 18446744072672904469 375819880 18446744073048724349 18446744073424544229 18446744072763716962 18446744072478709575 2118290601 887448560 18446744072420323481 18446744073307772041 18446744072018543906 18446744071616764331 511172301 18446744072127936632 18446744072639108933 1642909629 572466946 18446744071629960895 18446744072202427841 708252800 18446744072910680641 18446744073618933441 18446744072820062466 18446744072729444291 $

union和struct的区别

struct中的每个域在内存中都独立分配空间
union只分配最大域的空间, 所有域共享这个空间

cf93be54-9f42-44d8-85b2-a43486e3e016.jpg-15.3kB

union注意事项

union的使用受系统大小端的影响

bbb7baff-5479-467f-841c-bdfbfc1701c9.jpg-20.7kB

上述代码在大端模式下返回0,小端模式下返回1。
我们可以反过来利用union来判断系统大小端。

结构体的加深理解

C语言处理数据只关心两件事情:数据的大小和数据的操作方式。数据的大小可以通过结构体本身来限定,也可以通过指定大小的数组来限定,总之,只要占着地方,怎样都无所谓的。但是,结构体本身决定了如何“操作”对应的空间,也就是说,对同一个空间,绑定不同的结构体,就可以按照对应的原则来操作这段内存。


enum和typedef分析

枚举类型的使用方法

enum是一种自定义类型
enum默认常量在前一个值的基础上依次加1
enum类型的变量只能取定义时的离散值(不能取浮点数)

#include<stdio.h>enum color{    GREEN,    RED = 3,    BLUE};int main(){    enum color c = BLUE;    printf("%d\n", GREEN);    printf("%d\n", RED);    printf("%d\n", BLUE);    printf("%d\n", c);    return 0;}$gcc ctest.c $./a.out 0344$把GREEN赋初值为2,则结果为$./a.out 2344$

枚举类型和#define的区别

#define宏常量只是简单的进行值替换(文本替换),枚举常量是真正意义上的常量
#define宏常量无法被调试,枚举常量可以
#define宏常量无类型信息,枚举常量是一种特定类型的常量
所以定义常量时建议使用enum

typedef的意义

面试中……
考官: 你能说说 你能说说typedef具体的意义吗?
应聘者: typedef用于定义一种新的类型。。。

typedef用于给一个已经存在的数据类型重命名
typedef并没有产生新的类型
typedef重定义的类型不能进行unsigned和signed扩展
typedef叫typerename可能更合适。。。

证明:

#include<stdio.h>typedef int INT32;int main(){    int i = 0;    INT32 *p32 = &i;     char *pi = p32;    return 0;}$g++ ctest.c ctest.c: In function 'int main()':ctest.c:9: error: cannot convert 'INT32*' to 'char*' in initialization
#include<stdio.h>typedef int INT32;int main(){    int i = 0;    INT32 *p32 = &i;     int *pi = p32;    return 0;}$g++ ctest.c $

这说明typedef的作用就是重命名

typedef重命名时如果没有指定类型,如typedef abc;,默认是重命名int类型,相当于typedef int abc;,有些软件会有这样的提示:

type defaults to ‘int’ in declaration of ‘abc’

typedef和#define的区别

typedef是给已有类型取别名
#define为简单的字符串替换, 无别名的概念

typedef char* PCHAR;PCHAR p1, p2;#define PCHAR char*PCHAR p3, p4;p1,p2,p3,p4之间有区别吗?p1, p2, p3为指针,p4不是!

证明:

#include<stdio.h>#define PCHAR char*int main(){    PCHAR p1, p2;    char c;    p1 = &c;    p2 = &c;    return 0;}$g++ ctest.c ctest.c: In function 'int main()':ctest.c:9: error: invalid conversion from 'char*' to 'char'$
#include<stdio.h>typedef char* PCHAR;int main(){    PCHAR p1, p2;    char c;    p1 = &c;    p2 = &c;    return 0;}$g++ ctest.c $

版权声明:本文采用知识共享署名-非商业性使用-相同方式共享 4.0许可发布,本文内容整理于网络,如果有内容侵犯了你的权益,请及时联系我进行处理:49042765@qq.com或endice-终末冰雪

0 0
原创粉丝点击