c语言深度学习

                                    

0、数据类型可以理解为固定内存大小的别名  数据类型是初期建立便于利用的创建变量的模子

1、变量是只是一段内存的别名,通过别名来使用内存 

Tips

(

计算机硬件的存储本身是包含是浮点数,和定点数,然后读取这片内存的时候,按照原本已经制定好的规则(比如浮点数,或定点数)读取

)

2c++的引用也是给这一段内存起的另一个别名,

3register 变量的大小必须小于四个字节,如果变量申请的空间过大就会导致最终出错,数据溢出。(为什么不允许register修饰全局变量,全局变量存在静态区,在运行是一直存在的,如果可以放在全局变量里,一旦数据一多,register 会崩溃的,而导致Cpu 蹦溃)

4、自定义变量 typedef 的用法

变量是内存的别名,自定义的变量是对存在的类型进行重命名,不是创建变量

Typedef  int  INT32;

                            第二节

属性关键字 auto ,static register 

Auto c语言中的变量可以有自己的属性

在定义的时候可以加上“属性”关键字

属性关键字指明变量有特殊的意义

auto

1、auto c语言中所有的局部变量的默认属性,自动创建,函数完毕自动释放的

2、在栈里分配的空间,

Static 

1、static 关键字指明变量的静态属性,

2、全局变量的默认为静态属性,但不加static可以外调,但是加了static 就不可以外调,

3、函数的使用,用static修饰的函数限定只能在本文件内使用,所有的函数都是全局函数

4、全局变量的默认是静态属性,加上static 表示只能在本文件内使用

5、在函数里修饰的话,只初始化一次。只能在函数内部使用,但不会被销毁,他是在全局变量数据区的,只是有作用域(Tips 假使文件对 Extern 

6、作为全局变量作用域从定义处开始直到文件结尾,定义处前面要引用的话必须加 extern

7、extern 如果访问了其他文件的static变量,会出错,但是如果初始化了的话,就生成该变量在这个文件下的用 extern int i= 34; 不允许修改的,只可以在其他的地方进行修改。默认为exten,任何的全局变量都是默认了Extern的,只是显示的调用

// 参考: 1、初始的用途是定义的局部静态属性,后来又定义了一个限制在本文件内使用

Register 

1、register 关键字指明变量存储在寄存器中

2、Registere只是请求放在寄存器中,不一定能申请的到

3、ERROR register 变量必须是cpu可接受的值,

4、不能用&运算符获取register变量的地址,对实时性要求特别高的话就需要用寄存器变量

//全局变量 设为auto 或者register变量,全局变量的默认是全局静态区分配的,

而 用这个的话会产生警告,甚至有的编译器直接报错

                        第三节

if语句中零值比较的注意点

1bool型变量应该直接出现于条件中,不要进行比较

2、普通变量和0值比较时,0值应该出现在比较符号左边

3float型变量不能直接进行0值比较,需要定义精度

4、布尔值最好出现在左边。

C语言中不存在布尔变量,通常情况下都是自己编译器自己定义的,而编译器的不同,truefalse代表的值可能不同,所以判断这个会容易出错,最好使用替代的词,如 true,false

,或者直接加叹号处理

‚注意浮点型是连续的值,而计算机表示的二进制描述的一个非连续数,所以这个表现的很有误差是可以理解的

ƒ的处理用 #define  e  0.00000001

 Float fa  比较方法   if(fa < = fa+e  &&  fa > = fa -e)

switch语句对应单个条件多个分值的情形

1、switch处理的是一个条件,多个分支的情况只能处理整形和char型的

2、每个case语句分支必须要有break,否则会导致分支重叠

3、default语句有必要加上,以处理特殊情况

4、case语句中的值只能是整型或字符型

5、case语句排列顺序分析

 

 

6、按字母或数字顺序排列各条语句

7、正常情况放在前面,异常情况放在后面

8、default语句只用于处理真正的默认情况

 

9、if语句实用于需要“按片”进行判断的情形中

10、switch语句实用于需要对各个离散值进行分别判断的情形中

11、if语句可以安全从功能上代替switch语句,但switch语句无法代替if语句

12、switch语句对于多分支判断的情形更加简洁

 

循环: while do while(), for 

便捷性,for循环不易出错,do { } while要有检错语句 

一 whilen &&  ret += n--

一般函数设计原则

1、分配资源语句  2、执行控制语句 (判断是否可继续执行) 3继续执行

  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

        {

            p[i] = i;

            printf("%d\n", p[i]);

        }

        ret = 1;

  }while(0);

    

    free(p);

    return ret;

 

breakcontinue的区别

break表示终止循环的执行

continue表示终止本次循环体,进入下次循环执行
switch能否用continue关键字?为什么?

不能,因为break,天生用于终止块操作的。Loop and switch 其他的都是顺序是执行,跳出的无意义

 

 

 

                              第四节

Goto 

1、 高手潜规则 一般很少使用Goto关键词

2、 项目经验,Goto用的次数越多,则项目越差

3、 所有人员都认为Goto 不好用,决定禁止使用

¥   由于程序中直接跳转带来了结构化程序的不好控制,跳过了堆程序的内存分配,后期使用的时候会使程序奔溃,它破坏了程序的至上而下的规则。

Void 基础类型

1、修饰函数参数,不接受任何参数int  add( );

2、修饰函数返回值,表示不返回值 void add();

(Tips:  Void表示几个内存单位呢,这是c语言中的灰色单位,他代表无变量或无值,由于c语言标准没有申明,所以不同的厂商对这个的决策也不一样,有的用占一个字节,有的却没有。C++中明令禁止,不允许这种操作,所以不允许void  oid占的内存大小是厂商自定义的。

3、不允许定义 Void  i

4.、任何void * 作为作为左值可以接受任意的类型,而作为右值类型必须进行强制转化,很多可以通过,是因为要求不严格,为了可移植性,强制转换是必须要写的。

C语言规定只有相同类型的指针才可以相互赋值)

函数设计方法
函数的参数

