C语言相关细节

1. 基本要素

  1. 命名: (1)只能以字母或下划线开头
              (2) 只能包含数字,字母以及下划线
    2. 命名空间: 所有的标识符处在4个命名空间的其中一个。
    3. 缩进: 最好用8个空格缩进,避免不同的编译器对<TAB>键的缩进程度

2. 基本数据类型

    1. 布尔常量是指:true和false,占用4个字节
       布尔变量是指:自定义变量,占用1个字节
    2. 事实上,true 和 false 是以下宏定义:这就是为什么占用4个字节的原因了。
            #define true 1
            #define fase 0
    3. 实际上bool变量占用1个字节,而true和false占用4个字节,它们实际上都是int4. 由基本数据类型通过typedef预定义好的固定长度的数据类型称为系统基本数据类型。
    5. C语言世界里,非0即真。

3. 运算符

   1. 两个整型相除的结果是整型,小数部分将被舍弃(而不是四舍五入)
    2. 取模运算符左右两边的操作数都必须是整型int 
    3. ++a 时:先进行自增/自减再参与运算
       a++ 时: 先参与运算再进行自增/自减
    4. 在逻辑与操作(&&)中:只要有一个表达式的值我为假(即为0)则整个表达式的值就为假
       在逻辑或操作(||)中:只要有一个表达式为真(即不为0)则整个逻辑表达式的值就为真。
    5. 在逻辑运算语句中特别注意:当左边的表达式能够决定整个表达式的值时,右边的表达式
       会被直接忽略掉,在其中的所有运算都不会被执行。(注意:在&&,如果第一个为假,则整个都是假)
    6. 位逻辑反运算符(~)是对数据进行取反操作,即:01, 10
    7. 位逻辑与运算、位逻辑或运算:与逻辑与、逻辑或运算相同(需结合非 0 即 真 去细想)
    8. 位逻辑异或运算符(^):相同为0, 不同为1
    9. 移位运算(<<,>>):操作数必须是整型,并且不管是有符号还是无符号整型都遵循着同一个原则:
        凡是被移除去的位全部丢弃,凡是空出来的位全部补上。
    10. 所有的关系表达式都是布尔值
    11. 理解C程序中变量的两个属性:地址和内容
        (1) C程序中每一个变量都有一个地址,如果这个变量占用一个字节,那么该字节的地址就是该变量的地址。
        (2) 如果该变量占用了n个字节,那么最低的那个地址就作为该变量的地址,而该变量的空间内存放的值即为该变量的内容
    12. 赋值语句注意点:
        (1) 不能对常量赋值
        (2) 不能对只读变量赋值
        (3) 不能对数组整体赋值
    13. sizeof 运算符用来计算某种类型所占的字节数。它的操作数可以是类型,也可以是变量。
        (1) 如果是类型:则一定要添加圆括号
        (2) 如果是变量:则圆括号可以省略
    14. 注意:sizeof 如果对数组求大小,则计算结果是数组元素类型大小乘以元素个数
    15. sizeof 也可以用来直接返回字符串所占空间的大小,比如sizeof("abc")返回的结果是4,包括后面的字符串结束标记'\0'
    16. 逗号','表达式的运算顺序是从左到右,逗号运算符具有最低运算优先级,整个逗号表达式的值取决于最右边的表达式的值。
    17. return 有两个作用
        (1) 当出现在主函数main()中:退出进程,将其值返回给父进程
        (2) 当出现在普通函数中: 作用是返回其调用者

4. 控制流

    1. switch(expression)语句中,expression可以是:
        (1) 变量
        (2) 运算表达式甚至是函数调用
        (3) 但其结果必须保证是整型

5. 函数

    1. 函数头包含3个重要信息:
        (1) 函数的名字
        (2) 函数的返回值类型
        (3) 函数的参数列表
    2. 函数使用中的3个主要部分:
        (1) 函数的声明:即函数头
        (2) 函数的调用:可传实参
        (3) 函数的定义:函数功能实现的代码所在
    3. 实参是调用者定义的变量,形参是被调用函数定义的变量,它们是相对独立的,拥有各自的内存
        形参是由实参来初始化的。
    4. 栈内存
        (1) 每一个函数在运行时,都会占据一段栈内存,大小视其函数定义的局部变量的具体情况而定。
        (2) 一个程序里面的所有函数所占据的栈内存在逻辑上是连在一起的。
            函数之间的相互调用会不停的分配栈内存,其上的栈内存就是其调用者的栈内存。
        (3) 栈帧是向下(低地址)堆叠的,所以上面是栈底,下面是栈顶。
        (4) 一个C程序的栈帧由若干段函数的栈帧组成的,每一段栈帧都被用来存放对应函数的局部变量(也有其它值)
        (5) 栈内存的另一个最重要的特性:临时性,一旦对应的函数退出,相应的栈帧将立即被释放(即被系统收回)5. 递归函数一定要有个结束递归的情况,否则将会进入无穷递归
    6. 递归函数的特点:
        (1) 短小精悍
        (2) 弱点:效率低且容易使得栈溢出。
    7. 变参函数给形参赋值时从右至左分配内存的。
    8. 内联函数的特性:用空间代价(程序尺寸增大)来换取时间效率(不再需要切换函数),这样的函数称为内联函数(inline)
    9. 内联函数一般是被放在头文件里面的。

