[关闭]
@OrionPaxxx 2017-03-12T09:13:15.000000Z 字数 15710 阅读 1493

c

c学习笔记(c primer plus(第五版)中文版)

预编译代码的库文件

预编译代码文件是编译器进行了去注视、头文件包含和宏替换后的源代码文件,后缀为.i;它有助于查看宏替换后的程序代码,分析程序代码错误;

在VC中只需在C/C++-->Command Line中追加上/P命令参数就可以了;



这样Compile某个.cpp文件后,就能生成对应的预编译文件了

有关#include

#include甚至不是c语言的语句#符号表明这一行是在编译器接手之前由C预处理器处理的语句(预处理器指令,详情了解‘c预处理器和c库’

标准形式如:int main(void), void haha(void)

int指明mian函数的返回值类型是一个整数,void表示不接受任何参数。

非标准形式如:
mian()
void main()
应尽量使用标准形式 

标识符的长度

c99标准允许一个标识符最多可以有63个字符,不会识别额外的字符,
例如:
如果一个系统最大字符数为8,则shakespare和shakespencil奖被识别为同一个名字

前缀:

16进制:0x或者0X,%x
8进制:0,%0
10进制:%d

整数溢出:达到最大值时,溢出到起始点

在数值大于有符号类型最大值时,对无符号变量使用%d说明符会导致显示负值。

可移植的类型:inttypes.h

int16_t:16位有符号整数类型
uint32_t:32位无符号整数类型
intmax_t:最大有符号整数类型
uintmax_t:最大无符号整数类型

最小长度类型(minimum width type)

int_least8_t:可以容纳8位有效符号数的那些类型中最小的一个

fastest minimum width type(最快最小长度类型)

int_fast8_t:系统中对8位有符号数而言最快的的整型类型的别名

浮点值的上溢(overflow)和下溢(underflow)

上溢:printf输出inf或者infinity
下溢:0.1234e-10除以10得到0.0123e-10,损失以为有效数字
还有一个特殊的浮点值:NaN(not a number):例如asin()输入参数大于一时
,printf将此值显示为nan或者NaN

浮点数舍入误差:将一个大数加一再减去原数可能出现其他结果

C99标准支持复数和虚数类型

三种复数类型:

float_Complex
double_Complex
long double_Complex

三种虚数类型

float_Imaginaty
double_Imaginary
long double_Imaginary

如果包含了complex.h,则可用complex代替_Complex,imaginary代替_Imaginary

基本数据类型的关键字:

整型类型:int,long,short,unsigned int,unsigned long int,unsigned short int
浮点类型:float,double,long double
字符型:char,signed char,unsigned char
其他:_Bool, _Complex , _Imaginary

不匹配的转换(在printf的格式字符串)

  1. #include<stdio.h>
  2. int main(void)
  3. {
  4. int a=4;
  5. int b=5;
  6. float c=5.0;
  7. printf("%d\n",a,b);
  8. printf("%d %d\n",a);
  9. printf("%d %f\n",c,a);
  10. getchar();
  11. }

输出结果为:
4
4 20451598
0 0.000000
运行程序不会报错,%d不会将float转换为int,%f不会将int转换为float。再如:以%u输出一个负数会得到一个错误的结果。(但计算过程中会按照自动类型转换规则转换)。
一个好的编程习惯是让说明字符和要输出的值做到以下匹配:
1.数据类型匹配
2.字节数匹配(printf从堆栈中把数读出来时,会按照说明符指出的字节数读取,如果字节数不匹配可能导致只读取数据的部分或者把其他数据部分一同读入)

示例程序

  1. #include<stdio.h>
  2. void fuck(void);
  3. int main(void)
  4. {
  5. int haha;
  6. scanf("%d",&haha);
  7. printf("%c",haha);
  8. getchar();
  9. fuck();
  10. }
  11. void fuck(void)
  12. {
  13. getchar();
  14. }

或者

  1. #include<stdio.h>
  2. double haha(double n,float m);
  3. int main(void)
  4. {
  5. ...;
  6. ...;
  7. return 0}
  8. double haha(double n,float m)
  9. {
  10. mul=n*m;
  11. return mul;
  12. }

第四章:字符串和格式化输入输出

数组:同一类型的数据元素的有序序列

例子:

  1. char name{[40}];//声明创建一个有40个存储单元(或元素)的数组,每个单元可以存储一个char类型的值

scanf()读取输入时,会在第一个tab,blank 或者newline处停止读取,C使用诸如gets读取输入函数来处理一般的字符串。