‚函数的算法设计

ƒ函数的返回值 

Void的使用 void *  memset(void* p ,char v,int size) 函数作用把所有的数据都清零,可以接受任意类型的地址,并且可以把所有都可以改变。

Extern 

1、申明外部定义的文件或者变量和函数

2、作为c的标准指示字,C++编译器和一些变种C编译器默认会按“自己”的方式编译函数和变量,通过extern关键可以命令编译器“以标准C方式进行编译”。

extern "C"

{

    int add(int a, int b)

    {

        return a + b;

    }

}  

 

Sizeof()

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

2sizeof用于“计算”相应实体所占的内存大小

3sizeof的值在编译期就已经确定,意味着运行期的时间点的设置是无效的,例如: sizeofi++)起不到时间点的作用

4int a ,,double b sizeof( a+b ) 选择其中占用内存最大的一个值

Sizeof不是函数的各种用法

sizeof  (int)  ‚ sizeof  c  ƒ sizeof (c)

问题 sizeof  int 为什么不可以求出其中的整形变量的内存的大小?

答 原因:在编译器会认为sizeof 修饰int, int类型的只可以 unsignedsigned,

sizeof修饰 int 是不允许通过的,故而报错,所以禁止使用 sizeof  TypeName

5、数组做sizeof的参数不退化,传递给strlen就退化为指针了。

6、sizeof操作符的结果类型是size_t,它在头文件中typedefunsignedint类型。

7、该类型保证能容纳实现所建立的最大对象的字节大小。

8、2.sizeof是算符,strlen是函数。

9、3.sizeof可以用类型做参数,strlen只能用char*做参数,且必须是以''\0''结尾的。

10、sizeof还可以用函数做参数,比如:

 

 

Short f();

printf("%d\n",sizeof(f()) );

    输出的结果是sizeof(short),即2即返回值的类型大小

数组做sizeof的参数不退化,传递给strlen就退化为指针了。

11、大部分编译程序在编译的时候就把sizeof计算过了是类型或是变量的长度这就是sizeof(x)可以用来定义数组维数的原因

12、

13、

14、

Char str[20]="0123456789";

Int a=strlen(str);//a=10;

Int b=sizeof(str);//b=20;

15strlen的结果要在运行的时候才能计算出来,是用来计算字符串的长度,不是类型占内存的大小。

15、sizeof后如果是类型必须加括弧,如果是变量名可以不加括弧。这是因为sizeof是个操作符不是个函数。

16、8.当适用于一个结构类型时或变量, sizeof 返回实际的大小,当适用于静态的空间数组, sizeof 归还全部数组的尺寸。

17、sizeof 操作符不能返回被动态分派的数组或外部数组的尺寸

18、数组作为参数传给函数时传的是指针而不是数组,传递的是数组的首地址,

 

 

                         第五节

Const的用法

1、在C语言中const修饰的变量不能通过变量名进行改写操作,其本质还是变量,通过指针对其值进行修改是允许的

2const修饰的变量会在内存占用空间

3、本质上const只对编译器有用,在运行时无用(即运行时可以改变他的值)

4、对其改写操作例如: const int a = 89;  int *p = &a;  *p = 67; 改写成功

5、现代的编译技术中,在初期作为常量存入符号表,然后后期,要修改的时候临时分配空间并给他改变他的值,然而下次使用的时候第一次使用的时候都是从符号表,

如果程序中const修饰的整个文件都是作为右值而不动地址,就会只是单纯的替换,如果程序中出现对该变量取地址操作时时,就会到一直从内存里面取值读出。

‚作为左值时直接报错(Tips c++ 中 在初期作为常量存入符号表,然后后期,要修改的时候临时分配空间并给他改变他的值,然而下次使用的时候第一次使用的时候都是从符号表读取,即使内存改变了他的值,)

 在C语言中const修饰的数组是只读的

    ‚  const修饰的数组空间不可被改变 相当于指针和值都加了const, visual c++ 6.o 可通过,而c++中就绝对不允许这样操作的

7区别修饰的const

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

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

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

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

(Tips 

Const Typename *p ; // p 可变,p指向的内容不可变

Typename * Const  p; // p不可变,p指向的内容可变

)

方法 左数在右指,号出现的位置,数据区就数据不可改变,指针区指针不可改变

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

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

修饰函数指针表示,返回的指针的指向是不准改变的,

(注意可以通过其他别名或指针操作这块内存,修饰符修饰的师某一个变量名的权限,        而没有限制的变量当然可以操作的)

 

Volatile的用法

1volatile可理解为“编译器警告指示字”

2volatile用于告诉编译器必须每次去内存中取变量值

3volatile主要修饰可能被多个线程访问的变量

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

