C基础

什么是数据类型:

1.可以理解为固定内存大小的别名

2.数据类型是创建变量的模子

例如:int a可以理解为以int这个模子在内存中创建出一个变量a

变量的本质:

实质:变量实际是一段连续的存储空间的别名

定义:通过变量来申请并命名存储空间

使用:通过变量的名字可以使用存储空间

总结:变量所占用的内存大小取决于所属的数据类型

 1 #include <stdio.h>
 2 
 3 int main()
 4 {
 5       char c = 0;
 6     short s = 0;
 7     int i = 0;
 8     printf("%d,%d\n",sizeof(char),sizeof(c));
 9     printf("%d,%d\n",sizeof(short),sizeof(s));
10     printf("%d,%d\n",sizeof(int),sizeof(i));
11    
12    return 0;
13 }
变量是内存空间的别名

运行结果:

1,1
2,2
4,4
 1 #include <stdio.h>
 2 
 3 typedef int INT32;
 4 typedef unsigned char BYTE;
 5 typedef struct tag
 6 {
 7     BYTE b1;
 8     BYTE b2;
 9     short s;
10     INT32 i;
11 }TS;//注意结构体的大小
12 
13 int main()
14 {
15     INT32 i32;
16     BYTE b;
17     TS ts;
18     
19     printf("%d,%d\n",sizeof(INT32),sizeof(i32));
20     printf("%d,%d\n",sizeof(BYTE),sizeof(b));
21     printf("%d,%d\n",sizeof(TS),sizeof(ts));
22    return 0;
23 }
注意结构体的大小

运行结果:

4,4
1,1
8,8
有符号与无符号:
数据类型的最高位用于标识数据的符号
-最高位为1,表明这个数为负数
-最高位为0,表明这个数为正数
1 int sign = 0;
2 
3 char i = -5;
4 short j = 5;
5 int k = -1;
6 
7 sign = (i & 0x80);//sign 不等于 0
8 sign = (j & 0x8000);//sign 等于 0
9 sign = (k & 0x80000000);//sign 不等于 0
有符号与无符号的实类

 有符号数的表示方法

在计算机内部用补码表示有符号数:

-正数的补码为这个数本身

-负数的补码为负数的绝对值各位取反后加1(符号位固定为1)

8位整数5的补码为:0000 0101

8为整数-7的补码为: 1111 1001

无符号数的表示方法:

在计算机内部使用原码表示无符号数

注意:C中只有整数类型能够声名unsigned变量

有符号数与无符号数相加,会把有符号数看为无符号数

 1 #include <stdio.h>
 2 
 3 int main()
 4 {
 5     unsigned int i = 5;
 6     int j = -10;
 7     
 8     if( (i + j) > 0 )
 9     {
10         printf("i + j > 0\n");
11     }
12     else
13     {
14         printf("i + j <= 0\n");
15     }
16 }
有符号与无符号相加

运行结果:

i + j > 0
内存中的浮点数
类型 符号位 指数 尾数
float 1位(第31位) 8位(第23~30位) 23位(第0~22位)
double 1位(第63位) 11位(第52~62位) 52位(第0~51位)

浮点数的转换:
1)将浮点数转换为二进制
2)用科学计数法表示二进制浮点数
3)计算指数偏移后的值
注:计算指数是需要加上偏移量,而偏移量的值与类型有关
示例:对于指数为6,偏移后的值为:
float:127 + 6 = 133
double:1023 + 6 = 1029
8.25在内存中的float表示
8.25的二进制表示1000.01 =》1.00001*(2^3)
符号位:0
指数:127 + 3 = 130 =》10000010
小数:00001
内存中8.25的float表示:
010000010 00001000000000000000000 =》0x41040000
 1 #include <stdio.h>
 2 
 3 int main()
 4 {
 5     float f = 8.25;
 6     
 7     unsigned int* p = (unsigned int*)&f;
 8     
 9     printf("0x%08X\n", *p);
10     
11     
12     return 0;
13 }
8.25的代码验证

int和float都占4个字节的内存,为什么float却比int的范围大呢?

1.float表示的数字之间不是连续的,存在间隙

2.float只是一种近似的表示法,不能作为精确的数使用

3.浮点类型的运算速度较慢

类型之间的转换

 1 int main()               
 2 {
 3     long l = 800;
 4     int i   = (int)l;//强制类型转换
 5     return 0;
 6 
 7 }
 8 
 9 int main()
10 {
11     short s = 800;
12     int i     = s;   //隐式类型转换
13 
14     return 0;
15    
16 }
强和隐类型转换

强制类型转换的结果:

1.目标类型能够容纳目标值:结果不变

2.目标类型不能容纳目标值:结果将产生截断