6. 数组和指针

    1. 任意数据类型的数组都是合法的,包括指针数组,结构体数组,甚至数组的数组
    2. 初始化数组列表的元素个数比数组长度短是允许的。
    3. 如果数组是静态数组,则会自动初始化 0 ,否则随机值
    4. 内存地址是一片内存中每个字节(byte)的编号
    5. 一个数据最基本的属性:内容和地址,内容指的就是内存中所存放的二进制串。
       地址指的就是这块内存的编号,一个数据的地址也称为这个数据的指针。
    6. 每一个字节都有其对应的地址,编译器会将最小的地址作为类型数据的地址,就是所谓的基地址。提到一个变量的地址时
        指的都是基地址。
    7.32 位系统中,任何地址都是4个字节的。
    8. 0x0000 8000 --> 0x0804 8000 :任何程序都无法访问的内存,这段内存的权限为 0
    9. NULL 实际上就是一个宏,#define NULL (void *)0 ,NULL 就是地址:0x0000 0000 
    10. void 型指针
        (1) 当无法确定一个地址所对应的内存的数据类型时,将该地址类型定为void 型 即 通用型
        (2) void 型指针在解引用时,必须转化为某种具体的数据类型指针,否则无法解引用。
    11. 比如:P+2 :含义是指针指向高地址方向移动2个目标 
              p-2 :含义是指针指向低地址方向移动2个目标
    12. 相同类型的指针可以相减,结果是两个指针之间相隔目标个数,但不同类型的指针相减则毫无意义。
    13. 任何两个指针都不能相加
    14. 任何数组的名字a,除了其定义语句和sizeof语句之外,均代表其首元素的地址a[0]的地址,而并非是数组a的地址。
        数组a的地址是&a
    15. 函数形参列表中定义写为数组的形式只是方便推断实参可能是 int a[3] ,实际上形参只能是指针!不可能是数组。
    16. 再一次注意:数组形参只能是指针,不能是数组
    17. 零长数组一般被放置在一个结构体的末尾,作为扩展内存大小的占位符而存在的,这是数组唯一可以"越界"访问的场合。
    18. const 型的变量是只读的,不可赋值,只读的变量非常量,不可用于case 语句中!
    19. const 在C语言中的主要用法不是用来定义普通变量的,而是用来定义指针的
    20. int a,b; const int *p = &a; 等价于:int const *p = &a;
        上述中,指针p可以指向其他值,但不能通过其解引用修改其值,比如:*P = 100;是不行的。
    21. int *const p; 这里的const型指针p本身是只读的,不可被外部修改其值,但它解引用可以修改它所指的值。
    22. 总结:const int *p 或int const *p :p可以指向其它值,但不能通过解引用修改其它值
              int *const p :本身只读,不能随意指向其它值,但可以通过它修改其指向的值。
    23. C语言是没有字符串这个类型也没有这个概念的。
    24. 一个字符串的存储内幕实际上就是一个字符数组,而且必定有一个'\0'作为此字符串的结束标记
    25. 一个字符串的大小至少是一个字节(即只包含'\0'空串)
    26. 