编译器选择的优化,提高效率,某些变量值在文件中一直到那个做左值,编译器会直接从符号表中读取,而,当前线程睡眠,将其他线程的线程开启,(如硬件中断),如果线程改变其中得值,数据从符号表中读取,就会引起错误。(不可预期的错误 error of expectation

思考题 volatile const是否可以修饰同一个变量呢? 又有什么意以?

答可以共存 ,对于const来说,应该理解为只读的意思,如果一个变量被定义为const了,那么它告诉使用者这个变量是只读的,不要试图在当前的模块中改变它。 而对于volatile来说,它是告诉编译器,这个被声明的变量可能在本程序之外被修改,告诉编译器每次在使用这个变量的时候都需要重新加载,不能优化。 只读表示编译器不允许代码修改变量。但并不表示这个变量在其它地方不能够被修改。

 

                                       第六节

Struct 结构体变量

1、空结构体在计算机中占有多少内存? 

c语言标准没有准确的指出,所以不同的市场的厂商的理解有一个字节也有为空的,一个字节的认为,这样子用空结构体创建的变量的内存地址相同,所以只能找最小的单位一字节,这样子创建的两个结构体变量就不会相同了。

(

Tips: Void表示几个内存单位,这也是c语言中的灰色单位,他代表无变量或无值,由于c语言标准没有申明,所以有的厂商认为是零,有的直接报错,

)

2、柔性数组即数组大小待定的数组

   C语言中结构体的最后一个元素可以是大小未知的数组

   C语言中可以由结构体产生柔性数组

Struct  _soft     \\结构体名

{

Int length;
    int arr[];

}softarr; 

 柔性数组占有的内存大小是结构体的长度的大小的成员所占的大小,而无大小的数组是一个占位符,不占有内存,可直接使用
 ‚如何申请空间 

 Softarr * pst = (softarr * )malloc(sizeof(softarr) + len* sizeof(Typename));

 ƒ 使用 pst->arr[i] ......, (数据全部导入后,该数组的大小还是成员变量的大小,而数组变量

3、struct的成员变量的空间都是独立的,自己申请的

 

Union

truct中的每个域在内存中都独立分配空间

union只分配最大域的空间,所有域共享这个空  
实用注意事项:

大端模式  高地址放低位    →→→→

小端模式 低地址放低位     ←←←←

 

Enum 

1enum是一种自定义类型,默认首元素为0,以后类推的不断增加,

2enum默认常量在前一个值的基础上依次加1

3enum类型的变量只能取定义时的离散值(必须都是int 类型的 )

4Enum 是真正的常量,不用分配空间,故无法取地址,且只能初始化一次,在定义的时候赋值,其他的时候都不允许,

6、#define宏常量只是简单的进行值替换,枚举常量是真正意义上的常量

7、#define宏常量无法被调试,枚举常量可以

8、#define宏常量无类型信息,枚举常量是一种特定类型的常量

9、enumconst变量的区别,enum是真正意义上的常量,而const只是只读属性,可以间接改变其值,而且具有分配的内存,可以获取地址。

 

Typedef 的用法

Typedef 的意义是什么?

答 : Tyedef 是对已经存在的类型进行重新命名,而不是定义新的数据类型

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

2typedef并没有产生新的类型

3typedef重定义的类型不能进行unsignedsigned扩展

4typedef是给已有类型取别名

5#define为简单的字符串替换,无别名的概念

                              第二章

 

一 注释符号的使用技巧 : 

1、注释方法 

/*

 

*///

2、注释应该准确易懂,防止二义性,错误的注释有害而无利

3、注释是对代码的提示,避免臃肿和喧宾夺主

4、一目了然的代码避免加注释

5、不要用缩写来注释代码,这样可能会产生误解

6、注释用于阐述原因而不是用于描述程序的运行过程

7、注释符号相当于空格,而不是其他的,所以要注意使用

8、注释符不能出现在双引号之间,否则将会视为字符串

9、编译器会在编译过程删除注释,但不是简单的删除而是用空格代替

10、编译器认为双引号括起来内容都是字符串,双斜杠也不例外

/*……*/”型注释不能被嵌套

11//  statements 

二 接续符和转义符

接续符

1、编译器会将反斜杠剔除,跟在反斜杠后面的字符自动解到前一行

2、在接续单词时,反斜杠之后不能有空格,反斜杠的下一行之前也不能有空格

3、接续符适合在定义宏代码块时使用比如定义像函数之类的

例如:#define  swap(a,b)    {             \

                            int temp = a;  \

                            tem p= a;     \                          

                            a = b;        \

                            b = temp ;    \                           }

4、接续符的是指反斜杠在一行中的最后一个字符,告诉编译器下一行的内容是本行的,并且直接连接。

 

转义符

 1C语言中的转义符(\)主要用于表示无回显字符,也可用于表示常规字符

 2C语言中的反斜杠(\)同时具有接续符和转义符的作用

 3、当反斜杠作为接续符使用时可直接出现在程序中

 4、当反斜杠作为转义符使用时需出现在字符或字符串中

 

三 单引号和双引号

1、本质上单引号括起来的一个字符代表整数

2、双引号括起来的字符代表一个指针

3C编译器接受字符和字符串的比较,可意义是错误的

4C编译器允许字符串对字符变量赋值,其意义是可笑的

5char* p1 = (char *)1 ;//p1指向内存地址为一的地址,低地址一般要留给系统用的,不可能存在字符串给它用

6char* p2 = (char *)'1';//'1'这是一个aslla 码,代表一个整数,,其实这句话的意思让 p2指向内存的地址为‘1’的地方

7char* p3 = "1"; // 指向全局变量的静态区,printf(p3); 

8printf(‘\n’);// printf(); 接受一个字符串指针进行读取(遇见\0 结束) 而‘\n’地址是给系统用的,它不可能可以访问的,所以出现段错误。

9C语言中的单引号用来表示字符常量     ‘a+1 表示 ‘b’

10C语言中的双引号用来表示字符串常量   “a”+1 字符首地址右移一位

11、字符

四 逻辑运算符使用分析

1、程序中的短路

  短路规则:

  ||从左向右开始计算,当遇到为真的条件时停止计算,整个表达式为真;所有条件为假时表达式才为假。

  &&从左向右开始计算,当遇到为假的条件时停止计算,整个表达式为假;所有条件为真 时表达式才为真

2、三目运算法

   a < b ? a : b

 返回的是变量的值,而不是变量,而 c++在这里进行了改进,返回的是这个的引用。

 ‚  *(a < b ? &a : &b) 改进方式

五 位运算

  C语言号称高级语言为什么支持位运算?

   在嵌入式开发中,会经常与外部链接,串口和并口很需要位运算的

1、按位与 2 & 3  010 & 011  =  010  

2、按位或 2 |  3  010 | 011   =  011

3、按位异或2 ^ 3  010^011   =  011

有结合律和交换律

4、左移和右移注意点

  左移运算符<<将运算数的二进制位左移

  规则:高位丢弃,低位补0

5、右移运算符>>把运算数的二进制位右移

  规则:高位补符号位,地位丢弃

6、防错准则:

避免位运算符,逻辑运算符和数学运算符同时出现在一个表达式中

当位运算符,逻辑运算符和数学运算符需要同时参与运算时,尽量使用括号()来表达计算次

位移的大小不能大于输的长度且不能为负的

7、Tips

左移n位相当于乘以2n次方,但效率比数学运算符高

右移n位相当于除以2n次方,但效率比数学运算符高

8、交换变量的三种方式

 #define SWAP1(a,b) \

{                  \

    int temp = a;  \

    a = b;         \

    b = temp;      \

}

 

‚#define SWAP2(a,b) \

{                  \

    a = a + b;     \

    b = a - b;     \

    a = a - b;     \

}//比第一种少用了一个变量和 很大的时候会溢出

 

ƒ#define SWAP3(a,b) \

{                  \

    a = a ^ b;     \

    b = a ^ b;     \

    a = a ^ b;     \

}//效率最高,但只适合整形 (a ^ a) ^b = b 

试题: 有一个数列,其中的自然数都是以偶数的形式出现,只有一个自然数出现的次数为奇数次。编写程序找出这个自然数。

方法一: 将数列排序,排序后去读取,一旦奇数个就跳出

方法二: 用空间换时间,先找到最大的数n,然后申请n个空间,,把数组清零

然后用循环for (...)  b[a[i]]++ ; 最后遍历数组的大小,一但是奇数就返回。

方法三: 用按位异或不断的消除,最后只剩下一个就是要的答案

#include

Int main()

{

   Int a = {1,1,1,2,3,4,5,6,5,6,4,3,2};

   Int i = 0;

   Int find = 0;

   For (i = 0; i< (sizeof(a)/sizeof(int )), i++)

   Find = find^a[i];  

   Printf(“%d\n”,find;

   Return 0;

}

自增自减

I = 3

++i ++i++i

这是一个c语言的灰色地带,不同的编译器有不同的值,在visual c++ 5.0 

I = 3

++i, ++i,++i) 逗号表达式,从左到右开始做

贪心法 -- ++, --表达式的阅读技巧

1、编译器处理的每个符号应该尽可能多的包含字符

2、编译器以从左向右的顺序一个一个尽可能多的读入字符

3、当即将读入的字符不可能和已读入的字符组成合法符号为止

4、求值或报错,不能再读的时候,

例如 第一次读取 ++i  +  + 读到这的时候,发现已经不能在读到表达式使得表达式有意义,数值 ++, 没有办法,编译器报错 ,增加空格之后,贪心法的解析时,空格即为终止符,因此空格在容易混的地方能够更好的解析。

(Tips

c语言中 ++ i 与 i++ 在表达式中只相当与右值。

c++中,允许++ i 当做左值; 

)

运算顺序:

 

C语言的隐式转换

C语言隐式类型转换

算术运算式中,低类型转换为高类型

赋值表达式中,表达式的值转换为左边变量的类型

函数调用时,实参转换为形参的类型

函数返回值,return表达式转换为返回值类型

 

预编译

1、•处理所有的注释,以空格代替

2、•将所有的#define删除,并且展开所有的宏定义

3、•处理条件编译指令#if, #ifdef, #elif, #else, #endif

4、•处理#include,展开被包含的文件

5、•保留编译器需要使用的#pragma指令

编译

1、•对预处理文件进行一系列词法分析,语法分析和语义分析

2、•词法分析主要分析关键字,标示符,立即数等是否合法

3、•语法分析主要分析表达式是否遵循语法规则

4、•语义分析在语法分析的基础上进一步分析表达式是否合法

5、•分析结束后进行代码优化生成相应的汇编代码文件

汇编

1、•汇编器将汇编代码转变为机器可以执行的指令

2、•每个汇编语句几乎都对应一条机器指令

 

链接器的作用

1、连接器的主要作用是把各个模块之间相互引用的部分

2、处理好,使得各个模块之间能够正确的衔接。

 

编译器将编译工作主要分为预处理,编译和汇编三部

1、 连接器的工作是把各个独立的模块链接为可执行程序

2、 静态链接(object文件和libc.a文件放在一起。)在编译期完成,动态链接(公用的数据建立一个区,想要用的话就去调(lib.so) 本质是调用外部函数,而静态链接是把所有的函数和数据放在同一个文件里的。))在运行期完成