注:不是所有的强制类型转换都能成功,当不能进行强制类型转换时,编译器将产生错误信息

 1 #include <stdio.h>
 2 
 3 struct TS
 4 {
 5     int i;
 6     int j;
 7 };
 8 
 9 struct TS ts;
10 
11 int main()
12 {
13     short s = 0x1122;
14     
15     char c = (char)s;    // 0x22
16     
17     int i = (int)s;      // 0x00001122
18     
19     int j = (int)3.1415; // 3
20     
21    // unsigned int p = (unsigned int)&ts;
22     
23     //long l = (long)ts;   // error
24     
25     //ts = (struct TS)l;   // error
26     
27     printf("s = %x\n", s);
28     printf("c = %x\n", c);
29     printf("i = %x\n", i);
30     printf("j = %x\n", j);
31     //printf("p = %x\n", p);
32     printf("&ts = %p\n", &ts);
33     
34     return 0;
35 }
验证强制类型转换

隐式类型转换:

1.低类型向高类型的转换是安全的

2.高类型向低类型的转换是不安全的

 1 #include <stdio.h>
 2 
 3 int main()
 4 {
 5     char c = 'a';
 6     
 7     int i = c;    // safe
 8     
 9     unsigned int j = 0x11223344;
10     
11     short s = j;  // unsafe
12     
13     printf("c = %c\n", c);
14     printf("i = %d\n", i);
15     printf("j = %x\n", j);
16     printf("s = %x\n", s);
17     printf("sizeof(c + s) = %d\n", sizeof(c + s));
18     
19     return 0;
20 }
验证隐式类型转换

 变量的属性

auto关键字:

1.auto是C语言中局部变量的默认属性

2.auto表明将被修饰的变量存储于栈上

3.编译器默认的所有的局部变量都是auto类型的

register:

1.register关键字指明将局部变量存储于寄存器中

2.register只是请求寄存器变量,但是不一定请求成功

3.register变量必须是CPU寄存器可以接受的类型

4.不能使用&运算符获取register变量的地址

static关键字:

1.static修饰的局部变量存储于程序的静态区

2.static修饰的全局变量,只能在声明的文件中使用

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

 1 #include <stdio.h>
 2 
 3 int f1()
 4 {
 5     int r = 0;
 6     
 7     r++;
 8     
 9     return r;
10 }
11 
12 int f2()
13 {
14     static int r = 0;
15     
16     r++;
17     
18     return r;
19 }
20 
21 
22 int main()
23 {
24     auto int i = 0;       // 显示声明 auto 属性,i 为栈变量
25     static int k = 0;     // 局部变量 k 的存储区位于静态区,作用域位于 main 中
26     register int j = 0;   // 向编译器申请将 j 存储于寄存器中
27 
28     printf("%p\n", &i);
29     printf("%p\n", &k);
30    // printf("%p\n", &j);   // error
31     
32     for(i=0; i<5; i++)
33     {
34         printf("%d\n", f1());
35     }
36     
37     for(i=0; i<5; i++)
38     {
39         printf("%d\n", f2());
40     }
41     
42     return 0;
43 }
auto static register关键字

extern关键字:

1.extern变量表示此变量在其他文件中

2.extern函数表示此函数在其他文件中

3.extern用于“告诉”编译器用C语言的方式编译:方式为

extern "C"

{

  int f(int a,int b)

  {

    return a + b;

  }

}

注:在文件编译的过程中包含extern修饰的变量所在的文件一定要包含

 1 #include <stdio.h>
 2 
 3 extern int getl();
 4 
 5 int main()
 6 {
 7   printf("%d\n",getl());
 8 
 9   return 0;          
10 }
a.c
1 static int g_i;
2 
3 int getl()
4 {
5     return g_i;
6 }
b.c

 void的意义

void修饰函数的参数和返回值

如果函数没有返回值,那么应该将其声明为void

如果函数没有参数,应该声明其参数为void

注:1.函数没有参数但是不声明为void,那么调用的时候就可以传入任意的参数

2.函数没有返回值但是不声明为void,那么使用默认的(int)作为函数的返回值类型

不存在void变量:由于C语言中没有定义void究竟是多大的内存别名,通俗说就是没有void的标尺,无法在内存中裁剪出void对应的变量

void指针的意义:

由于C语言中只有相同类型的指针才可以相互赋值

void*指针作为左值用于“接收”任意类型的指针

void*指针作为右值使用时需要进行强制类型转换

 1 #include <stdio.h>
 2 
 3 void MemSet(void* src,int length,unsigned char n)
 4 {
 5    unsigned char*p = (unsigned char*)src;
 6    
 7    int i = 0;
 8 
 9    for(i = 0;i < length;i++)
10    {
11        p[i] = n;
12    } 
13 }
14 int main()
15 {
16    int a[5];
17    int i = 0;
18    
19    for(i = 0; i < 5;i++)
20    {
21         printf("%d\n",a[i]);
22    }
23    return 0;
24 }
void*实现memset函数

