C编译器和链接器
(1)、初次体验
# include <stdio.h>
int main(int argc, char* argv[])
{
printf("Hello World!\n");
return 0;
}
(2)、自己动手,编译,连接c程序
安装vc++6.0
CL hello.c /c 编译 生成。obj文件,.obj文件是通用的COFF的格式,是个目标文件,可以和其他平台交互.
CL hello.c /c /p ,就会多了1个.i文件。然后打开它,尽然有400多行,里面就是stdio.h文件内容。当然stdio.h文件中也包含了其他文件,所以全部展开后就400多行了!
link hello.obj link就为win平台造了个pe格式的hello.exe
如果直接在cmd命名行中 CL hello.c,编译器给自动链接,同时生成了.obj和.exe 文件。
(3)、位操作:位操作符呢一共有6种,分别是按位与(&),按位或(|),按位异或(^),取反(~),左移(<<),右移(>>)。
(4)、数组特性#include <stdio.h>
void change(int x[])
{
int temp = x[0];
x[0] = x[1];
x[1] = temp;
printf("数组名的大小为:%d\n", sizeof(x));
}
int main(void)
{
int a[2] = {11, 22};
printf("交换前的数组元素为:%d\t%d\n", a[0],a[1]);
change(a);
printf("交换后的数组元素为:%d\t%d\n", a[0],a[1]);
printf("数组名的大小为:%d\n", sizeof(a));
return (0);
}
/**********************************************
交换前的数组元素为:11 22
数组名的大小为:4
交换后的数组元素为:22 11
数组名的大小为:8
***********************************************/
数组名做为函数参数的时候,传递过去的是这个数组的4字节首地址,
所以数组名传参时候的副本只记下4字节首地址即可。因为传递的是地址,所以可以修改原数组的值!
(5)、数组的寻址公式:
a[n] = a + sizeof(type) * n
a[n]:你所要寻址的数组元素
a:数组首地址
sizeof(type):元素的大小
n:数组下标
反码:当是正数的时候呢,它的反码与原码一样!负数呢!就符号位为1,其他位都取反
[+100]反=01100100 [+0]反=00000000
[-100]反=10011011 [-0]反=11111111
注意:在反码中,零也有两种表示形式。
补码:反码加1
[+100]补=01100100 [+0]补=00000000
[-100]补=10011100 [-0]补=00000000
注意:在补码中,零有唯一的编码,[+0[补=[-0]补=00000000。
对于正数,原码和反码,补码都是相同的
这是因为在计算机系统中,数值一律用补码来表示(存储)。
主要原因:
1、统一了零的编码;
2、将符号位和其它数值位统一处理;
3、将减法运算转变为加法运算;
4、两个用补码表示的数相加时,如果最高位(符号位)有进位,则进位被舍弃。
eg:计算9-6的结果。
9-6=9+(-6)=3
采用补码来进行计算呢?
9的补码是0000 1001,-6的补码是11111010
0000 1001
+1111 1010
1 0000 0011
最高位的1溢出,剩余8位二进制表示的是3的补码。结果为3.
(7)、全局变量,全局变量,静态局部变量,静态全局变量
全局变量,静态局部变量,静态全局变量都在静态存储区分配空间,而局部变量在栈里分配空间
全局变量:定义在函数内部的变量叫局部变量。局部没有经过任何修饰的局部变量叫自动变量 (它存在于动态数据区——栈),这个区域内的数据随着程序的运行动态的生成和释放。 函数返回就释放,函数要调用就生成。关键字:默认是atuo(一般不写)特点:当程序执行到自动变量的作用域的时候就分配
全局变量:定义在函数外部的变量叫全局变量。全局变量只需在一个源文件中定义,就可以作用于所有的源文件,当然,其他不包含全局变量定义的源文件需要用extern 关键字再次声明这个全局变量。
静态局部变量具有局部作用域,它只被初始化一次,自从第一次被初始化直到程序运行结束都一直存在。它和全局变量的区别在于全局变量对所有的函数都是可见的,而静态局部变量只对定义自己的函数体始终可见。
静态全局变量也具有全局作用域,它与全局变量的区别在于如果程序包含多个文件的话,它作用于定义它的文件里。不能作用到其它文件里,即被static关键字修饰过的变量具有文件作用域。这样即使两个不同的源文件都定义了相同名字的静态全局变量,它们也是不同的变量。
(8)、指针和指针变量
指针的本质:其实指针的本质是对内存地址的抽象,在计算机中用来表示地址加其解释方式。
数组的元素如果是char型,那么这个就是char型数组,如果数组元素都是int型,那么这个就叫int型数组,
如果数组元素都是指针,那么这个就是指针数组。
指针变量:存放一个值为其他内存对象地址的变量叫指针变量。
指针与地址的区别:地址是表示内存的编号信息;指针有2个信息,1个是地址信息,1个是此地址的解释信息。比如:int *p表示此指针指向的地址的内存对象用int去解析.float *q表示此指针指向的地址的内存对象用float去解析。
C语言的直接引用:我们通过变量名来直接引用变量。例如a=123.编译器会自动将变量名转换成变量的存储地址。然后再将123放入变量a的存储空间中。
C语言的间接引用:首先将变量的地址存放在一个变量(指针变量),然后再通过存放变量的地址的指针变量来引用变量。
#include <stdio.h>
void main()
{
int a, b;
int *p;//定义指针变量p
p = &b;//将变量b的地址存放在变量p中
a = 3;//直接引用变量a
*p = 5;//间接引用变量b
}
(9)、字符串和字符串函数
char word [40];
scanf("%s" ,word);
printf("%s" ,word);
等价于
char word [40];
fgets(word ,40, stdin);
fputs(word,stdout);
"x" 实际上是由两个字符构成‘x’和'\0'
char word [40];
scanf("%s" ,word);
printf("%s\n" ,word);
//sizeof以字节为单位计算数据的大小
printf("%d\n" ,sizeof( word));
//strlen 以字符为单位计算字符串的长度
printf("%d\n" ,strlen( word));
字符串常量:是指一对双引号中的任何字符。编译器自动会在末尾加上结束标记\0字符,字符串常量属于静态存储,在运行中只保存一份。一般使用#define来定义字符串常量。字符串常量一般属于静态存储类,静态存储是指,如果在一个函数中使用字符串常量,即使是多次调用这个函数,该字符串在整个运行过程中只有一份。
9.3、字符串数组及初始化
const char m[] ="Hello World"
等价于const char m[]={'H','e'....'\0'}
注意标记结束的空字符。如果没有它,得到的就是一个字符数组而不是一个字符串。
指向数组元素的指针变量
int a[5], *p;
p = &a[0];
1.C语言中,数组名代表数组的首地址,就是第一个元素的地址。因为上面的赋值等价于:p = a;
2.指向数组元素的指针也可以定义时赋值: int *p = &a[0];或者 int *p = a;但是不能a=p;因为a是常量,p是变量,就好比x=3哦3=x的概念。
如果p的初始值是&a[0],那么:p+i和a+i都可以表示元素a[i]的地址;*(p+i)和*(a+i)都表示指针p+i或a+i所指向的数组元素a[i]的值。
总结:引用一个数组元素可以有两种方法:下标法:a[i];指针法:*(p+i)。
//区别:数组名是常量,存储第一个数组的地址,指针则是变量。
char heart[] = "I Love Mill";
char *head = "I Love Till";
//(1)都可以使用数组符号
for (i=0;i<6;i++)
{
printf("%c",heart[i]);
}
printf("\n");
for (i=0;i<6;i++)
{
printf("%c",head[i]);
}
printf("\n");
//(2)都可以使用指针加法
for (i=0;i<6;i++)
{
printf("%c",*(heart+i));
}
printf("\n");
for (i=0;i<6;i++)
{
printf("%c",*(head+i));
}
printf("\n");
//(3) 指针可以使用增量运算符,而数组名不可以
while(*(head)!='\0')
{
printf("%c",*(head++));
}
printf("\n");
(10)、指针函数和函数指针
返回指针值的函数
1 返回值为指针型数据的函数,定义一般的形式为:
类型名 *函数名(参数表)
例如:int* func(int x, int y);
解释:表示func的返回值为指向int型数据的指针。
指向函数的指针
定义:类型名 (* 指针变量名) ();
为什么会存在指向函数的指针:因为啊!函数作为一段程序,在内存中也要占据一片存储区域呢!
它就会有一个起始地址,即函数的入口地址,
指向函数的指针有什么作用啊:这样一来啊,我们就可以定义一个指针变量指向函数,然后通过指针调用函数。将函数作为参数在函数间传递。
间接调用与直接调用:通过函数指针来调用函数叫间接调用,通过函数名来调用叫直接调用。
例子:
int (*p)()
表示:p是一个指向函数的指针变量,此函数的返回值为int型。
int m1(int x, int y){
.....
return
}
void main(){
int a;
int (*p)(int,int) = NULL;
p = m1;
a = p(2,3); //间接调用
}
(11)、指针数组的定义:如果数组的元素都是指针类型,那么我们就把这种叫做指针数组。
指针数组是如下定义的:类型名字 *数组名[常量表达式]
char *aStringA[]=
{
"I'm not afraid to take a stand",
"We'll walk this road together, through the storm",
"Whatever weather, cold or warm"
};
字符串在常量区,长度是12字节, 元素交换很方便,只需要交换指针。
(12)、指向指针的指针
变量的直接引用与间接引用
通过变量名叫做直接引用,通过指针对变量的引用叫间接引用
间接引用的两种情况
1,如果在一个指针变量中存放的是一个目标变量的地址叫做一级地址
2,如果在一个指针变量中存放的是指向目标变量的地址的指针变量的地址,那么这个就叫做二级地址。
void main()
{
int a = 99;
int *pa = &a;
int **ppa = &a;
}
这样是绝对不行的,因为类型不匹配呀!会报错啦!
那么再看这个程序:
void main()
{
int a = 99;
int *pa = &a;
int **ppa = &pa;
}
返回指针值的函数
(13)、容易混淆的const
关键字const 并不是把变量变成常量,在一个符号前加上const限定符只是表示这个符号不能被赋值,也就是它的值对于这个符号来说只是只读的。const 最有用的地方,就是限制函数的形参,这样该函数将不会修改实参指针所指的数据,但是其它的函数可能会修改它。
const int limit = 0;
const int *limit = &limit;
limit 是一个指向常量整形的指针,这个指针不能用于修改这个整型值,但在任何时候,这个指针本身的值是可以改变的。
const int * gra;
int const * gra;
int * const gra;
三者的区别:
最后一种情况,指针是只读的,前两种情况指针所指向的对象是只读的,也就是地址存储的整型值
(14)、C的内存分配方式:
内存划分:
1 、栈区 (stack) 由编译器自动分配释放 ,存放为运行函数而分配的局部变量、函数参数、返回数据、返回地址等。其操作方式类似于数据结构中的栈。
2 、堆区 (heap) 一般由程序员分配释放,程序员自己负责在何时用 free 或 delete 释放内存 。动态内存的生存期由程序员决定 ,使用非常灵活,但如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏 分配方式类似于链表。
3 、全局区 (静态区static) 存放全局变量、静态数据、常量。程序结束后又系统释放,全局变量的初始化是编译器做的。被写在了可执行文件里
4 、文字常量区 常量字符串就是放在这里的。 程序结束后由系统释放。
5 、代码区 存放函数体(类成员函数和全局函数) 的二进制代码。可读可执行。
(14)、什么是声明,什么定义
在C语言中对象(函数,变量)必须有且只有一个定义,但是可以有多个声明。extern 声明告诉编译器对象的类型和名称,定义是为对象分配内存。
数组和指针:
指针数组:如果数组的元素都是指针类型,那么他就是指针数组。
定义:int *arr[100]
这个int *x[100]求长度会是多少呢,在32位系统中指针是占4字节的,那么这里有100个,那就是400字节.
eg: char *cArr[]= {"hello","world"};
cArr数组里面放的2个4字节指针啦.是指向字符串的指针,也就是字符串的首地址。
(15)、stdafx.h Standard Application Framework Extensions
所谓头文件预编译,就是把一个工程(Project)中使用的一些MFC标准头文件(如Windows.H、Afxwin.H)预先编译,以后该工程编译时,不再编译这部分头文件,仅仅使用预编译的结果。这样可以加快编译速度,节省时间。 预编译头文件通过编译stdafx.cpp生成,以工程名命名,由于预编译的头文件的后缀是“pch”,所以编译结果文件是projectname.pch。 编译器通过一个头文件stdafx.h来使用预编译头文件。stdafx.h这个头文件名是可以在project的编译设置里指定的。编译器认为,所有在指令#include "stdafx.h"前的代码都是预编译的,它跳过#include "stdafx. h"指令,使用projectname.pch编译这条指令之后的所有代码。 因此,所有的MFC实现文件第一条语句都是:#include "stdafx.h"。
与stdio.h的区别
我们一般用TC或vc编译C程序的时候都要首先包含这个stdio.h头文件,这个头文件里面包含了scanf和printf函数的定义,如果我们不在程序开头include这个文件,那么你调用上面这两个函数就不会成功,它其实和c++中的iostream.h文件的作用差不多的,它们一般都已经在stdafx.h文件中被包含。