Tips 静态链接占的空间较大,但运行效率高,动态连接占用空间少,但运行效率低)

 

#define 

1#define表达式给有函数调用的假象,却不是函数

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

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

4、最好不要定义时间点的量,只选择单纯的变量,如 i++ ,

5#define  PT   允许这样定义,但没有仍和意义,将 PT 的内容替换为空,或者说,是直接删去 

6define可以替代任何变量,这个操作只是预编译中,替换而已

7define的功能比函数强大,但是漏洞比函数大的多。

8#define  DIM(Array)  (sizeof(Arryay)/sizeof(*Array))

、函数天生的弱点是数组会再调用期间退化成指针,引起数组大小的丢失,所以必须加上length,在本文件内我们会有意识的使用,在不同文件中访问会容易出错。

10、可以扩展C语言的用法,而函数却不能。

11、宏表达式在预编译期被处理,编译器不知道宏表达式的存在

12、宏表达式用“实参”完全替代形参,不进行任何运算

13、宏表达式没有任何的“调用”开销

14、宏表达式中不能出现递归定义

定义的内置宏,用于实现发现那个行出错的消息。

__LINE__     __FILE__  __DATE__

__STDC__    __TIME__

标准的日志宏:

#define LOG do     \

{                  \

time _t  t;              \

struct  tm* ti;          \

time(&t);                \

ti = localetime(&t);      \

printf("%s  [%s:%d]  %s \n", asctime(ti), __FILE__, __LINE__,s); \ 

}while(0);

 

 

条件编译 

1、条件编译的行为类似于C语言中的ifelse

2、条件编译是预编译指示命令,用于控制是否编译某段代码

 

#include的困惑

1#include的本质是将已经存在的文件内容嵌入到当前文件中

2#include的间接包含同样会产生嵌入文件内容的动作

