分享

c语言

 袁小波 2014-03-08
标识符

合法的标识符是由字母,数字,下划线组成,并且第一个标识符必须为字母或是下划线,不可以是数字。

标识符分为关键字、预定义标识符、用户标识符三类。

关键字都是用小写英文字母表示,共有31个,不允许用户将其用做变量名使用;

预定义标识符被用做库函数名和预编译命令,比如:define scanf printf include;

用户标识符是程序员根据自己的需要定义的一类标识符。

数据类型及占位

基本数据类型:char(1)、int(4) 、long(4)、short(2)、double(8)、float(4)、bool(1)

结构类型:struct(累加)、union(共享)、int iArr[10] (40)

指针类型:int *p(4)、char **p(4)、void *p(4)

空类型:void

结构体的占位大小是由内部成员占位大小累加决定的,共用体(联合体)的大小是由占位最大的内部成员决定的。

字符常量

字符常量是用一对单引号括起来的一个字符,如‘a’、‘=’。

C语言中一个字符占用一个字节,但该字节中存放的并非此字节本身,而是该字符所在机器采用的字符集的代码。

大多数系统采用ASCII代码字符集,如‘a’的ASII值为97,所以字符常量可以像整数一样参与运算。

转义字符

C语言中还有一类转义字符,以反斜杠‘\’开始。转义字符分为三种:一般转义字符、八进制转义字符和十六进制转义字符。

一般转义字符:用来表示那些不能显示的ASCII字符, 比如‘\0’,‘\t’等。

八进制转义字符:由反斜杠‘\’和随后的1~3个八进制数字构成的字符序列(如果你愿意可以在八进制数字前面加上一个0来表示八进制转移字符)。如‘\144’和‘\101’分别表示‘a’和‘A’。

十六进制转义字符:由反斜杠‘\’和字母x(或X)及随后的1~2个十六进制数字构成的字符序列。如‘\x41’和‘\X61’分别表示字符‘A’和‘a’。

字符串常量

字符串常量是用双引号括起来的字符序列,字符串常量占用的内存字节数等于字符串中字节数加1,增加的一个字节中存放‘\0',这是字符串结束的标志。

变量的作用域

C语言中所有的变量都有自己的作用域,按照作用域的不同,变量可以分为局部变量和全局变量。

局部变量:只在函数内部有效,其作用域仅限于函数内,离开函数后再使用这种变量就是错误的;

全局变量:全局变量不再属于哪个函数,它的有效范围是从该变量定义的位置处开始直到源文件的结束。

在一个函数之前定义的全局变量,在函数内无需说明就可以使用。 但是如果在该函数内定义了一个与之前定义的全局变量同名的变量, 那么这个同名局部变量会在函数内部屏蔽全局变量的影响。

int i = 1;
void main()
{
    int i = i;
    printf(“%d\n”,i);
}
main()内部定义了一个和全局变量相同的局部变量i,局部变量会屏蔽全局变量的影响。那么,局部变量i和main()外的i无关,是一个未定义的值。

变量的存储类型

存储类型是指变量占用内存空间的方式,分为静态存储和动态存储。

静态存储的变量在变量定义时就为其分配了固定的存储单元并一直保持不变,直至整个程序结束, 静态变量和全局变量属于静态存储方式;

动态存储的变量在程序的执行过程中,仅当需要时才临时性的分配存储单元, 并且在使用完毕,动态存储的变量会立即释放,函数中定义的局部变量属于动态存储方式。



静态变量分为静态局部变量和静态外部变量,静态局部变量是在函数内部定义的, 静态外部变量和全局变量一样都是在函数外部定义的。

静态局部变量:
1、静态局部变量属于静态存储类别,在静态存储区内分配存储单元,在整个程序的运行期间都不释放;
2、静态局部变量是在编译时赋初值的,即只赋初值一次;
3、如果定义局部变量时不赋初值,对静态变量来说,编译器会自动赋初值0;
4、虽然静态局部变量在函数调用结束后仍然存在,但其它的函数是不能引用它的。