上述代码中缺少 MemSet(a,sizeof(a),0);

const关键字

1.const修饰的变量是只读的,本质还是变量

2.const修饰的局部变量在栈上分配空间

3.const修饰的全局变量在全局数据区分配空间

4.const只在编译期有用,在运行期无用

注:const修饰的变量不是真的常量,它只是告诉编译器该变量不能出现在赋值符号的左边

在现在C语言编译器中,修改const修饰的全局变量将导致程序崩溃,const修饰的局部变量可以使用指针操作来修改

 1 #include <stdio.h>
 2 
 3 const int g_cc = 2;
 4 
 5 int main()
 6 {
 7     const int cc = 1;
 8     
 9     int* p = (int*)&cc;
10     
11     printf("cc = %d\n", cc);
12     
13     *p = 3;
14     
15     printf("cc = %d\n", cc);
16     
17     p = (int*)&g_cc;
18     
19     printf("g_cc = %d\n", g_cc);
20     
21     //*p = 4;error 因为const修饰的全局变量存放于只读存储区,不可以被修改
22     
23     printf("g_cc = %d\n", g_cc);
24     
25     return 0;
26 }
const对全局变量和局部变量的验证

const将具有全局生命周期的变量存储在只读存储区

 1 #include <stdio.h>
 2 
 3 const int g_array[5] = {0};
 4 
 5 void modify(int* p, int v)
 6 {
 7     *p = v;
 8 }
 9 
10 int main()
11 {
12     int const i = 0;
13     const static int j = 0;//具有全局的生命周期
14     int const array[5] = {0};
15     
16     modify((int*)&i, 1);           // ok
17     modify((int*)&j, 2);           // error
18     modify((int*)&array[0], 3);    // ok
19     modify((int*)&g_array[0], 4);  // error
20     
21     printf("i = %d\n", i);
22     printf("j = %d\n", j);
23     printf("array[0] = %d\n", array[0]);
24     printf("g_array[0] = %d\n", g_array[0]);
25     
26     return 0;
27 }
View Code

const修饰函数的参数,表示在函数体的内部不希望改变参数的值

const修饰函数的返回值表示返回值不可改变,多用于返回值为指针的情形

C语言中的字符串变量存储于只读存储区中,在程序中需要使用const char*指针

 1 #include <stdio.h>
 2 
 3 const char* f(const int i)
 4 {
 5    // i = 5;const修饰的在函数体内部不可以被改变
 6     
 7     return "Delphi Tang";
 8 }
 9 
10 int main()
11 {
12    const char* pc = f(0);
13     
14     printf("%s\n", pc);
15     
16    // pc[6] = '_';const修饰的函数返回值不可以被改变
17     
18     printf("%s\n", pc);
19     
20     return 0;
21 }
View Code

volatile关键字

1.告诉编译器必须每次都去内存中去取volatile修饰的变量

2.volatile也可以修饰可能被未知因数更改的变量

struct结构体

1.产生柔性数组:即数组大小待定的数组

类如:struct SoftArray

{

  int len;

  int array[];

}

注:SoftArray中的array仅是一个待使用的标识符,不占用存储空间。

 1 #include <stdio.h>
 2 
 3 struct SoftArray
 4 {
 5     int len;
 6     int array[];
 7 };
 8 
 9 int main()
10 {
11     struct SoftArray t1;
12     struct SoftArray t2;
13     
14     printf("sizeof(struct SoftArray) = %d\n", sizeof(struct SoftArray));
15     printf("sizeof(t1) = %d, &t1 = %p\n", sizeof(t1), &t1);
16     printf("sizeof(t2) = %d, &t2 = %p\n", sizeof(t2), &t2);
17     
18     return 0;
19 }
验证array不占用存储空间

结果为:

sizeof(struct SoftArray) = 4
sizeof(t1) = 4, &t1 = 0x7ffefb23106c
sizeof(t2) = 4, &t2 = 0x7ffefb231068
 1 #include <stdio.h>
 2 #include <malloc.h>
 3 
 4 struct SoftArray
 5 {
 6     int len;
 7     int array[];
 8 };
 9 
10 struct SoftArray* create_soft_array(int size)
11 {
12     struct SoftArray* ret = NULL;
13     
14     if(size>0)
15     {
16         ret = (struct SoftArray*)malloc(sizeof(struct SoftArray) + sizeof(int)*size);
17         ret->len = size;
18     }
19     return ret;
20 }
21 void delete_soft_array(struct SoftArray* sa)
22 {
23     free(sa);
24 }
25 void func(struct SoftArray* sa)
26 {
27     int i = 0;
28     if(NULL != sa)
29     {
30         for(i = 0;i < sa->len;i++)
31         {
32             sa->array[i] = i + 1;
33         }
34         
35     }
36 }
37 int main()
38 {
39     int i = 0;
40     struct SoftArray* sa = create_soft_array(10);
41     func(sa);
42     for(i = 0;i < sa->len;i++)
43     {
44         printf("%d\n",sa->array[i]);
45     }
46     delete_soft_array(sa);    
47     return 0;
48 }
柔性数组的使用