sizeof 和 strlen(包含在string.h头文件中)

sizeof:以字节为单位给出数据大小(查看数据占了多少个字节)
strlen:以字符为单位给出字符串长度

C没有为字符串定义专门的变量类型,而是把他存储在char数组中

#

  1. #define NAME value//没有分号,一个好的习惯是用大写来表示常量,用小写来表示变量

const修饰符

把一个变量声明转化换成常量声明

  1. const int haha=123;//使哈哈成为一个只读值

limits.h头文件和float.h 头文件

scanf函数

1.如果使用scanf来读取之前讨论过的某种基本变量类型的值,在变量前加&
2.如果使用scanf来把一个字符串读进一个字符数组中,不加&
3.scanf函数需要严格按照格式字符串的形式输入如:

  1. scanf("%d,%d",ha,ha);//键盘需键入中间的逗号
  2. scanf("%d%d",ha,ha);//键盘输入需用空白符将2 个数据隔开

运算符,表达式和语句

示例:while(++a<18)

不要将2个运算符的优先级和求职顺序混淆如:

  1. y=2;
  2. n=3;
  3. nextum=(y+n++)*6;

n的值为:30(先使用再增加)

每一个表达式都有一个值

表达式
-4+6 2
c=3+8 11
5>3 1
6+(c=3+8) 17

(不建议使用合法但是奇怪的表达式)

指派运算符

  1. mice=1.6+1.7;
  2. mice=(int)1.6+(int)1.7;

副作用和顺序点

p115

stdbool.h头文件

c99提供一个stdbool.h头文件,包含这个头文件可以使用bool来代替_Bool,并且把true和false定义为1和0的符号常量,在程序中包含这个头文件可以写出与C++兼容的代码,因为C++把bool,true和false定义为关键字。

运算符的优先级

p137

for循环

for(A;B;C)
A:初始化,在循环开始时执行一次
B:判断条件,在每次执行循环前对其进行求值,为假时退出
C:在每次循环结束时进行计算
可以让或多个表达式为空但是不能省略逗号
事实上,A,B,C几乎可以是任何表达式,但要记住他们执行的时间点,以及表达式B为假时退出循环。

逗号表达式

常在for循环中使用,拓展for循环的灵活性。逗号是一个顺序点(从左到右进行计算),其值为最后一个表达式的值。

do while 语句

  1. do
  2. {
  3. ...
  4. }while(...);

循环体中只有一个表达式时可以省略大括号

数组(P151)

scanf 的返回值为接受输入值的个数(p155)

第七章C控制语句:分支和跳转

一个例子

  1. while
  2. (ch=getchar())
  3. !='n')
对上述程序说明:计算机首先7调用getchar函数,然后将其返回值赋给ch,而赋值表达式的值就等于表达式左侧数的值。

getchar和putchar

  1. ch=getchar();
  2. putchar(ch);
getchar():从键盘读入单个字符,不接受参数,其返回值为接受到的字符,ch=getchar()的值(为一个赋值表达式的值)为ch的值,也就是getchar的返回值。

ctype.h(p158)

例子:如果isalpha()函数的参数是个字母,则它返回一个非零值

如果没有花括号,else将与和他最近的一个if相匹配

iso646.h头文件:

&& 同and
||同or
!同not

条件运算符:惟一的一个三目运算符

expression1 ? expression2 : expression3
如果expression1为真,则整个表达式的值和expression2的值相同,如果expression1为假,则整个表达式的值和expression3的值相同

switch 语句

  1. switchinteger expression or integer
  2. {
  3. case constant1:
  4. statement;
  5. case constan2:
  6. statement;
  7. ...
  8. ...
  9. default:statement;
  10. }

switch 后的括号里和case后的表达式只能是整型常量(或字符型)和整型表达式。
如果没有break语句每一个case都将被扫描,而每一个匹配的case都将被执行。如果没有匹配的case,将执行default后的语句,如果没有default,程序将跳转到switch语句之后的语句。
可以使用多重标签:

  1. case 1
  2. case 2statement;

goto语句:通常不需要使用它

  1. goto part2;
  2. ...
  3. ...
  4. part2:statement;

第八章:字符输入/输出和和输入确认

EOF(p202)

判断是否到达文件末尾:

  1. while(
  2. (ch=getchar())!=EOF
  3. )
  4. putchar(ch);