void f()
{
    static int num = 4;
    printf("%d",num);
    num++;
}
void main()
{
    for(int i=0;i<3;i++)
    {
        f();
        printf("\n");
    }
}
程序的输出结果为:4 5 6,如果将关键字static去掉,程序的输出结果为:4 4 4。

静态外部变量:
1. 有些外部变量不希望被其他文件所引用,可以在变量定义时用static修饰,表示该变量为静态外部变量。
2. 静态外部变量只能在本文件中被引用,这种限制条件提高了程序的健壮性和安全性。

//file1.c
static int a,b;

//file2.c
extern int a,b;
void func(int x)
{
    x = a+b;
}
文件file2.c中声明了两个外部变量,并在函数func()中去使用它们。 因此编译器在编译程序时会到其它文件中去找,结果发现file1.c中的两个变量被static所修饰,因此不能使用, 于是程序就会抛出编译错误

运算符的优先级

运算符 解释 结合方式
() [] -> . 括号,函数参数,数组下标,结构体成员访问 由左向右
! ~ ++ -- + - * & (type) sizeof 逻辑否,按位否,自增,自减,正负 由右向左
* / % 乘 除 取模 由左向右
+ - 加 减 由左向右
<< >> 加 减 由左向右
< <= >= > 小于 小于等于 大于等于 大于 由左向右
== != 等于 不等于 由左向右
& 按位与 由左向右
^ 按位异或 由左向右
| 按位或 由左向右
&& 逻辑与 由左向右
|| 逻辑或 由左向右
: 条件 由右向左
= += -= *= /= &= ^= |= <<= >>= 各种赋值 由右向左
, 逗号 由左向右
位运算

位运算符一共有6个,分别是‘&’、‘|’、‘^’、‘~’、‘<<’和‘>>’。

‘~’是单目运算符,其优先级高于其它5个双目运算符。

移位运算符的优先级要高于双目的按位运算符。

双目按位运算符的优先级从高低为:‘&’、‘^’、‘|’。

自增自减运算符

使用自增自减运算符要注意一下几点:
1、该运算符只能用于变量,不能用于常量和表达式。
++5;
这种写法是错误的。
2、应该避免在同一个表达式中对同一个变量多次使用该运算符, 因为表达式的求值顺序不确定完全取决于编译器,会产生意想不到的结果。
(i++)+(i++)+(i--);
设i的值为10,因为3个表达式的求值顺序并不确定,表达式的值可能是32也可能是30。正确的做法是分割这些子表达式为独立的表达式。
3、应避免将多个包含该运算符的表达式用于实参。在调用函数时,实参的求值顺序标准C并没有明确规定。
printf("%d,%d",++i,i++);
此函数的输出值是不确定的,与编译器有关。
4、前缀自增、自减运算符的优先级和一元的正号、负号优先级相同,且是右结合的。 后缀自增、自减运算符的优先级比一元的正号、负号的优先级高,且是左结合的。
- --i;相当于-(--i);
-i--;相当于-(i--);

sizeof运算符

sizeof的使用注意以下几点:
1、sizeof可以用于数据类型和表达式(用于表达式时可以没有括号), 不可以用于函数类型、不完全类型或位字段。

2、sizeof的优先级为2级,比算术运算符的优先级高, 如果采用不带括号形式的使用方法要特别注意。

3、表达式进行sizeof运算后的结果就是该表达式值的类型值。运算结果在程序编译时决定。
 int i;sizeof(i+1);
等价于
sizeof(int);
类型转换

C语言中的类型转换分为两种:自动类型转换和强制类型转换。

以下几种情况会发生自动类型转换: 
1 算术表达式或逻辑表达式中操作数的类型不相同,此转化称为算术转换; 
2 赋值运算符右侧表达式类型和左侧的变量不匹配; 
3 函数调用中的实参和形参类型不匹配; 
4 return语句中表达式类型和函数返回值类型不匹配。