结果为:

1
2
3
4
5
6
7
8
9
10
union共用体
1.union只分配最大成员的空间,所有成员共享这个空间
 1 #include <stdio.h>
 2 struct A
 3 {
 4     int a;
 5     int b;
 6     int c;
 7 };
 8 union B
 9 {
10     int a;
11     int b;
12     int c;
13 };
14 int main()
15 {
16     printf("%d\n",sizeof(struct A));
17     printf("%d\n",sizeof(union B));
18 }
union的验证

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

 1 #include <stdio.h>
 2 
 3 int system_mode()
 4 {
 5     union SM
 6     {
 7         int i;
 8         char c;
 9     };
10 
11     union SM sm;
12     
13     sm.i = 0x12345678;
14     
15     return sm.c;
16 }
17 
18 
19 int main()
20 {
21     printf("System Mode: %0x\n", system_mode());
22     return 0;
23 }
验证系统是大端还是小端

注:打印的是低地址上的数据

enum(枚举类型)

1.是一种自定的数据类型

2.其中的变量可以自定义数值

3.默认第一个变量的值为0

4.默认情况下其中变量的值在前一个值得基础上加1

enum中定义的值是C语言中真正意义上的常量
enum 
{

      ARRAY_SIZE = 10;
};

int array[ARRAY_SIZE] = {0};
int i = 0;

for(i = 0;i<ARRAY_SIZE;i++)
{
   array[i] = i + 1;
}
enum用于定义整型常量
 1 #include <stdio.h>
 2 
 3 enum
 4 {
 5     ARRAY_SIZE = 10
 6 };
 7 
 8 enum Color
 9 {
10     RED    = 0x00FF0000,
11     GREEN  = 0x0000FF00,
12     BLUE   = 0x000000FF
13 };
14 
15 void PrintColor(enum Color c)
16 {
17     switch( c )
18     {
19         case RED:
20             printf("Color: RED (0x%08X)\n", c);
21             break;
22         case GREEN:
23             printf("Color: GREEN (0x%08X)\n", c);
24             break;
25         case BLUE:
26             printf("Color: BLUE(0x%08X)\n", c);
27             break;
28     }
29 }
30 
31 void InitArray(int array[])
32 {
33     int i = 0;
34     
35     for(i=0; i<ARRAY_SIZE; i++)
36     {
37         array[i] = i + 1;
38     }
39 }
40 
41 void PrintArray(int array[])
42 {
43     int i = 0;
44     
45     for(i=0; i<ARRAY_SIZE; i++)
46     {
47         printf("%d\n", array[i]);
48     }
49 }
50 
51 
52 int main()
53 {
54     enum Color c = GREEN;
55     
56     int array[ARRAY_SIZE] = {0};
57     
58     PrintColor(c);
59     
60     InitArray(array);
61     
62     PrintArray(array);
63     
64     return 0;
65 }
enum的使用

sizeof关键字

1.sizeof是编译器的内置指示符而不是函数

2.用于计算类型或者变量所占内存的大小

3.sizeof的值在编译期间就已经确定了

 1 #include <stdio.h>
 2 
 3 int f(void)
 4 {
 5     printf("DT\n");
 6     return 0;
 7 }
 8 int main(void)
 9 {
10     int var = 0;
11     int size = sizeof(var++);
12     
13     printf("var = %d,size = %d\n",var,size);
14     
15     size = sizeof(f());
16     
17     printf("size = %d\n",size);
18     return 0;
19 }
sizeof的验证

typedef关键字

1.用于给已经存在的数据类型重命名

2.本质上不能产生新的类型

3.typedef重命名的类型:1)在ypedef语句之后定义2)不能被unsigned和signed修饰

 1 #include <stdio.h>
 2 
 3 typedef int Int32;
 4 
 5 struct _tag_point
 6 {
 7     int x;
 8     int y;
 9 };
10 
11 typedef struct _tag_point Point;
12 
13 typedef struct
14 {
15     int length;
16     int array[];
17 } SoftArray; 
18 
19 typedef struct _tag_list_node ListNode;
20 struct _tag_list_node
21 {
22     ListNode* next;
23 };
24 
25 int main()
26 {
27     Int32 i = -100;        // int 
28     //unsigned Int32 ii = 0;
29     Point p;               // struct _tag_point
30     SoftArray* sa = NULL;   
31     ListNode* node = NULL; // struct _tag_list_node*
32     
33     return 0;
34 }
typedef

 C语言中的注释