如果间接的包含相同的头文件怎么办?会不会出错?

会出错,但是处理的方式是 ifndef   _headname_ H_

                         

条件编译的意义:

1、条件编译使得我们可以按不同的条件编译不同的代码段,

因而可以产生不同的目标代码

2#if#else#endif被预编译器处理;而ifelse语句被

编译器处理,必然被编译进目标代码

3、 实际工程中条件编译主要用于一下两种情况:

Ø 不同的生产线共用一份代码

Ø  区分编译产品的调试版和发布版

Ø 通过编译器命令行能够定义预处理器使用的宏

Ø 条件编译可以避免重复包含头同一个头文件

Ø 条件编译是在工程开发中可以区别不同产品线的代码

Ø 条件编译可以定义产品的发布版和调试版

如何实现在命令行实现宏定义 

 EXAMPLE :  GCC -DEBUG -DHIGH  //同时定义多个宏

              GCC -DEBUG = \”CZCZXCZX\”

              GCC - D MAX= 2 //定义 #define MAX  2

              GCC -DCOMMAND =\”SDCSCS\” 定义字符串

#error用于生成一个编译错误消息,并停止编译

用法

    #error message

    注:message不需要用双引号包围

#error编译指示字用于自定义程序员特有的编译错误消息

类似的,

#warning用于生成编译警告,但不会停止编译

 

#line用于强制指定新的行号和编译文件名,并对源程序

的代码重新编号

  用法

   #line number filename

    注:filename可省略

   #line编译指示字的本质是重定义__LINE____FILE__

用途:无关表示部分的代码有某个人开发的,开发的那个程序段出现错误,

 

也就是告诉调试者的写的代码的相对位置进行检错。

现在有个更好的处理技术,来处理这个问题的。

 

#pragma简介

1#pragma是编译器指示字,用于指示编译器完成一些特定的动作

2#pragma所定义的很多指示字是编译器和操作系统特有的

3#pragma在不同的编译器间是不可移植的

4、预处理器将忽略它不认识的#pragma指令

5、两个不同的编译器可能以两种不同的方式解释同一条#pragma指令

6、确定编程的的版本,便于差错,用于显示自己的程序的版本

    提示作者版本是否正确

    #pragma message("Compile Android SDK 2.0...")  

深入:

1、预处理器将忽略它不认识的#pragma指令

2、 两个不同的编译器可能以两种不同的方式解释同一条#pragma指令

     一般用法:

     # pragma parameter

     注:不同的parameter参数语法和意义各不相同

#pragma的内存对齐

构体变量是否可以直接用memcmp函数

进行相等判断?为什么?

不可以,因为内存对齐会影响到两个大小形同,可能没用的数据也进行了比较

为什么需要内存对齐?

 CPU对内存的读取不是连续的,而是分成块读取的,块的大小只

能是124816字节

 当读取操作的数据未对齐,则需要两次总线周期来访问内存,因

此性能会大打折扣

 某些硬件平台只能从规定的地址处取某些特定类型的数据,否则

抛出硬件异常

struct占用的内存大小

 第一个成员起始于0偏移处

 每个成员按其类型大小和指定对齐参数n中较小的一个进行对齐

• 偏移地址和成员占用大小均需对齐

• 结构体成员的对齐参数为其所有成员使用的对齐参数的最大值

 结构体总长度必须为所有对齐参数的整数倍

struct S1

{

    short a;

    long b;

};

 

struct S2

{

    char c;

    struct S1 d;

    double e;

};

S2 的把内存大小,的结构体内用过的最大对齐数。即此刻struct S对齐数的。

 

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

#运算符在宏中的妙用

##运算符用于在预编译期粘连两个符号

#define STRUCT(type) typedef struct _tag_##type type; struct _tag_##type

//STRUCT(Student)//  typedef struct _tag_##type type; struct _tag_##type 

STRUCT(Student)

{

    char* name;

    int id;

};

 

指针的本质:

1、指针在本质上也是一个变量

2、指针需要占用一定的内存空间

3、指针用于保存内存地址的

4.不同的指针所占的大小相同,即使是void* 也是四个字节

5、指针可以访问大部分内存(低地址除外(系统使用的地址。访问这些地址都是不合法的))

6、可以以除低地址意外的任意数据为起点进行访问,当然包括自己的数据区,堆栈区,(覆盖自己的指令区,但无意义),所以指针的使用,要慎重。

7、修改以自己定义的的数据而使用的指针的类型,就要用指针指向的类型进行访问,否则访问的数据会出错。

8、例如 : int i = 4; int j = 5; int * p = (int *)((unsigned )&i +1); *p = 4;

此刻访问 visual c++ 6.0 限制这样访问,会出错,gcc,不限制,但是这样改后i的值会改变 ,(小端模式 )。

传值和传址

指针是变量,因此可以声明指针参数

1、当一个函数体内部需要改变实参的值,则需要使用指针参数

 2、函数调用时实参值将复制到形参

 3、指针适用于复杂数据类型作为参数的函数中

 

指针小结

指针是C语言中一种特别的变量

指针所保存的值是内存的地址

可以通过指针修改内存中的任意地址内容

 

数组

  数组是相同类型的变量的有序集合

  数组在一片连续的内存空间中存储元素

 数组元素的个数可以显示或隐式指定

 数组地址与数组名地址

  数组名代表数组首元素的地址

 数组的地址需要用取地址符&才能得到

 数组首元素的地址值与数组的地址值相同

  数组首元素的地址与数组的地址是两个不同的概念

  数组名的盲点:

数组名可以看做一个常量指针,当不是指针,请注意只是代表起始地址不变的数组首地址

 1、数组名“指向”的是内存中数组首元素的起始位置

 2、在表达式中数组名只能作为右值使用

 3、只有在下列场合中数组名不能看做常量指针

 4、数组名作为sizeof操作符的参数(此时代表整个数组)

 5、数组名作为&运算符的参数(此时代表整个数组)&a + 1 (unsigned int)(&a) + sizeof(*&a)

 

 

 数组是一片连续的内存空间