算术转换多用于二元运算符的运算对象上,包括算术运算符和关系运算符。 一般都是提升的,不会产生数据丢失;

赋值中的转换原则:把赋值运算符右侧表达式的类型转换为左侧变量的类型, 此转换有时是提升的有时是截断的;

return类型与函数返回值类型不匹配时有时会转化为返回值类型, 有时程序无法通过编译,所以强烈建议return语句表达式类型与返回值类型一致;

实参和形参类型不匹配时要么是引起精度损失,要么是编译出错, 建议大家在使用函数时,要遵守参数列表中对应的实参与形参类型应当相同的原则。

使用强制类型转换符注意几点:

1 类型转换运算符为一元运算符,优先级高于二元运算符;

2 强制类型转换运算只对其后紧跟着的变量有效。
 int f+d;
已知f为float型,d为double型,这样操作后只会将f转换为int型。如果想让f+d的结果转换为为int型,应该
 int(f+d);
3 强制类型转换只是改变了某一表达式中变量的类型, 而不是真正改变了变量的类型,后面遇到此变量参与运算时,它的类型依然是原来的。

数组的定义及初始化

在C语言中,一维数组的定义方式如下: 
类型说明符 数组名[常量表达式]; 
其中,类型说明符用来说明数组元素的类型;数组名是用户自定义的标识符; 常量表达式中不能含有变量,因为数组一旦被定义,它的大小就已经被划定,无法动态的改变, 所以数组的长度也就不能是一个变量。

一维数组的初始化方式有以下几种: 
1、定义数组时就给数组中的每个元素赋初值,可以将数组中元素的值用一个大括号括起来, 每个元素之间用逗号隔开。

  int a[8]={34,25,18,99,5,18,60,1};
也可以只给部分元素赋值。
  int a[8]={34,25,18};
上述语句仅仅对数组中的前3个元素进行了赋值,后面剩余的元素则默认为0。
2、在声明数组时若对其中全部元素均赋初值,也可以不指定数组的具体长度, 编译器会根据赋值的情况为数组自动指定合适的长度。

  int a[]={34,25,18,99,5,18,60,1};

编译器会默认为数组的长度为8。
二维数组的定义: 
类型说明符 数组名[常量表达式1][常量表达式2];

二维数组初始化: 
1、直接将二维数组中的所有元素按照其实际排列顺序进行赋值;

  int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
这种初始化方法的可读性很差,因为数组之间的行列关系非常模糊。
2、将二维数组的每行分别用大括号括起来,然后分行给二维数组赋值;

  int a[3][4]={{0,1,2,3}
             {4,5,6,7}
             {8,9,10,11}};
3、可以选择性的对部分数组元素进行赋值;

  int a[3][4]={{1}{4}};
上面的语句只对每行首列进行赋值,其它元素默认赋值为0。这种初始化方式适合容量比较大的数组。
4、如果在初始化时对二维数组里的全部元素进行赋值,则可以省略第一维的长度,但是第二维的长度不能省略。

  int a[][3]={0,1,2,3,4,5,6,7,8,9,10,11};
字符数组初始化 
字符数组只是数组的一种特殊类型,因此初始化字符数组的方法和普通数组相同。

字符数组还有其他的方法来初始化:可以将整个字符串用双引号括起来再放到大括号中。

  char str[] = {"Good Luck!"};
大括号也可以被省略。
  char str[] = "Good Luck!";
越界访问