int main()
{
    /**==============================
    关于字符串和指针以及'\0'之间的大小,比较,赋值等相关内容
    =================================*/
    char *p = "12345678";
    char str1[8];
    char str2[9];
    char str3[8]={'1','2','3','4','5','6','7','8'};
    char str4[9]={'1','2','3','4','5','6','7','8','\0'};
    int i;
    
    //用str3给str1 赋值
    for(i=0;i<8;i++)
    {
        str1[i]=str3[i];
        printf("%c\n",str1[i]);
    }
    //用str4 给str2 赋值
    for(i=0;i<9;i++)
    {
        str2[i]=str4[i];
        printf("%c\n",str2[i]);
    }
    printf("\tp字符串的大小:【%d】,p字符串的长度:【%d】\n",sizeof(p),strlen(p));
    printf("\tstr1字符串的大小:【%d】,str1字符串的长度:【%d】\n",sizeof(str1),strlen(str1));
    printf("\tstr2字符串的大小:【%d】,str2字符串的长度:【%d】\n",sizeof(str2),strlen(str2));
    printf("\tstr3字符串的大小:【%d】,str3字符串的长度:【%d】\n",sizeof(str3),strlen(str3));
    printf("\tstr4字符串的大小:【%d】,str4字符串的长度:【%d】\n",sizeof(str4),strlen(str4));
    
    printf("strcmp(p,str1)==【%d】\n",strcmp(p,str1));
    printf("strcmp(p,str2)==【%d】\n",strcmp(p,str2));
    printf("strcmp(p,str3)==【%d】\n",strcmp(p,str3));
    printf("strcmp(p,str4)==【%d】\n",strcmp(p,str4));
    return 0;
}

7. 内存管理

    1. 一个用户进程可以访问的内存区域介于 0x08048000 - 0xc000 0000 之间,这个区域又被分成了几部分,分别用来存放进程的代码
        和数据,以及进程在运行时生成的动态信息
    2. 栈:是一种后进先出的逻辑,它的全称是:运行时栈
    3. 一旦有新的函数被调用,就会立即在栈顶分配一帧内存,专门能用于存放该函数内定义的局部变量,包括所有的形参
    4. 栈主要是用来存储进程执行过程中所产生的局部变量的。
    5. 堆和栈最大的区别在于堆是不设大小限制的,最大值取决于系统的物理内存。
    6. 堆的全称是:运行时堆
    7. .bss数据段:专门用来存放未初始化的静态数据,它们都将被初始化为0
       .data数据段:专门存放已经初始化的静态数据,这个初始值从程序文件中复制而来。
       .rodata数据段:用来存放只读数据,即常量,比如字符串,字符常量,整型浮点型常量等。
    8. 栈中的环境变量和命令行参数在程序一开始运行时就被固定在了栈底(紧挨着内核的地方)
    9. 栈还有一个名称:堆栈,堆栈和堆没有一点关系
    10. 栈和堆都是动态变化的,分别向下和向上增长,大小随着进程的运行不断变大和变小
    11. 静态数据指的是所有的全局变量,以及static 型局部变量。
    12. 代码段:.text,init段
        .text段:用来存放用户程序代码
        .init段:用来存放存储系统给每一个可执行程序自动添加的"初始化"代码
    13. 初始化只能执行一遍
    14. 堆内存的生命周期是:从malloc()/calloc()/realloc()开始,free()结束

8. 组合数据类型

1. C语言对结构体类型没有限制(但结构体不能包含函数),最后用一个分号结束
2. 结构体变量时,类型除了要写明结构体模板的名字student之外,还必须携带struct关键字
3. 对于一个普通变量来说,我们对它的操作无非是:初始化,赋值,引用, 组成数组,定义指针,传递参数等
4. 每一款不同的处理器,存取内存数据都会有不同的策略,如果是32位的cpu,一般来讲它在存取内存数据时,每次至少存取4个字节(32位)
5. 结构体对齐方法:
    如果最大的变量尺寸小于4个字节:那么按照最大值来对齐
    如果变量的尺寸大于或等于4个字节:则一律按4字节对齐
6.  枚举类型纯粹就是整型,可以给它赋值任意正数
7. C语言中,使用常量有3种方式:
    (1) 直接使用
    (2) 宏定义
    (3) 枚举常量