1.注释应该准确易懂,防止二义性

2.注释是对代码的提示,应该避免臃肿

3.简单的代码要避免加注释

4.注释要完整不可以使用缩写

5.注释用来阐明原因和意图而不是描述程序的运行过程

单引号与双引号

C语言中单引号用来表示字符字面量

C语言中双引号用来表示字符串字面量

'a'表示字符字面量(编译为对应的ASCII码)

在内存中占1个字节(a本身)

'a'+1表示“a”的ASCII码加1,结果为'b'

"a"表示字符串字面量(编译为对应的内存地址)

在内存中占2个字节(a本身和字符串结束符"\0")

"a"+1表示指针运算,结果指向字符串的结束符"\0"

低地址空间不可访问

#include <stdio.h>

int main()
{

    char* p1 =  1 ;
    char* p2 = '1';
    char* p3 = "1";

    printf("%s, %s, %s", p1, p2, p3);//error 操作低地址空间
    
    printf('\n');//error 操作低地址空间
    printf("\n");
    
    return 0;
}
View Code

 位运算符分析

1.左操作数必须为整数类型

char和short被隐式转换为int后进行移位操作

2.左移操作符<<,高位丢弃,低位补0

3.右移操作数>>,高位补符号为,低位丢弃

注:由于负数在计算机中是使用补码表示的,所以负数的右移是使用补码计算的,最后的结果是在转换为原码显示

 1 #include <stdio.h>
 2 
 3 int main(void)
 4 {
 5     printf("%d\n",3<<2);//12
 6     
 7     printf("%d\n",3>>1);//1
 8     
 9     int a = -10;
10     
11     printf("%d\n",a>>1);//-5
12     return 0;
13 }
View Code

 宏定义的使用和分析

1.#define定义的是在预处理阶段完成替换(只是替换不做运算

2.#define的位置无限制,在定义之后就都可以使用这个宏(无作用域的限制(由于编译器看不到宏,变量和函数有作用域的限制))

3.#define定义的宏常量本质为字面量,不占用内存(const定义的常量本质为变量,故要占用内存)

 1 #include <stdio.h>
 2 #define ERROR -1
 3 #define PATH1 "D:\test\test.c"
 4 #define PATH2 D:\test\test.c
 5 #define PATH3 D:\test\
 6 test.c
 7 int main(void)
 8 {
 9     int  err = ERROR;
10     char* p1 = PATH1;
11     char* p2 = PATH2;//PATH2被替换为D:\test\test.c,不符合C语言中的指针操作
12     char* p3 = PATH3;//PATH3被替换为D:\testtest.c,不符合C语言中的指针操作
13     return 0;
14 }
宏定义的使用

注:上述代码宏定义正确,但是因为不符合C语言的语法所以在编译阶段会报错

#define表达式的使用类似函数调用

#define表达式可以比函数更强大

#define表达式比函数更容易出错

#宏表达式没有任何调用开销

宏是在预处理阶段处理的,而语法、语义的检查是在编译阶段检查的,所以宏定义的东西不会有语法的检查,故宏出现语法的错误也可以编译完成。

#宏表达式中不可以出现递归调用

 1 #include <stdio.h>
 2 #include <malloc.h>
 3 
 4 #define MALLOC(type, x)   (type*)malloc(sizeof(type)*x)
 5 #define FREE(p)           (free(p), p=NULL)
 6 
 7 #define LOG_INT(i)        printf("%s = %d\n", #i, i)
 8 #define LOG_CHAR(c)       printf("%s = %c\n", #c, c)
 9 #define LOG_FLOAT(f)      printf("%s = %f\n", #f, f)
10 #define LOG_POINTER(p)    printf("%s = %p\n", #p, p)
11 #define LOG_STRING(s)     printf("%s = %s\n", #s, s)
12 
13 #define FOREACH(i, n)     while(1) { int i = 0, l = n; for(i=0; i < l; i++)
14 #define BEGIN             {
15 #define END               } break; } 
16 
17 int main()
18 {
19     int* pi = MALLOC(int, 5);
20     char* str = "D.T.Software";
21     
22     LOG_STRING(str);
23     
24     LOG_POINTER(pi);
25     
26     FOREACH(k, 5)
27     BEGIN
28         pi[k] = k + 1;
29     END
30     
31     FOREACH(n, 5)
32     BEGIN
33         int value = pi[n];
34         LOG_INT(value);
35     END
36     
37     FREE(pi);
38     
39     LOG_POINTER(pi);
40     
41     return 0;
42 }
宏定义的妙用
 1 #include <stdio.h>
 2 
 3 #define _SUM_(a, b) (a) + (b)
 4 #define _MIN_(a, b) ((a) < (b) ? (a) : (b))
 5 #define _DIM_(a) sizeof(a)/sizeof(*a)
 6 
 7 
 8 int main()
 9 {
10     int a = 1;
11     int b = 2;
12     int c[4] = {0};
13 
14     int s1 = _SUM_(a, b);
15     int s2 = _SUM_(a, b) * _SUM_(a, b);
16     int m = _MIN_(a++, b);//替换为((a++) < (b) ? (a++) : (b))所以结果为2
17     int d = _DIM_(c);
18 
19      printf("s1 = %d\n", s1);
20      printf("s2 = %d\n", s2);
21      printf("m = %d\n", m);
22      printf("d = %d\n", d);
23 
24     return 0;
25 }
View Code
 1 #include <stdio.h>
 2 
 3 void def()
 4 {
 5     #define PI 3.1415926
 6     #define AREA(r) r * r * PI
 7 }
 8 
 9 double area(double r)