C语言不提供对于数组边界检查,因此在引用数组元素时小心访问越界。
一旦越界,后果不堪设想!
void main()
{
    int a[5] = {1,2,3,4,5};
    int b[5] = {6,7,8,9,10};
    printf("%d\n",b[6]);
程序的运行结果是2。尽管程序没有崩溃但是发生了逻辑错误。如果非法位置上的值是比较敏感的,后果是非常严重的,在程序设计使应该杜绝这种情况的发生。

scanf函数


scanf函数的一般形式是: 
scanf(格式控制字符串”,地址列表);

scanf("%d %d %d",&a,&b,&c);
使用此函数要注意一下两点: 
1、如果输入空格则认为输入字符串结束,空格后的字符将作为下一个输入项; 
2、如果输入的数据是double型,要在类型前面加字符'l'做修饰符。

double f;
scanf("%f",&f);
改为
 scanf("%lf",&f);

宏定义又称为宏代换、宏替换,简称“宏”, 使用宏可提高程序的通用性和易读性,减少不一致性,减少输入错误和便于修改。

使用宏要注意以下问题:
1、宏名一般用大写; 
2、宏定义末尾不加分号; 
3、宏定义写在函数的花括号外边,作用域为其后的程序,通常在文件的最开头。可以用#undef命令终止宏定义的作用域; 
4、宏定义允许嵌套;

 #define WIDTH 80
 #define LENGTH (WIDTH+40)
5、函数调用在编译后程序运行时进行,并且分配内存。 宏替换在编译前进行,不分配内存。
const只读变量修饰符

const用来修饰变量而非常量,但是变量的值不可以修改,使用const在一定程度上可以提高程序的安全性和可靠性。

const变量有两种定义方式:
1、const在类型的前面

 const int var =100;
2、const在类型的后面
 int const var =100;
两种定义的方式没有本质区别。
数组与指针

指向数组的指针与数组名

指向数组的指针指向数组的首元素,即指针变量保存数组首地址。
数组名有两重意思: 
1、数组的名称; 
2、&a[0]也就是数组的首地址 
数组名不是指针。

字符指针与字符数组比较
1、赋值方式不同:对字符数组进行赋值时,不可以用整个字符串进行赋值,只能对其中的某个字符赋值,下面的赋值方式是错误的,

char str[12];str = "Good Luck!";
但是对于字符指针变量来说,却可以采用下面的赋值方式,相当于指针str指向一个字符串常量。
char *str;str="Good Luck!";
2、地址是否可以改变:指向字符数组的字符指针和字符数组名都表示数组的首地址, 但是字符指针可以通过一定的计算(自增或者自减)来改变自身的指向,而字符数组名却不可以。

3、在定义一个字符数组时,编译器就为其分配内存单元。而定义一个字符指针变量时, 编译器给指针变量分配内存单元,但并不知道该指针变量具体指向哪个字符串,即指针变量存放的地址不确定。

字符串与字符数组

字符串是常量,存储在程序的常量区,字符串中的字符不能修改,任何试图修改字符串中字符的操作都会引起运行时错误; 字符数组是变量,字符数组中的字符可以修改。

野指针

“野指针”不是NULL指针,是指向“垃圾”内存(不可用内存)的指针。

人们一般不会错用NULL指针,因为用if语句很容易判断。 但是“野指针”是很危险的,if无法判断一个指针是正常指针还是“野指针”。 有个良好的编程习惯是避免“野指针”的唯一方法。

野指针的成因主要有三种:
1、指针变量没有被初始化。

2、指针被free或者delete之后没有置为NULL,让人误以为该指针是个合法的指针而继续使用。

3、指针操作超越了变量的作用范围。比如返回指向栈内存的指针,由于栈内存在函数结束时会被释放,此时使用该指针就会产生意想不到的后果。

函数指针

函数指针是指向函数的指针变量。函数指针的声明方法:
函数类型 (*指针变量名) ();

指向函数的指针变量所指向的地址其实就是函数中第一条指令的地址。

调用该函数时除了通过函数名来调用它以外,还可以通过指向该函数的指针变量来调用。

有一点要注意:一个指向函数的指针其初始值不能为空,因为它在使用之前必须被赋予一个真实的函数地址。

结构体

结构体变量的定义有三种形式:
1、先声明结构体类型再定义结构体变量;
struct 结构体类型名
{
    数据类型名1 成员名1;
    数据类型名2 成员名2;
    …… ……
    数据类型名n 成员名n;
};
struct 结构体类型名 结构体变量名;
2、声明类型的同时定义变量;
struct 结构体类型名
{
    数据类型名1 成员名1;
    数据类型名2 成员名2;
    …… ……
    数据类型名n 成员名n;
}变量名列表;
3.直接定义结构体变量而没有结构体类型名。
struct 
{
    数据类型名1 成员名1;
    数据类型名2 成员名2;
    …… ……
    数据类型名n 成员名n;
}变量名列表;
结构体变量的初始化
在定义结构体变量时就可以对其进行初始化,若没有初始化,编译器会给每个成员一个默认值。

位域

位域是把一个字节的二进制位划分为不同的区域,并说明每个区域的位数。

位域从本质上说就是一种结构类型,不过其成员是按二进制位分配的,所以位域的使用和结构成员的使用相同。

使用位域有以下优势:
1、每个区域都有一个域名允许程序员按域名进行操作;
2、使用位域结构类型可以节省存储空间。
struct byte_struct
{
    int x:8;
    int y:2;
    int z:6;
}field;
field为byte_struct类型变量,共占用2个字节。其中x占8位,y占2位,z占6位。
共用体

结构体在内存中采用线性存储,存储空间大小大于(由于操作系统的字节对齐)等于内部成员之和; 共用体成员存储时采用覆盖技术,共享存储空间。

共用体变量所占内存长度等于成员变量中所占存储空间最大的那个,由于成员被分配在同一段内存空间中, 共用体变量的值是最后一次存放成员的值,就是说在某一时刻只有一个成员变量起作用。

struct 
{
    int i;
    float j;
}sum;
union
{
    int i;
    float j;
}sum;
int型数据占4个字节的内存,float型数据占4个字节内存。结构体变量所占内存大小为i和j所占内存之和,即8个字节;共用体变量sum所占内存大小为4个字节。
内存对齐

内存对齐(或字节对齐):内存空间里的数据按照一定的对齐规则在内存空间中排列,而不是一个一个顺序存放。

字节对齐的细节和具体编译器实现相关,但一般而言内存对齐满足三个准则:
1、结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
2、结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节;
3、结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。

struct 
{
    int a;
    short b;
    double f;
    char c;
    short d;
}stu;
void main 
{
    int i = 0;
    i = sizeof(stu);
    printf("%d\n",i);
} 此函数的输出结果是20。
枚举

如果遇到变量取值能够列举的情况,就可以使用枚举数据类型。 枚举类型的定义形式和结构体和共用体类似.
enum 枚举名{枚举值表};
枚举类型看似构造类型,但实质上是一种基本类型,因为枚举元素实际上是常量,枚举常量的数据类型是int型。
在默认情况下枚举常量的值是在枚举值列表中的顺序号,依次为0、1、2……。

enum colour{
    red, //red = 0
    green, //green = 1
    blue //blue = 2
};
也可以在定义枚举类型时给枚举常量指定值。
enum colour{
    red = 4,
    green = 7,
    blue = 1
};
如果只对一个枚举常量赋值,没有给后续的枚举常量赋值,那么这些常量会被赋予后续的值。
enum colour{
    red,
    green = 20,
    blue
};
在这个声明中,red默认为0,对green赋值为20,那么blue就取20的下一个值21。
typedef

typedef被称为用户自定义类型,其实不是一种用户定义的新的数据类型,只是某种数据类型的别名。

使用typedef和#define指令都可以定义自己命名的数据类型,不过二者仍然具有以下几点区别:
1、typedef只针对数据类型,不针对值。#define则即可以针对数据类型也可以针对值;

2、typedef是对已有数据类型的彻底“封装”,在声明之后就不可以再往其中增添任何代码。 #define只是简单的文本替换,定义之后仍然可以增加一些合法的代码;

3、在定义几个连续的变量声明中,用typedef定义的变量可以保证所有的变量都为同一数据类型,但是#define定义的类型则无法保证。

typedef char *Pchar;
Pchar p1,p2;//p1、p2都为字符指针类型 
#define Pchar char* 
Pchar p1, p2;//p1为字符指针类型,p2为字符型。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多