数组的地址和数组首元素的地址意义不同

数组名在大多数情况下被当成常量指针处理

数组名其实并不是指针,在外部声明时不能混淆

 数组是一段连续的内存空间

数组的空间大小为sizeof(array_type) * array_size

数组名可看做指向数组第一个元素的常量指针

(Tips

{

    数组名代表的是一片空间,但是其输出地的地址值是编译器在特定区域保存的,但是他的意义是首元素的地址}

)

T IPS 初始化的效率,比其他的都非初始化操作效率高)

 不同文件下的(数组访问和指针访问区别在于,数组不需要寻址。而指针需要寻址。)

1、定义为数组,声明为指针,(会出错,把数组的内容当作地址,进行寻址进而出错,

    如何纠错,把定义的指针访问方式改成指针的形式 (char*) ((unsigned int*) &p

2、定义为指针,声明为数组  (打印乱码,因为把地址当做字符串打出了)

    纠错:  printf("pt = %s ",(char*)(unsigned int*p));

 

指针运算 p+n = (unsigned int p) +n * sizeof(*p)

指针之间只支持减法运算,且必须参与运算的指针类型必

须相同

p1 – p2;  = ( (unsigned int)p1 - (unsigned int)p2) / sizeof(type);

 

指针也可以进行关系运算

< <= > >=

指针关系运算的前提是同时指向同一个数组中的元素

任意两个指针之间的比较运算(==, !=)无限制

 

 

下标 VS 指针

从理论上而言,当指针以固定增量在数组中移动时,其效

率高于下标产生的代码

当指针增量为1且硬件具有硬件增量模型时,表现更佳

注意:

现代编译器的生成代码优化率已大大提高,在固定增

量时,下标形式的效率已经和指针形式相当;但从可

读性和代码维护的角度来看,下标形式更优。

 

A&a的区别 

a为数组是数组首元素的地址

&a为整个数组的地址

a&a的意义不同其区别在于指针运算

//a + 1 (unsigned int)a + sizeof(*a)

&a + 1 (unsigned int)(&a) + sizeof(*&a)

 

指针也可以进行关系运算

< <= > >=

指针关系运算的前提是同时指向同一个数组中的元素

任意两个指针之间的比较运算(==, !=)无限制

 

指针也可以进行关系运算

< <= > >=

指针关系运算的前提是同时指向同一个数组中的元素

任意两个指针之间的比较运算(==, !=)无限制

 

strncpy只复制len个字符到目标字符串

   当源字符串的长度小于len时,剩余的空间以’\0’填充。

   当源字符串的长度大于len时,只有len个字符会被复制,且

它将不会以’\0’结束。

  strncat最多从源字符串中复制len个字符到目标串中

   strncat总是在结果字符串后面添加’\0

   strncat不会用’\0’填充目标串中的剩余空间

strncmp只比较len个字符是否相等

 

 

数组类型:

1C语言中的数组有自己特定的类型

2、数组的类型由元素类型和数组大小共同决定

定义数组类型

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

typedef type(name)[size];

数组类型:

     typedef int(AINT5)[5];

     typedef float(AFLOAT10)[10];

数组定义:

     AINT5 iArray;

     AFLOAT10 fArray;

数组指针:

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

数组名是数组首元素的起始地址,但并不是数组的起始地址

通过将取地址符&作用于数组名可以得到数组的起始地址

可通过数组类型定义数组指针: ArrayType* pointer;

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

 pointer为数组指针变量名

 type为指向的数组的类型

 n为指向的数组的大小

指针数组:

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

指针数组中每个元素为一个指针

数组指针的定义:type* pArray[n];

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

 pArray为数组名

 n为数组大小

Main函数的参数

在执行程序的时候可以向main函数传递参数 a.exe 命令行数据

int main()

int main(int argc)

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

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

argc – 命令行参数个数

argv – 命令行参数数组

env – 环境变量数组

总结:

数组指针本质上是一个指针

数组指针指向的值是数组的地址

指针数组本质上是一个数组

指针数组中每个元素的类型是指针

 

多为指针和多维数组:

1、指针变量在内存中会占用一定的空间

2、可以定义指针来保存指针变量的地址值

为什么需要指向指针的指针?

1、 指针在本质上也是变量

2、对于指针也同样存在传值调用与传址调用

3、函数要修改外部的变量必须通过传址(C++ 中可以用引用)  

二维数组与二级指针

 1、二维数组在内存中以一维的方式排布

2、二维数组中的第一维是一维数组

3、二维数组中的第二维才是具体的值

4、二维数组的数组名可看做常量指针

数组名的理解,类型的分析

一维数组名代表数组首元素的地址

int a[5]  a的类型为int*

二维数组名同样代表数组首元素的地址

int m[2][5]  m的类型为int(*)[5]

结论:

1. 二维数组名可以看做是指向数组的常量指针

2. 二维数组可以看做是一维数组

3. 二维数组中的每个元素都是同类型的一维数组

疑问:

为什么二维却不用一维

数组结论 :

C语言中只有一维数组,而且数组大小必须在编译期就作为常数确定

C语言中的数组元素可是任何类型的数据,即数组的元素可以是另一个数组

 C语言中只有数组的大小和数组首元素的地址是编译器直接确定的

 

C语言中只会以值拷贝的方式传递参数

当向函数传递数组时

1、将整个数组拷贝一份传入函数

2、将数组名看做常量指针传数组首元素地址

3C语言以高效为最初设计目标,在函数传递的时

4、候如果拷贝整个数组执行效率将大大下降。

 

           二维数组参数

1、二维数组参数同样存在退化的问题

2、 二维数组可以看做是一维数组

3、 二维数组中的每个元素是一维数组

二维数组参数中第一维的参数可以省略

void f(int a[5]);   void f(int a[]);  void f(int* a);

void g(int a[3][3]);  void g(int a[][3]); void g(int (*a)[3]);

 

等价关系 数组传入函数

二维数组:   数组的指针:

char a[3][4]    char (*a)[4]

指针数组:   指针的指针:

int* a[5]       int** a

一维数组:   指针:

float a[5]       float* a

数组参数等效的指针参数 去方框,加*

 

C语言中无法向一个函数传递任意的多维数组  p[][] 或者 p[][][]。任意多维

为了提供正确的指针运算,必须提供除第一维之外的所有维长度 // int p[2][3] 必须提供 int p[2]

 

限制

 一维数组参数– 必须提供一个标示数组结束位置的长度信息

 二维数组参数– 不能直接传递给函数(必须写好类型 的例如: int[3]

 三维或更多维数组参数– 无法使用

 

函数指针:

C语言中的函数有自己特定的类型

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

例:int add(int i, int j)的类型为int(int, int)

2C语言中通过typedef为函数类型重命名

typedef type name(parameter list)

例:

typedef int f(int, int);

typedef void p(int);

 

回调函数是利用函数指针实现的一种调用机制

   回调机制原理

   调用者不知道具体事件发生的时候需要调用的具体函数

   被调函数不知道何时被调用,只知道被调用后需要完成的任务

   当具体事件发生时,调用者通过函数指针调用具体函数

   回调机制的将调用者和被调函数分开,两者互不依赖

 

右左法则

1. 从最里层的圆括号中未定义的标示符看起

2. 首先往右看,再往左看

3. 当遇到圆括号或者方括号时可以确定部分类型,

并调转方向

4. 重复2,3步骤,直到阅读结束

 

               为什么使用动态内存分配

 
C语言中的一切操作都是基于内存的

变量和数组都是内存的别名,如何分配这些内存由编

译器在编译期间决定

   定义数组的时候必须指定数组长度

   而数组长度是在编译期就必须决定的

 

malloc所分配的是一块连续的内存,以字节为单位,

并且不带任何的类型信息

free用于将动态内存归还系统

void* malloc(size_t size);

void free(void* pointer);

注意:

• malloc实际分配的内存可能会比请求的稍微多一点,但是不

  能依赖于编译器的这个行为

• 当请求的动态内存无法满足时malloc返回NULL

• 当free的参数为NULL时,函数直接返回
   malloc(0) 是不会返回NULL 的 因为NULL代表一种异常 当malloc(0)时 返回一个
合法的指针 只是由于长度是所以即使地址合法 你也不能使用它.

 

你认识malloc的兄弟吗?

void* calloc(size_t num, size_t size);

void* realloc(void* pointer, size_t new_size);

 calloc的参数代表所返回内存的类型信息(数组的长度,和数组元素的大小)

 calloc会将返回的内存初始化为0

 realloc用于修改一个原先已经分配的内存块大小

 在使用realloc之后应该使用其返回值

 pointer的第一个参数为NULL时,等价于malloc

动态内存分配是C语言中的强大功能

程序能够在需要的时候有机会使用更多的内存

malloc单纯的从系统中申请固定字节大小的内存

calloc能以类型大小为单位申请内存并初始化为0,有类型 ,

realloc用于重置内存大小

 

栈 


栈是现代计算机程序里最为重要的概念之一

栈在程序中用于维护函数调用上下文,没有栈就没有函数,没有局部变量

 

栈保存了一个函数调用所需的维护信息

 函数参数,函数返回地址

 局部变量

 函数调用上下文

 

堆:  

为什么有了栈还需要堆?

 栈上的数据在函数返回后就会被释放掉,无法传递到函数外

部,如:局部数组

堆是程序中一块巨大的内存空间,可由程序自由使用

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

有效

静态存储区
程序静态存储区随着程序的运行而分配空间,直到程

序运行结束

 在程序的编译期静态存储区的大小就已经确定

 程序的静态存储区主要用于保存程序中的全局变量和

静态变量

与栈和堆不同,静态存储区的信息最终会保存到可执行程序中

栈,堆和静态存储区是C语言程序常涉及的三个基本内

存区

栈区主要用于函数调用的使用

堆区主要是用于内存的动态申请和归还

静态存储区用于保存全局变量和静态变量

 

野指针:

 

1野指针通常是因为指针变量中保存的值不是一个合法(合法地址指的是 :栈空间地址,和堆空间地址。)的内存地址而造成的

2野指针不是NULL指针,是指向不可用内存的指针(多人使用)

3NULL指针不容易用错,因为if语句很好判断一个指针是不是NULL

4、无法判定这个指针是不是野指针。

野指针的来由:

1、局部指针变量没有被初始化(大到程序会奔溃,小到可能不发生后果,所以视为不越界,无问题,所以后果是不可预料的。)

2、使用已经释放过后的指针(写了其他人用的空间,不可释放两次,释放两次会出错。)

3、指针所指向的变量在指针之前被销毁

违法操作:

   结构体成员指针未初始化

   没有为结构体指针分配足够的内存

   内存分配成功,但并未初始化 。(直接输出 %s

   数组越界(注意数组越界,否则引起很大的出入。)

   内存泄露分析

   多次指针释放(谁申请,谁释放/)

   使用已释放的指针(错误);

CSDN :

以下是我对内存堆空间申请的一些个人理解:

malloc开辟空间时,并不是开辟你指定的大小,而是开辟一个内存的块信息头,再加上你的申请的空间对齐后的大小。成功后返回的指针,是你能使用的部份的起始地址,而这个起始地址减去内存块的信息头,才是真正你的开辟空间的起始。在free时,会根据你传入的指针去获取对应内存块的信息,并把释放后的内存放入空闲内存的链表,你第一次free时,修改对应存储的信息。当你第二次free时,在判断该内存块的状态,取对应的信息时,就会出错。所以会出现你遇见的问题。如果你在free时,释放的内存不是malloc返回时的地址指向空间,也会出现错误。你可以试一下char *p = malloc(20); free(p+sizeof(int));

四条规则:

  1、动态申请操作必须和释放操作匹配,防止内存泄露和多次释放

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

  3malloc申请了内存之后,应该立即检查指针值是否为NULL,防止使用值为NULL的指针

  4、牢记数组的长度,防止数组越界操作,考虑使用柔性数组

 

                     面向过程的程序设计

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

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

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

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

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

声明和定义:

1、程序中的声明可理解为预先告诉编译器实体的存在,如:变量,函数,等等

2、程序中的定义明确指示编译器实体的意义

函数参数:

函数参数

1、函数参数在本质上与局部变量相同,都是在栈上分配空间

2、函数参数的初始值是函数调用时的实参值

 

函数参数

函数参数的求值顺序依赖于编译器的实现!!!

C语言中大多数运算符对其操作数求值的顺

序都是依赖于编译器的实现的!!!

int i = f() * g();

顺序点:

1、程序中存在一定的顺序点

2、顺序点指的是执行过程中修改变量值的最晚时刻

3、在程序达到顺序点的时候,之前所做的一切操作必须反映

 4、到后续的访问中

 5C语言会默认没有类型的函数参数为int

小结:

 C语言是一种面向过程的语言

函数可理解为解决问题的步骤

函数的实参并没有固定的计算次序

顺序点是C语言中变量改变的最晚时机

函数定义时参数和返回值的缺省类型为int

 

 

可变参数函数:

C语言中可以定义参数可变的函数

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

va_list变量与va_start (args,n), va_end(args)va_arg(args, typename)配合使用能够访问参数值。

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

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

可变参数宏无法判断实际存在的参数的数量

可变参数宏无法判断参数的实际类型

   警告:

      va_arg中如果指定了错误的类型,那么结果是不可预测的。(会出错至少的。)

可变参数的设计总结:

   可变参数是C语言提供的一种函数设计技巧

  可变参数的函数提供了一种更方便的函数调用方式

  可变参数必须顺序的访问

  无法直接访问可变参数列表中间的参数值

活动记录

活动记录是函数调用时用于记录一系列相关信息的记录

 临时变量域:用来存放临时变量的值,如k++的中间结果

 局部变量域:用来存放函数本次执行中的局部变量

 机器状态域:用来保存调用函数之前有关机器状态的信息,包括

各种寄存器的当前值和返回地址等;

 实参数域:用于存放函数的实参信息

 返回值域:为调用者函数存放返回值

参数入栈:

既然函数参数的计算次序是依赖编译器实现的,

那么函数参数的入栈次序是如何确定的呢?

 

调用约定

1、当一个函数被调用时,参数会传递给被调用的函数,而返回值会被返回给调用函数。函数调用约定就是描述参数

 

 是怎么传递到栈空间的,以及栈空间由谁维护。

参数传递顺序

     从右到左依次入栈:__stdcall__cdecl__thiscall

     从左到右依次入栈:__pascal__fastcall

调用堆栈清理

     调用者清除栈。

     被调用函数返回后清除栈

函数调用是C语言的核心机制

     活动记录中保存了函数调用以及返回所需要的一切信息

      调用约定是调用者和被调用者之间的调用协议,常用于不同开发者编写的库函数之间

调用方法:

int __stdcall add(int a,int b)

 {

 return a+b;

 }

递归概述

  递归是数学领域中概念在程序设计中的应用

  递归是一种强有力的程序设计方法

  递归的本质为函数内部在适当的时候调用自身

 

C递归函数有两个主要的组成部分:

   递归点– 以不同参数调用自身

   出口– 不在递归调用

C语言中的递归函数必然会使用判断语句

  递归函数在需要编写的时候定义函数的出口,否则栈会溢出

   递归函数是一种分而治之的思想(会把问题分解掉,使得问题变小,更容易做。)

思考题

编写一个函数打印一个字符数组的全排列?

 

函数设计原则:

   1、不要在函数中使用全局变量,尽量让函数从意义上是一个独立的功能模块

   2、参数名要能够体现参数的意义

   void str_copy (char *str1, char *str2);

   void str_copy (char *str_dest, char *str_src);

   3、如果参数是指针,且仅作输入参数用,则应在类型前加const,以防止该指针在函数体内被意外修改

   void str_copy (char *str_dest, const char *str_src);

   4、不要省略返回值的类型,如果函数没有返回值,那么应声明为void类型

  5、在函数体的“入口处”,对参数的有效性进行检查,对指针的检查尤为重要

   6、语句不可返回指向“栈内存”的“指针”,因为该内存在函数体结束时被自动销毁。

   7、函数体的规模要小,尽量控制在80行代码之内

  8、相同的输入应当产生相同的输出,尽量避免函数带有“记忆”功能

   9、避免函数有太多的参数,参数个数尽量控制在4个以内

 10、有时候函数不需要返回值,但为了增加灵活性,如支持链式表达,可以附加返回值

     char s[64];

     int len = strlen(strcpy(s, android));

11、函数名与返回值类型在语义上不可冲突

     char c

    c = getchar();// 返回int 类型。我们认为是char 类型,所以,我们要避免这样写函数名;

    if(EOF == c)

   {

     //

    }

面试题

#include 

 

void main()

{

    int TestArray[5][5] = { {11,12,13,14,15},

                            {16,17,18,19,20},

                            {21,22,23,24,25},

                            {26,27,28,29,30},

                            {31,32,33,34,35}

                          };

    int* p1 = (int*)(&TestArray + 1);

    int* p2 = (int*)(*(TestArray + 1) + 6);

 

    printf("Result: %d; %d; %d; %d; %d\n", *(*TestArray), *(*(TestArray + 1)), 

                                           *(*(TestArray + 3) + 3), p1[-8], 

                                           p2[4]);

 }

面试题

#include

 

void main()

{

    char* p = "hello world!";

    int a = (int)p;

    short s = 'c';

 

    printf("%c\n", (long)(*((int*)p)));

    printf("%s\n", a);

    printf("%s\n", &s);

}

输出:

                                                                                                                                                                                                                               (注课程源于国嵌C语言学习,及个人学习体会)

 

你可能感兴趣的:(C语言)