10 {
11     return AREA(r);
12 }
13 
14 int main()
15 {
16     double r = area(5);
17 
18     printf("PI = %f\n", PI);
19     printf("d = 5; a = %f\n", r);
20     
21     return 0;
22 }
宏定义的作用于验证

 #运算符用于在预处理期将宏参数转换为字符串

 1 #include <stdio.h>
 2 
 3 #define CALL(f, p) (printf("Call function %s\n", #f), f(p))
 4    
 5 int square(int n)
 6 {
 7     return n * n;
 8 }
 9 
10 int func(int x)
11 {
12     return x;
13 }
14 
15 int main()
16 {
17     int result = 0;
18     
19     result = CALL(square, 4);//替换为(printf("Call function %s\n,"square"")square(4))
20     
21     printf("result = %d\n", result);
22     
23     result = CALL(func, 10);
24     
25     printf("result = %d\n", result);
26 
27     return 0;
28 }
#运算符的妙用

注:#和##运算符只在宏定义中有效

数组地址与数组名

1.数组名代表数组首元素的地址(数组名不包含数组的长度信息,所以步长与数组地址的步长不一样

2.数组的地址需要用取地址符(&)才能得到

3.数组首元素的地址和数组的地址值相同,但是表示的概念是不同的

4.指针之间支持减法(相减时为数组下标)和关系(<,<=,>,>=)运算(需注意的是两个指针需要指向同一个数组)

5.两个指针之间的比较运算(==,!=)无限制(需注意参与比较运算的指针类型必须相同)

6.数组名只是可以看做指针而已,他们之间不是完全等价的关系

 1 #include <stdio.h>
 2 
 3 int main()
 4 {
 5     extern int *a;//error,由于数组名和指针不是完全等价的所以会报段错误,
 6     extern int a[];//这样正确,如果数组名和指针完全一样的话此句话和上面的语句应该都是正确的
 7     printf("&a = %p\n", &a);
 8     printf("a = %p\n", a);
 9     printf("*a = %d\n", *a);
10 
11     
12     return 0;
13 }
14 test.c
15 
16 
17 int a[] = {1, 2, 3, 4, 5};
18 
19 ext.c
数组和指针的不同

字符串字面量的本质

1.本质为数组

2.为指向第一个数组元素的指针

 1 #include <stdio.h>
 2 
 3 int main()
 4 {
 5     char b = "abc"[0];
 6     char c = *("123" + 1);
 7     char t = *"";
 8     
 9     printf("%c\n", b);//a
10     printf("%c\n", c);//2
11     printf("%d\n", t);//0
12     
13     printf("%s\n", "Hello");//Hello
14     printf("%p\n", "World");//0x00405052
15     
16     return 0;
17 }
字符串字面量的本质

指针数组和数组指针

数组有自己特定的类型:例如int array[5] 的类型为int[5]

C语言中通过typedef为数组类型重新命名

格式:typedef type(name)[size];

使用:typedef int(AINT5)[5];

定义:AINT5 iArray;

数组指针

1.数组指针用于指向一个数组

2.定义格式:

1)可以使用数组类型定义数组指针:AINT5 *pointer

2)也可以直接定义:type(*pointer)[n]

type:为指向的数组元素类型

pointer:为数组指针变量名

n:为指向的数组的大小

3.数组地址通过&加数组名来获得

 1 #include <stdio.h>
 2 
 3 typedef int(AINT5)[5];
 4 typedef float(AFLOAT10)[10];
 5 typedef char(ACHAR9)[9];
 6 
 7 int main()
 8 {
 9     AINT5 a1;
10     float fArray[10];
11     AFLOAT10* pf = &fArray;
12     ACHAR9 cArray;
13 
14     char(*pc)[9] = &cArray;
15     char(*pcw)[4] = cArray;
16     
17     int i = 0;
18     
19     printf("%d, %d\n", sizeof(AINT5), sizeof(a1));
20     
21     for(i=0; i<10; i++)
22     {
23         (*pf)[i] = i;
24     }
25     
26     for(i=0; i<10; i++)
27     {
28         printf("%f\n", fArray[i]);
29     }
30     
31     printf("%p, %p, %p\n", &cArray, pc+1, pcw+1);
32 
33     return 0;
34 }
数组类型和数组指针