9. 高级议题

    1. C语言在编译之前,会处理源程序文件中的所有预处理指令
    2. 什么东西应该放在头文件?
        (1) 普通函数声明
        (2) 宏定义
        (3) 结构体,共用体模板定义
        (4) 枚举常量列表
        (5) static 函数和inline函数定义
        (6) 其他头文件
    3. 普通函数的定义不能放在头文件里,因为普通函数默认是所有文件可见
    4. static 型的函数是可以放在头文件里面的,因为这些函数被任何一个.c源文件包含了也不会与别的文件冲突。
        实际上static 型函数一般也被放在头文件里
    5. inline 函数默认就是static 型函数,因此一般也被放在头文件
    6. 宏分两种:一种带参宏,一种不带参宏
    7. 根据C语言的语法,复合语句不能出现在表达式中.
    8. typedef 是给一个类型取别名的。
    9. 宏定义:(注意: __ 是两个不是一个,不是 _FUNCTION_  而是 __FUNCTION__
		__FUNCTION__ :函数名
__TIME__ :文件运行的时间
__LINE__ :所在行数
__FILE__:文件的名字
    

10 . 未归类杂项



1.数组没有初始化的时候,如果该数组是全局变量,则自动赋值为0,如果是局部变量,则为随机值。
2.所有的内存地址都是4个字节,内存地址多数是使用十六进制来描述
3.所有的指针都是4个字节的,因为指针是指向内存的,内存的地址大小都是4个字节的。
4.指针是一个地址,指针变量是一个变量。
    *p=0xbfe616b8;   p--->指针变量  0xbfe616b8  --->指针
5.int *A  *不是乘号,是指针的标志,内存立即分配4个字节的内存出来,用于存放地址,使用变量A间接访问这个地址。
6.int 决定这个指针指向的内容的数据类型是什么。
    char a = 'x';  //在内存中申请1个字节的大小。用于存放char 型数据,使用变量a来间接访问,把字符x赋值给a.

    char *c = &a; //在内存中申请4个字节的大小,用于存放一个地址,这个地址指向的内容是char型的数据,把变量a的地址赋值给指针变量c.

7.指针的种类:指针指向的内容不一致,而不是指针变量本身不一致。

8.程序在运行时一定是在内存

9.野指针就是声明一个指针变量后,没有对其进行复制,这个指针指向一个随机的位置。
10.空指针也是一个指针,这个指针可以指向任何类型的变量

11.防止指针是野指针的方法
    1)定义完后马上赋值 int *p = &a;
    2)把指针指向空指针。int *p = NULL; 或者把指针指向:[0x00000000--0x08048000)

12.其实NULL是一个宏定义
13. 空指针NULL
140x00000000 ----0x08048000 :所有程序都不能访问这段内存。

15.int *p = NULL;   --->指针p是安全的。

注意:
如果指针变量p指向的位置是我们的安全区域: 0x00000000 --0x08048000, 不能通过p去访问p指向的内容。
 0x00000000 - 0x08048000权限访问为0,所有程序如果不要访问该段内存,都会因为权限拒绝而访问失败。

16.void * 通用指针类型,但是没有void型变量。
17.void * p; --->可以强制转换到其他类型的指针。
    void a;  --->错误。
18.void *p 不能直接解引用,而是要强制转换为某种类型之后才解引用。
19.数组指针:不是一个数组,是一个指针,指向数组的指针。
20:数组指针是一个指针,不是一个数组,指针数组是一个数组,不是一个指针。

    int (*p)[30]  数组指针                     int *p[30]    指针数组

21:数组指针指向的是数组的基地址。

22.数组名就是该数组中首元素的地址。
    B = &B[0]; B 等价于 *p 
        B[0] 等价于(*p)[0];

23.函数指针当中:函数名就是函数的地址。p= &fun;

24.指针的加减法:都是以一个单位的操作,就是加1的话就是挪一个单位,即4个字节。

int A[3];  sizeof(A);
25.任何数组变量名A,除了定义语句sizeof()语句之外,都代表首元素的地址。

26.数组作为函数的参数时,不是把整个数组都传递过去,而是数组的首元素的地址传递。

27. void fun(int *p)  ---->接收的是首元素的地址,所以说要定义一个指向的内容跟首元素的类型一致的指针。
    void fun(int b[]) --->接收的是一个指针,不是一个数组
    void fun(int b[3]) --->

28. 数组作为函数参数的传递形式:
============================================================
#include <stdio.h>

//void show_value(int a[3])
void show_value(int *p)//形参的类型:指向首元素类型的指针变量
{
    int i;
    for(i=0;i<3;i++)
    {
        //printf("a[%d]:%d\n",i,a[i]);
        printf("p[%d]:%d\n",i,p[i]);
    }
}

int main()
{
    int b;
    int a[3] = {100,200,300};   
    show_value(a);//实参的类型:指向首元素的指针
    return 0;
}
=======================================================

29, 二维数组 : int B[3][2]中;
    数组名:B
    首元素:B[0]  --->B = &B[0];
    整个数组的基地址:&B

 B = &B[0] &B 三个值是一样的,但类型不一样。

二维数组中第一个元素的首元素的地址:&B[0][0] 类型:int *p
int *p1 = NULL;
int (*p2)[2] =NULL;
int (*p3)[3][2] = NULL;

     p1 = &B[0][0];
     p2 = &B[0];
     p3 = &B;           //指向整个二维数组

     