计算机术语,缩写通常为EOF(End Of File),在操作系统中表示资料源无更多的资料可读取。资料源通常称为档案或串流.在C语言中,或更精确地说成C标准函数库中表示文件结束符(end of file)。在while循环中以EOF作为文件结束标志,这种以EOF作为文件结束标志的文件,必须是文本文件。在文本文件中,数据都是以字符的ASCII代码值的形式存放。我们知道,ASCII代码值的范围是0~127,不可能出现-1,因此可以用EOF作为文件结束标志。

NULL

在C语言中它是一个无类型指针,并且值为0。NULL的出现是一种约定俗成,事实上它不是C语言中的关键字;把一个指针赋值为NULL,通常的说法是“将指针悬空”。这样,指针就无法再进行任何数据访问了。

参见:百度百科--NULL

  1. #include<stdio.h>
  2. int main(void)
  3. {
  4. int * ar;
  5. ar=NULL;
  6. printf("%p",ar);
  7. }

上面程序输出为:

00000000

重定向

第九章: 函数

多源代码文件程序的编译(p241)

在linux下,下面的命令将2个文件编译在一起并生成可执行文件a.out

头文件的使用

将常量和函数原型放在头文件中,
#include“xxxx.h"中双引号表示被包含的文件位于当前工作目录下