指针数组

1.指针数组是一个普通的数组

2.指针数组中的每一个元素为一个指针

3.指针数组的格式为:type* parray[n];

type:为数组中每个元素的类型

parray:为数组名

n:为数组大小

4.使用:

char* keyword[] = {
"do",
"for",
"if",
"register",
"return",
"switch",
"while",
"case",
"static"
};

 函数指针

1.函数指针用于指向一个函数

2.函数名是函数执行的入口地址

3.函数也有自己的类型

1)函数的类型由返回值类型,参数类型和参数个数共同决定

例如:int add(int a,int b)的类型为int(int,int)

4.可以通过typedef来为函数类型重命名

typedef type name(parameter list)

例如:typedef int f(int,int)

5.可以通过函数类型定义函数指针:例如:f *pointer

6.也可以直接定义函数指针:例如:type (*pointer)(parameter list)

type:为函数的返回值类型

pointer:为函数指针变量名

parameter:为所指的函数的参数列表

 1 #include <stdio.h>
 2 
 3 typedef int(FUNC)(int);
 4 
 5 int test(int i)
 6 {
 7     return i * i;
 8 }
 9 
10 void f()
11 {
12     printf("Call f()...\n");
13 }
14 
15 int main()
16 {
17     FUNC* pt = test;
18     void(*pf)() = &f;
19     
20     printf("pf = %p\n", pf);
21     printf("f = %p\n", f);
22     printf("&f = %p\n", &f);
23     
24     pf();//此为以后主要的函数指针的调用
25     
26     (*pf)();
27     
28     printf("Function pointer call: %d\n", pt(2));
29     
30     return 0;
31 }
函数指针的使用

动态内存分配

malloc和free

1.malloc所分配的是一块连续的内存

2.以字节为单位,且不带任何的类型信息

3.free用于将动态的内存归还给系统

4.格式:

void* malloc(size_t size);

void free(void* pointer);

5.注意:malloc实际分配的内存可能会比请求的多(这是由于内存对齐的原因,例如:内存是4个字节对齐,但是申请的是1个字节但是会返回4个字节的空间)

6.malloc(0),可以分配成功,返回地址信息,长度为0(既要有地址信息又要有长度信息),一直malloc(0)不释放,由于第5条的原因所以会导致内存泄漏

1 #include <stdio.h>
2 #include <malloc.h>
3 int main()
4 {
5     
6     int* p = (int*)malloc(0);
7     printf("%p\n",p);
8     return 0;
9 }
malloc(0)

1.栈在程序中用于维护函数调用的上下文

2.函数中的参数和局部变量存储于栈上

3.函数调用时,对应的栈空间在函数返回前是专用的,但是函数调用结束后,栈空间将被释放(注意:调用函数的返回值不可以为局部变量的地址,因为栈空间的释放,局部变量所在的空间也就被释放了,这就会导致野指针的出现)

 1 #include <stdio.h>
 2 
 3 int* g()
 4 {
 5     int a[10] = {0};
 6     
 7     return a;//由于返回的是局部变量的地址,但是此变量在函数调用完成后就销毁了,所以相当于是返回了一个不知道什么内容的地址
 8 }
 9 
10 void f()
11 {
12     int b[10] = {1,2,3,4,5,6,7,8,9,10};
13     int* pointer = g();
14     int i;
15     for(i=0,i<10,i++)
16     {
17         pointer[i] = b[i];
18         printf("%d\n",pointer[i]);
19     }
20     
21 }
22 
23 int main()
24 {
25     f();
26     
27     return 0;
28 }
指向栈数据的指针

1.堆是程序中一块预留的程序空间,可由程序自由使用

2.堆中被程序申请使用的内存在被主动释放前将一直有效

3.使用堆的原因:栈上的数据在函数返回后就会被释放掉,无法传递到函数的外部,如:局部变量

静态存储区

1.静态存储区随着程序的运行而分配空间

2.静态存储区的生命周期直到程序运行结束

3.在程序的编译期静态存储区的大小就确定了

3.静态存储区主要用于保存全局变量和静态局部变量

4.静态存储区的信息最终会保存到可执行程序中

 1 #include <stdio.h>
 2 
 3 int g_v = 1;
 4 
 5 static int g_vs  = 2;
 6 
 7 void f()
 8 {
 9     static int g_vl = 3;
10     
11     printf("%p\n", &g_vl);
12 }
13 
14 int main()
15 {
16     printf("%p\n", &g_v);
17     
18     printf("%p\n", &g_vs);
19     
20     f();
21     
22     return 0;
23 }
静态存储区的验证