30. 字符指针--->一个指针,指针指向一个char 类型数据
 char (*p) = &a; 取a的地址,再把a的地址存放在指针变量p中。p指向这个字符。


 31.char *p  = "helloworld",p是指向字符?还是指向字符串?(C语言没有字符串类型);

 32.GCC编译器在处理字符串时,会把这个字符串当做字符数组来处理。

33. char *p="helloworld"; 
    p不是指向整个字符串,而是指向整个字符串中的首元素的地址,也就是字符'h'的地址。

34. char A[20] = "helloworld"; 
    char *p = "helloword";
    数组A是一个变量,所以对其赋值时,从常量区直接拷贝过去。
    p是一个指针变量,可以存储指向char型数据的地址,但是它指向的数据段中的字符串常量,是不能通过指针变量p来修改字符串。

35.数据段rodata是放置常量的一个内存区域。比如:int a= 100;这个100就是放入里面的。而a是可以随意放入任何数据的。

36:指针指向一个常量是不能直接修改其数据的,如果是指向一个变量就能间接修改其常量值。
37.打印字符串是用%s


38. ===============================打印字符串和修改字符串里面某个数值的代码==================

    char a[] = "helloworld";
    char *p1 = a;   //*p1 = &a;

    char *p = "helloworld";
    printf("a = %s\n",a);
    printf("p = %s\n",p);
    printf("p1 = %s\n",p1);

    a[0] = 'H';
    printf("a = %s\n",a);
    printf("p = %s\n",p);
    printf("p1 = %s\n",p1);
    return 0;

    ==============================================================================
    39. char *p = "helloworld";
        p[0] = 'H';此处是将H赋值给p指向字符串的首个地址的,但是编译通过,执行的时候会错误。
        因为 编译时候是检查语法错误,此处无语法错误,但是编译后执行的时候才发现p原来指向的地方是个常量,因此,不能用p修改helloworld;
        以上的程序编译通过,但是执行出现段错误,因为p指向字符串是一个常量,不能通过p来修改一个常量的值。
40.通过指针修改一个变量的值。
 比如:int a= 5;
      int *p = &a;
        *p = 10; 此处是将a的值修改为了10.

41.char *p = "helloworld";(字符串中)
     *(p+0) = h = p[0];  两者是相同的,解引用。
     *(p+1) = e = p[1];

42.解引用和取地址是一个反过程的。

43.const 可以修饰变量,(少) ,也可以修饰指针(多)

44. const char *p 等价于 char const *p :这里的const都是修饰p指向的内容。
    int b;
    char a = 'x';
    const char *p;
    p = &b
    p=&a;
    *p='y';--->除了这行有问题,以上其他几行都没问题,解释:45

45. const char *p;  const 修饰的不是指针变量p,而是指针变量p指向的内容。所以说变量p不是只读,可以赋值任何的地址。但是不能通过指针变量p来修改指向的内容。

46.char * const p;
const修饰的不是p指向的内容,修饰的指针变量p的本身,一旦p被赋值了某个地址


47.int * const p;  --->这里是个野指针,野指针也有地址,如果一开始没给其赋初始值,那么因为const的存在,p也是不可改变的。
const修饰的是p的本身,所以p一旦被初始化,就不能赋值别的地址。

48.实参能改变形参的值,但是形参不能改变是实参的值,因为每当函数退出时,形参的栈空间会释放掉,也不能修改实参的值。

49.字符串长度 --->strlen()计算字符串长度时是不包括\0的,得到字符实际个数。
50. 字符串中,'\0'是不存在的,打印不出来的,但是却占有一个空间。用sizeof()可以知道它的确占有一个空间。

51.%s--->打印字符串内容,遇到\0就会停止打印。








1.  取余符号两边必须是整型
2.  命名规则:字母数字下划线,不能以数字开头
3.long,int ,short类型数据占用内存大小是由C语言编译系统决定的。
4. i++是先用,在自增。
5.在c语言中非0即真!!!
6.函数的形参和实参分别占用不同的存储单元,形参在没调用它的时候是不占用内存的,只有调用后才分配了一个内存单元。
7.函数的形参是存放在栈内存中的。
8.可以给指针变量强制赋值一个浮点数作为地址值。


重点:const修饰符
1  const struct sockaddr *address;  :address 所指向的内容不可被其改变
2  struct sockaddr *const address;
发布了9 篇原创文章 · 获赞 10 · 访问量 278

猜你喜欢

转载自blog.csdn.net/my___sunshine/article/details/104004026