按照教材上的例子,一个c文件存放main(),另一个c文件存放函数定义,再用一个头文件存放常量定义和函数原型(c文件中要将头文件#include进去)

地址运算符:&

间接(取值)运算符:*

  1. z=*x //将x指向的赋给z

指针

改变调用函数中的变量

  1. #include<stdio.h>
  2. void change(int u,int v);
  3. int main(void)
  4. {
  5. int x=1;
  6. int y=2;
  7. printf("x=%d,y=%d,&x=%x,&y=%x\n",x,y,&x,&y);
  8. change(x,y);
  9. printf("x=%d,y=%d,&x=%x,&y=%x\n",x,y,&x,&y);
  10. }
  11. void change(int x,int y)
  12. {
  13. int temp;
  14. temp=x;
  15. x=y;
  16. y=temp;
  17. printf("x=%d,y=%d,&x=%x,&y=%x\n",x,y,&x,&y);
  18. }

运行上面程序,结果如下:

x=1,y=2,&x=7c7291d0,&y=7c7291d4
x=2,y=1,&x=7c7291ac,&y=7c7291a8
x=1,y=2,&x=7c7291d0,&y=7c7291d4

main()中的值并未得到交换,因为change中的中使用的变量独立于main()中的变量,有独立的地址。问题出现在把执行结果传递个给main()的过程中,结果并未传递给main()。我们可以改变main()调用子函数的方式:
  1. x=change(x,y);
同时在子函数末尾加上:
  1. return x;//return(x);也可以
将x的值传递回去,但是y值依然没有改变!要想main()中x,y的值都改变,就需要用到指针!


指针是一个数值为地址的变量
  1. x = &y;//我们称x指向y,x为指针变量,而 &y 是一个常量。。这是x的值是y的地址

指针是一种新的数据类型
声明指针变量

  1. int * pi ; /* pi 是指向一个整形变量的指针*/
  2. char * pc; /* pc是指向一个字符型变量的指针*/
  3. flaot * a, * b ; /* a 和b是指向浮点变量的指针*/

使用指针在函数见通信

  1. #include<stdio.h>
  2. void change(int * u,int * v);
  3. void main(void)
  4. {
  5. int x=1;
  6. int y=2;
  7. printf("%d,%d\n",x ,y);
  8. change(&x,&y);
  9. printf("%d,%d\n",x ,y);
  10. }
  11. void change(int * u,int * v)
  12. {
  13. int temp;
  14. temp=*u;
  15. *u=*v;
  16. *v=temp;
  17. }
上面程序输出结果为:
1,2
2,1

量有常量和和比那量之分。对于每一个变量,都有与之对应的数据类型/变量名/值和地址等,而一个变量的地址是唯一确定的。
1.理解函数之间的信息传递机制,也就是说要明白函数参数以及返回值是如何工作的
2.因为函数的参数和其他局部变量是函数所私有的,所以在不同函数中声明的同名变量是完全不同的
3.任何函数不能直接访问其他函数中声明的变量(main()也不能),这种操作的局限性有助于保护数据的完整性
4.当确实需要在一个函数中访问其他函数声明的变量时,可以使用指针参数

对于python也具有同样的性质,我们看看下面的程序:

  1. a=12
  2. b=45
  3. print a,b
  4. def exchange(a,b):
  5. temp=a
  6. a=b
  7. b=temp
  8. print a,b
  9. exchange(a,b)
  10. print a,b

其输出结果为:

12 45
45 12
12 45

但是如果用python面向对象编程却能过实现:

  1. class test:
  2. def __init__(self):
  3. self.a=12
  4. self.b=45
  5. print self.a,self.b
  6. def exchange(self):
  7. temp=self.a
  8. self.a=self.b
  9. self.b=temp
  10. print self.a,self.b
  11. def printf(self):
  12. print self.a,self.b
  13. ss=test()
  14. ss.exchange()
  15. ss.printf()

上面程序输出为:

12 45
45 12
45 12

第十章数组和指针

声明数组和初始化

char haha[50];
int haha[]={1,2,3,1,2,1,41,5,2};

对数组使用const方法创建只读数组

#define NUMBER =12;
const int days[NUMBER]={1,2,3,1,5,4};//数值数目少于数组元素数目时,多余的数组被初始化为0.

但是如果不对数组进行初始化,则数组内存储的元素不定。(与存储类有关,之前的所有变量和数组都是制动存储类型,有些存储类型会把没有初始化的变量和数组的存储单元设置为0.

c99新标准允许对可以对指定项目初始化:
  1. int days[6]={[4]=3};

看看下面的示例程序:

  1. #include<stdio.h>
  2. #define NUMBER 10
  3. int main(void)
  4. {
  5. int i=0;
  6. int haha[NUMBER]={31,28,[4]=31,30,31,[1]=29};
  7. for(i;i<=NUMBER;i++)
  8. {
  9. printf("%d %d\n",i+1,haha[i]);
  10. }
  11. }

其输出结果为:

1   31
2   29
3   0
4   0
5   31
6   30
7   31
8   0
9   0
10   0
11   -1964437760
做如下说明:
1.[4]=31,30,31对haha[4],haha[5],haha[6]进行赋值
2.多次初始化,最后一次有效

对数组赋值

:只能对单个元素赋值

多维数组

  1. const int haha[3][23][34];//定义三维数组
下面是一个对二维数组的初始化:
  1. int haha[2][3]={{1,2,3},{3,4,5},{5,6,7,}};

在c99之前放括号[]内只能是整型常量或者整型表达式,而且必须大于0(等于0也不行),下面的语句在c99之前不行:

  1. int n=23;
  2. int haha[n];

指针和数组

数组名是该数组首元素的地址:

  1. haha==&haha[0];

在c中对一个指针加一的结果是对该指针增加一个存储单元;对于数组而言,地址会增加到下一个元素的地址,而不是下一字节,这就是为什么在声明指针变量时必须声明他所指向的对象的类型。

定义指针:
1.指针的数值就是它所指向对象的地址,对于含有多个字节的数据类型,对象的地址
2.在指针变量前使用运算符*可以得到指针指向对象的值
3.对指针加一,等价与等价于对指针的值加上它指向对象的字节大小
  1. dates=&date[0];
  2. date+2==&date[2];//其值为真
  3. *(date+2)==date[2];//其值为真

声明数组参量

在函数原型和函数定义中可以用ar[]代替* ar
在对数组进行操作的函数中,下面4种函数原型等价

  1. int test(int * ar);
  2. int test(int *);
  3. int test(int ar[]);
  4. int test(int []);

由于数组名只包含了数组首地址和数组类型,因此在传递数组参量时常常需要再传递一个代表数组元素个数的整数参量。

在c中ar[i]和*(ar+i),等价,而且不论ar是数组名还是指针变量都能工作,但是只有当ar是一个指针变量时,car能使用
ar++这样的表达式。(不能对数组名进行赋值)

保护传递参数的内容

对诸如int之类的基本数据类型:
传递int数值,起保护作用,传递int指针&int,可以修改相应的值
传递数组参量,只能使用指针,若过要保护数组参量,对形式参量使用const:

  1. int haha(const int ar[]);

区分:const创建数组常量,指针常量和指向常量的指针

  1. const int days[];//定义数组常量
  2. int rates[3]={1,2,3};
  3. const double * pc=rates;
  4. /*定义指向常量的指针,不能用于修改数值,但是诸如rates[1]之类的可以修改数值。可以使用pc++让pc指向其他地址*/
  5. double * const pd=rates;//不能使用pd++使pd指向其他地址
  6. const double * const pf;//不能修改数值,也不能指向其他数值

指针和多维数组

  1. #include<stdio.h>
  2. int main(void)
  3. {
  4. int haha[2][3]={{1,2,3},{4,5,6}};
  5. int i;
  6. int j;
  7. printf("%p\n",haha);
  8. printf("%p\n",haha[0]);
  9. printf("%p\n",&haha[0][0]);
  10. printf("%p\n",&(haha[0]));
  11. printf("%d\n",haha[0][0]);
  12. }

输出结果如下:

  1. 0x7ffc64f3f790
  2. 0x7ffc64f3f790
  3. 0x7ffc64f3f790
  4. 0x7ffc64f3f790
  5. 1

再看另外一个例子:

  1. #include<stdio.h>
  2. int main(void)
  3. {
  4. int haha[3]={1,2,3};
  5. printf("%p\n",haha);
  6. printf("%p\n",&haha);
  7. }

输出结果为:

  1. 0x7fff45d08f00
  2. 0x7fff45d08f00

对haha+1和对haha[0]+1的结果不同

指向多维数组的指针

  1. int (* pc)[2]//pz指向一个包含2个int值的数组
  1. int * pc [2]//这样会创建2个指向单个int值的指针

指针兼容性

可以将一个int值赋给一个double变量,但是对于指针不行。

函数和指针

在函数原型和函数定义中可以使用如下形式定义一个指向数组的指针:

  1. void haha(int (* pc)[3]);
  2. void haha(int pc[][3]);//在pc是形式参量是可以用这种形式
  3. /*在函数原型中,还可以做如下简化:*/
  4. void haha(int [][3]);

还可以使用typedef定义多维数组

  1. typedef int haha[3];//haha是包含3个int的数组
  2. typedef haha lolo[4];//lolo是包含3个haha的数组
  3. /*下面几种形式等价*/
  4. void fick(lolo pc);
  5. void fick(int (* pc)[3]);
  6. void fick(int pc[][3]);
  7. void fick(int pc[4][3]);//有效,但编译器忽略4

声明多维数组方法如下:

  1. int haha(int pc[][2][3][4]);//除了最左边的方括号可以留空,其余都应该填写数字
  2. int haha(int (*pc)[2][3][4]);//与上面等效
  3. /*pc指向一个2*3*4的int数组*/

变长数组(c99新特性)

(其大小可以有变量来指定)

  1. int x=3;
  2. int y=4;
  3. double haha[x][y];//一个变长数组

复符合文字

下面是一个复合文字,

  1. (int [3]){1,2,3};

可以简写为:

  1. (int []){1,2,3};

可以直接作为实参,或者将赋值给一个指针变量

  1. int * pc;
  2. pc=(int [3]){1,2,3};

在多维数组的情况下,有如下示例:

  1. int (* pc)[3];
  2. pc={{1,2,3},{4,5,6}}

第十一章 字符串和字符串函数

  1. #include<stdio.h>
  2. int main(void)
  3. {
  4. printf("%s,%p,%c\n","we","are",*"qwer");
  5. }

输出结果为:
下面的程序:
we,00D65860,q

  1. #include<stdio.h>
  2. int main(void)
  3. {
  4. char haha[]="motherfucker";
  5. printf("%s,%p\n",haha,haha);
  6. }

输出结果为:
motherfucker,012ff830

在c中字符串是一个char数组,并以空字符"\0"结尾,下面2种定义等价

  1. char haha[]="qwer";//编译器自动补上'\0'.
  2. char haha[]={'q','w','e','r','\0');//如果没有结尾的'\0',只是字符数组而不是字符串。

注意区分'\0',0和'0'。

创建字符串数组及其初始化

可以使用下面2中表达式:

  1. char * haha="qwer";//指针变量,后面的编写中可以使用诸如haha++这种表达式,让指针只想别处
  2. char hehe[]="qwer";//,常量数组名,后面的编写不能使用诸如hehe++这种表达式。数组的元素是变量,但是数组名是常量
  3. haha=hehe//有效
  4. hehe=haha//无效
  5. /* ps:,声明指针与声明其他数组数组不同,声明数组只能使用数组名,而不能使用指针 */

除此之外,我们还要注意下面的区别:

如果一数组名的形式声明:

  1. #include<stdio.h>
  2. int main(void)
  3. {
  4. char ha[]="qwer";
  5. char he[]="qwer";
  6. printf("%p,%p",ha,he);
  7. }

结果为:

010ffe04,010ffdf4

但是如果用指针形式声明:

  1. #include<stdio.h>
  2. int main(void)
  3. {
  4. char * ha="qwer";
  5. char * he="qwer";
  6. printf("%p,%p",ha,he);
  7. }

输出结果为:

00b65858,00b65858

编译器可能使用同意个单个的拷贝来表示所有相同的字符串,让不同的指针变量都指向同一个字符串。
因此,修改一个字符串可能导致所有用了这个字符串的地方都被修改,为避免这种情况,可在前面加上const修饰符。

字符串数组

声明一个字符串数组方法:

  1. char *haha[3]={"qwer","wtf","weq"};//声明包含三个指针的数组,可以
  2. char (*ha)[3]={"qwer","wtf","weq"};//为声明一个指向有三个元素的数组的指针,不可以
  3. char hehe[][3]={"qwer","wtf","weq"};//只能建立所有字符串等长的数组

haha数组并不存放字符串,只是存放了字符串的地址,因此,可以用下面的语句来访问字符

  1. char qwer;
  2. qwer=*ha[0];
  3. qwer=[0][0];

haha创建指向3个字符串的指针的数组,而hehe创建的是char数组的数组。haha存放3个地址,hehe存放3个完整的字符数组

指针和字符串

看看下面这个程序:

  1. #include<stdio.h>
  2. int main(void)
  3. {
  4. char * ar = "dont be a fool";
  5. char * ha;
  6. ha=ar;
  7. printf("%s,%p,%p\n",ar,&ar,ar);
  8. printf("%s,%p,%p\n",ha,&ha,ar);
  9. }

输出为:
dont be a fool;0095ff08,013c5858
dont be a fool;0095fefc,013c5858

字符串输入

  1. char * name;
  2. scanf("%s",name);

上面的程序,不可取,因为name是一个没有初始化的指针,可能指向任何位置,可能导致程序异常终止。
最简单的方法是在声明中确定其大小:

  1. char * name[45];

现在name是一个已分配81字节存储块的地址,另一种方法是使用c库中分布存储空间的函数(12章)。
为字符串语录空间后,就可以读入字符串了。
c库提供三个读取字符串的函数:scanf(),fgets(),gets()

scanf(),fgets(),gets()函数(p300)

puts90,fputs(),printf(),函数(p304)

自定义字符串输入/输出函数

字符串函数

strlen()

  1. void fit(const * string)
  2. {
  3. if(strlen(string)>8)
  4. printf("qwer");
  5. }

strcat()

接受2个字符串参数,将第二个字符串的一份拷贝添加到第一个字符串的末尾,使第一个成为一个新的字符串,而地位个不变。

strncat()

strcat()函数不检查第一个函数是否能够容下第二个字符串,,strncat需要另一个参数来指明最多允许添加字符的个数。

strcmp(),strncmp()。(p310)

strcpy()和strncpy()函数。(p314)

sprintf()以及其他字符串函数

例子:字符串排序(排序指针而不是而不是字符串)

命令行参数

程序读取命令行的附加项
下面是一段程序

  1. #include<stdio.h>
  2. int main(int argc,char * argv[])
  3. {
  4. int counter;
  5. for(counter=0;counter<argc;counter++)
  6. {
  7. printf("%s\t",argv[counter]);
  8. }
  9. }

在命令行中:

gcc test.c

得到a.exe文件
然后:

a.exe q w e r t y

输出为:

a.exe   q   w   e   r   t   y

main允许接收2个参数(或者不接收),一个为输入命令行的字符串个数,通常称为 argc(argument count)另一个为指向字符串的数组指针argv。命令行中的每个字符串存储到内存中,并且为其分配一个指针。系统使用空格来判断字符串的结束和开始。

#

存储类,链接和内存管理

存储类:可以按照存储时期来描述它,也可以按照作用域和链接来描述它

作用域:

代码块作用域:仅在定义它的代码块({}包括的一块)中起作用
函数原型作用域:从变量定义处一直到原型声明的尾部
文件作用域:从它的定义处到包含该定义的文件结尾处都是可见的

  1. #inculde<stdio.h>
  2. int haha; //具有文件作用域的变量,在main和qwer中都能使用
  3. void qwer(void);
  4. int main(void)
  5. {
  6. ...
  7. }

函数作用域:只适用于goto语句使用的标签,一个函数中的goto标签对该函数中任何地方的代码都是可见的,无论标签出现在那个代码块中。

链接:

空连接(no linkage):具有代码块作用域或者函数原型作用域的变量有空连接,意味着他们由定义他们的代码块或者函数原型所私有。
外部链接(external linkage):具有文件作用域的变量可能有内部链接或者外部链接。一个具有外部链接的变量可以在一个多文件程序的任何地方使用。
内部链接(internal linkage):具有文件作用域的变量可能有内部链接或者外部链接。一个具有内部链接的变量可以在一个文件的任何地方使用。

  1. int haha;//文件作用域,外部链接
  2. static int dodger;//文件作用域,内部链接
  3. int main(void)
  4. {
  5. ...
  6. }

存储时期

静态存储时期(static storage duration):在执行程序期间一直存在,具有文件作用域的变量具有静态存储时期,
自动存储时期(automatic storage duration):具有代码块作用域的变量一般具有自动存储时期,在程序进入定义这些变量的代码块时,程序为其分配内存;在退出这个代码块是时,分配的内存被释放。(在一个函数调用结束后,它的变量可以用来存储下一个被调用函数的变量。
其他内容参见教材P324

5种存储模型和基于指针的第六种存储模型

1.自动变量:auto int i,内层定义覆盖(内层代码块定义的变量与外层某一变量具有相同变量名时,如果不同明则不会覆盖),若不初始化分配的内存的存储类容不定。

示例程序:
  1. #include<stdio.h>
  2. int main(void)
  3. {
  4. int x=10;
  5. printf("%d\n",x);
  6. {
  7. int x=20;
  8. printf("%d\n",x);
  9. }
  10. printf("%d\n",x);
  11. while(x++ <= 13)
  12. {
  13. int x=100;
  14. printf("%d\n",x);
  15. }
  16. printf("%d\n",x);
  17. }

输出为:

10
20
10
100
100
100
100
15
  1. #include<stdio.h>
  2. int main(void)
  3. {
  4. int i=10;
  5. printf("%d\n",i);
  6. for(int i=0;i<=3;i++,printf("%d\n",i))//其中定义的变量i不属于for循环的子代码块,而属于main
  7. {
  8. int i=34;
  9. printf("%d\n",i);
  10. }
  11. }

输出为:

10
34
1
34
2
34
3
34
4

但是如果在之代码块中没有定义同名变量则不会覆盖:

  1. #include<stdio.h>
  2. int main(void)
  3. {
  4. int i=10;
  5. printf("%d\n",i);
  6. for(int i=0;i<=3;i++,printf("%d\n",i))
  7. {
  8. printf("%d\n",i);
  9. }
  10. }

输出结果为:

10
0
1
1
2
2
3
3
4

无{}的代码块:

  1. #include<stdio.h>
  2. int main(void)
  3. {
  4. int x=10;
  5. printf("%d\n",x);
  6. for(int x=1;x<=2;x++)
  7. printf("%d\n",x);
  8. printf("%d\n",x);
  9. }

输出为:

10
1
2
10

**注意在for循环中定义变量(int x=1),为c99特性,在编译时要在命令行中加入: **

-std=c99

寄存器变量

  1. register int i;//register只能用于具有代码块作用域的变量

(可能使程序运行更快,但是不能位寻址),

具有代码块作用域的静态变量

  1. static int i;

当包含这些变量的函数完成工作时,变量不消失,从一次函数调用到下一次,计算机都记录它们的值。

具有外部链接的静态变量,关键字:extern

只能用常量表达式来初始化文件作用域变量!!
在所有函数外定义的变量为具有外部链接的静态变量:

  1. #include<stdio.h>
  2. int i;//定义声明
  3. int main(void)
  4. {
  5. extern int i;/*引用声明,使用在其他地方定义的变量,不会引起空间分配,不要使用extern进行外部定义,只能用他引用一个已经存在的外部定义。*/
  6. ...
  7. }

下面的做法不能用于定义具有外部链接的静态变量:

  1. extern int i;//应用声明,没有定义和分配内存,编译器假定tern的真正定义在程序的某个地方。
  2. int main(void)
  3. {
  4. ...
  5. }

一个外部变量只能初始化一次,下面的语句是错误的

  1. extern int i =10; //错误

具有内部链接的静态变量。

  1. static int i;//具有内部链接的静态变量
  2. int j;//外部链接
  3. int main(void)
  4. {
  5. extern int i;//表示main在使用一个全局变量,但是i任然具有内部链接。
  6. extern int j;//表示main在使用一个全局变量
  7. ...
  8. }

除了一个定义声明外,其他的所有声明必须使用关键字extern,并且只有在定义声明中可以初始化。多文件编译时,除非在第二个文件中也声明了该变量(用extern),否则在一个文件中定义的外部变量不能用于第二个文件,但是若在头文件中做了如下声明:extern int i;则不用再在第二个函数中使用引用声明extern

存储类和函数

  1. double gamma(void);/*默认为外部*/
  2. static double delta(void);//只能在本文件夹使用,可以在其他文件夹创建同名函数
  3. extern double beta();//通常用来引用声明其他文件夹中定义的函数

随机数函数和静态变量

( p346,没看以后看吧)

分配内存

下面的语句子编译时就已经分配了内存:

  1. int planets[100];

可以在程序运行是分配更多内存,当然我们可以使用VLA的方法:

  1. int m;
  2. ...
  3. int planets[m];

我们还可以用 stdlib.h 里面的函数:

malloc()函数:

接受参数:所需内存的字节数
返回值:分配的内存的第一个字节的地址,可以将赋值给一个指针变量,其为指向void的指针类型(通用类型),一般需要把返回值的类型指派为适当的类型。

  1. double * pc;
  2. pc=(double *)malloc(n*sizeof(double));/*n为之前声明过的变量,pc为指向double的类型,可以使用诸如pc[2]这样的语句访问内存*/

通常对应一个malloc(),应该调用一次free(),其接受参数为之前malloc()分配的地址,它释放先前分配的内存,但是不能用free()释放通过其他方式(例如声明一个数组)分配的内存,下面是一个例子:

  1. int * pi;
  2. pi=(int *)malloc(4*sizeof(int));
  3. ...
  4. free(pi);

函数calloc():与malloc()类似

  1. int * men;
  2. men=(int *)calloc(100,sizeof(int));//建立100个int所占字节大小的单元

动态分配内存与变长数组(VLA)的区别:VLA是自动存储,就是在VLA所用内存空间在运行完定义部分后自动释放,但是malloc不行,需要free()才能释放存储空间,free()可以使用不同与malloc()指针的指针变量,必须一致的是指针中存储的地址。

对于多维数组可以参看下面的程序:

  1. int n;
  2. int m;
  3. int ar[n][m];
  4. int (*p1)[m];
  5. p1=(int (*)[m])malloc(n*m*sizeof(int));//注意对* 使用了(),

存储类和动态内存分配

三种内存:
1.具有外部/内部/空链接的静态变量
2.自动变量
3.动态分配的内存:在malloc或相关函数处产生,在free()处消失,由程序员控制而不是一系列固定的规则控制。

类型限定词

1.const

2.volatile

3.restrict

旧关键字的新位置

C预处理器和C库

难点:##运算符 和#pragma
三种预处理器功能:1.宏定义 2.文件包含 3.条件编译
其中宏定义是替换,不做计算,也不做表达式求解

#define

  1. #define TWO 2
  2. #define FOUR TWO*two

在#define中使用参数

  1. #define SQUARE(x) X*X

在程序中可以这样使用

  1. z=SQUARE(2);

注意:在程序中凡是出现SQUARE(x)的地方将用x*x替换,所以SQUARE(4+2)被替换成:4+2*4+2,可以用添加括号的方法得到我们想要的结果:SQUARE(x) (x)*(x)

# include

#undef

C/C++不允许重复#define同一个常量如:

  1. #define SIX 2*3
  2. #define SIX 4 //非法的,报错

可以使用#undef取消常量定义

  1. #define SIX 2*3
  2. #undef SIX
  3. #define SIX 4

#ifdef #else #endif

#ifndef #else #endif

头文件的有些语句在一个文件中只能出现一次,如结构类型的声明,
通常使用下面方法避免重复包含带来的麻烦

  1. #ifndef STDIO_H_
  2. #define STDIO_H_
  3. //头文件的内容
  4. #endif

#if#elif

很想常规C中的语句

  1. #if defined(SYS) //defined 为预处理器运算符,判断其参数是否已经用#define定义过
  2. #include"head1.h"
  3. #elif defined(HAHA)
  4. #include"head2.h"
  5. #else defined(DUCK)
  6. #include"head.h"
  7. #endif

预定义宏

  1. __DATE__
  2. __LINE__
  3. __TIME__
  4. __FILE__
  5. __STDC__
  6. __STDC_HOSTED__
  7. __STDC_VERSION__ //相关含义参见C primer plus 474页

预定义宏可以在原程序中直接使用,如:

  1. int main(void)
  2. {
  3. std::cout << __TIME__;
  4. }

...

#line 和#error

  1. #if __STD_VERSION__ !=199901L
  2. #error NOT C99 //使处理器发出一条错误消息,肯恩恶搞的话,编译过程应该中断
  3. #endif

#pragma

编译指示是机器或者操作系统专有的,对于每个编译器都是不同的
在现代编译器中,可以用命令行参数或者IDE菜单修改编译器的某些设置,也可以用#pragma将编译器指令置于源代码中如:

  1. #pragma c9x on //启用对C9X的支持
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注