结果为连续的三个地址,所以得到验证为这三个变量存储于静态存储区

 程序的内存分布

程序与进程

程序是静态的表现为一个可执行文件

进程是动态的概念,程序由操作系统加载后得到进程

每个程序对应多个进程

每个进程对应一个程序

脚本文件执行过程:1.由操作系统加载脚本解释程序,生成进程,进程反过来读取脚本文件

各个段的作用:

堆栈是在程序运行之后才正式存在,是程序运行的基础

.bss段存放的是未初始化的全局变量和静态变量

.text段存放的程序中的可执行代码

.data段保存的是已经初始化了的全局变量和静态变量

.rodata段存放的是程序中的常量值,例如字符串常量

程序术语的对应关系

静态存储区通常指的是.bss和.data段

只读存储区通常指程序中的.rodata段

局部变量所占空间为栈上的空间

动态空间为堆中的空间

程序可执行代码存放于.text段

野指针

何为野指针:

1.指针变量中的值是非法的内存地址,进而形成野指针

2.野指针不是NULL指针,而是指向不可用内存地址的指针

3.NULL指针无危害

4.C语言中无法判断一个指针所保存的地址是否合法

怎么形成野指针:

1.局部指针变量没有被初始化

2.指针指向的变量在指针改变指向之前被销毁

3.使用已经释放过的指针

4.进行了错误的指针运算

5.进行了错误的强制类型转换,例如:int *p = (int *)0x01,由于低地址不可以被访问所以此指针(p)也为野指针

 1 #include <stdio.h>
 2 #include <malloc.h>
 3 
 4 
 5 int main()
 6 {
 7     int* p1 = (int*)malloc(40);
 8     int* p2 = (int*)1234567;
 9     int i = 0;
10     
11     for(i=0; i<40; i++)
12     {
13         *(p1 + i) = 40 - i;
14     }
15 
16     free(p1); 
17     
18     for(i=0; i<40; i++)
19     {
20         p1[i] = p2[i];//使用了已经释放了的指针
21     }
22     
23     return 0;
24 }
野指针

如何避免出现野指针:

1.不返回局部变量和局部数组的地址

2.任何变量在定义之后必须初始化为0

3.字符串数组必须确认是\0结束符后才能成为字符串

4.任何使用与内存操作相关的函数必须指定长度信息

5.指针在销毁之后,要使其指向NULL

常见的内存错误

1.结构体成员指针未初始化

2.结构体成员指针未分配足够的内存

3.内存分配成功,但是未初始化

4.内存越界

 内存的使用规则

1.动态申请内存之后,应该立即检查指针的值是否为NULL,防止使用NULL指针

2.free之后的指针必须立即赋值为NULL

3.任何与内存相关的操作函数都必须带长度信息

4.malloc操作和free操作必须匹配,防止内存泄漏和多次释放

函数的意义

程序 = 数据 + 算法

           ||

           V

C程序 = 数据 + 函数

C语言的程序划分为各个模块函数,最后都是在调用库函数

面向过程的程序设计

1.面向过程是一种以过程为中心的编程思想

2.首先将复杂问题分解为一个个容易解决的问题

3.分解后的问题可以按照步骤一步一步完成

4.函数是面向过程在C语言中的体现

5.解决问题的每一个步骤可以使用函数来实现

声明和定义
声明:告诉编译器程序单元的存在

定义:程序单元的具体是做什么的

可变参函数的实现

参数可变函数的实现依赖于stdarg.h头文件

va_list 参数集合

va_arg 取具体参数值

va_start 标识参数访问的开始

va_end 标识参数访问的结束

 1 #include <stdio.h>
 2 #include <stdarg.h>
 3 
 4 float average(int n,...)
 5 {
 6    va_list args;
 7    int i = 0;
 8    float sum = 0;
 9    va_start(args, n);
10    for(i=0;i<n;i++)
11    {
12      sum += va_arg(args,int);
13    }
14    va_end(args);
15    return sum/n;
16 }
17 
18 int main(void)
19 {
20     printf("%f\n", average(5, 1, 2, 3, 4, 5));
21     printf("%f\n", average(4, 1, 2, 3, 4));
22     
23     return 0;
24 }

可变参数的限制:

1.可变参数必须从头到尾按照顺序逐个访问

2.参数列表中至少要存在一个确定的命名参数

3.可变参数函数无法确定实际存在的参数的数量

4.可变参数函数无法确定参数的实际类型

注:va_arg中如果指定了错误的类型,那么结果是不可预测的

 

posted @ 2020-08-17 20:35  夜空的北极星  阅读(318)  评论(0编辑  收藏  举报