++ 指针 ++
引用的概念:赋值时都是先将变量名a转换为a的存储地址,根据地址找到变量a的存储空间。然后将数据以2进制的形式放入a存储空间中。
直接引用:直接通过变量名来读写变量
间接引用:首先将变量a的地址存放在另一个变量中,比如存放在变量b中,然后通过变量b来间接引用变量a,间接读写变量a的值。
[间接引用来修改值:先根据 变量名b 获取 变量b 的地址ffc2,取出变量b中存储的内容ffc1,也就是变量a的地址,再根据变量a的地址ffc1找到a的存储空间,然后修改里面的数据。]
指针变量:用来存放变量地址的变量,就称为”指针变量"。在上面的情况下,变量b就是个"指针变量",我们可以说指针变量b指向变量a。
char a;
// a = 10;//该用方式是直接引用的方式;
char *b;//定义了一个指针就是b,而且b只能指向char类型的变量;
//让指针变量b指向a;
b = &a;
//* 指针运算符,*b代表访问b中存储的地址对应的存储空间;也就变量a的存储空间
//此处*b中的*与char *b中的*的定义是不一样的,这里为操作符,上面的只是定义;
//相当于a=10;
*b =10;
printf("%d\n",a);
//相当于取出a的存储空间中的内容,两个输出结果一样;
printf("%d\n",*b);
char a =10;
char *b;
// *b = &a;//此种写法错误;
char *p;//由于没有赋值,系统会乱指向一个地址;这样操作是错误的,一定要确定指针指向的变量后,再进行相应的操作;
*p =10;
p=100;//这种写法也错误,指针变量是存放变量地址的,不能随便赋值,除非这个值恰好是变量的地址值。
以值交换的例子说明:若参数直接传入变量值,交换后不能改变传入变量的值;只有传入的是原参数的地址,用地址交换,才能改变原传入参数的值。
void swapPoint(char *a,char *b){
printf("swap交换前的值%d,%d\n",*a,*b);
char temp= *a;
*a=*b;
*b=temp;
printf("swap交换后的值%d,%d\n",*a,*b);
}
使用指针可在函数内部修改外变量的值
int sumAndMinus(int a,int b,int *minus){
*minus = a-b;
return a+b;
}
利用返回一个值,传入另外参数的地址,便可同时修改多个值。[常用]
注意:
在同一种编译器环境下,一个指针变量所占用的内存空间是固定的。比如,在16位编译器环境下,任何一个指针变量都只占用2个字节,并不会随所指向变量的类型而改变。
既然每个指针变量所占用的内存空间是一样的,而且存储的都是地址,为何指针变量还要分类型?而且只能指向一种类型的变量?比如指向int类型的指针、指向char类型的指针。
[这个问题跟”数组为什么要分类型”是一样的。由于指针的长度是由编译器决定的和类型没有关系,但定义的类型不一样时会认为要取出的内容就是定义的类型的长度。如char c;int *p=&c;就会认为c的值是占用两个字节,便从c的起始地址取两个字节,但c的值实际上只有一个字节,于是就会取出错误的值。]
指向一维数组的指针
组及其数组元素都占有存储空间,都有自己的地址,因此指针变量可以指向整个数组,也可以指向数组元素。
int a[2];int *p; p=a与p=&a[0];相同;
void traversalArray(){
int a[3]={1,2,3};
int *p = a;//让指针指向第一个指针;
for(int i=0;i<3;i++){
//由于在数组中地址是连续的,p+i即可取出地址,*(p+i)即可取出对应的值
//对于指针类型来说p+1,不只是纯粹的地址+1;而是和指向的类型有关,+1是+该类型的字节对应的地址字节个数;如float类型,即p+4;
// printf("a[%d]=%d\n",i,*(p+i));//p的地址和值都未改变
// printf("a[%d]=%d\n",i,*(a+i));//原理同上,但不能写成a++;不能改变数组的首地址;
printf("a[%d]=%d\n",i,*(p++));
//p的地址和值一直会改变
printf("p的地址:%d,p的值%d\n",p,*p);
}
}
若一个函数的参数是一个数组或一个指针变量,都可以传数组名或数组的指针;
指针和字符串
定义:
char *s=“mj”;或char *s;s=“mj”;
[错误写法:char s[10];s=“mj”;或char *s =”mj”;*s=“jm”(*s=‘f’;此处s是一个常量不能修改,但char s[]=“mj”,此处是一个变量,可直接修改);均是错误写法。]
指针函数
函数作为一段程序,在内存中也要占据部分存储空间,它也有一个起始地址,即函数的入口地址。可以利用一个指针指向一个函数。其中,函数名就代表着函数的地址。
定义:
可将函数作为为参数传入另一个函数并进行调用。
char * point(){
return "返回指针的函数\n";
}
int sum(int a,int b){
printf("sum %d\n",a+b);
return a+b;
}
/*将另外一个函数做为参数(可类似于java中的接口使用)*/
int count(int a,int b,int(*p)(int,int)){
return p(a,b);
}
void testPointVoid(){
// printf(point());
//定义了一个指向函数的指针变量p;
//被p指向的函数,返回的值为int类型,接收两个int类型的参数
int (*p)(int,int);
//让指针变量p指向sum函数;
p=sum;
//利用指针变量间接调用sum函数
// (*p)(5,6);
//或
p(5,6);
//将求和的方法名传入到方法中进行计算
int result = count(5, 6, sum);
printf("result:%d\n",result);
}
预处理指令
概述:
1.C语言在对源程序进行编译之前,会先对一些特殊的预处理指令作解释(比如之前使用的#include文件包含指令),产生一个新的源程序(这个过程称为编译预处理),之后再进行通常的编译
2.为了区分预处理指令和一般的C语句,所有预处理指令都以符号"#"开头,并且结尾不用分号
3.预处理指令可以出现在程序的任何位置,它的作用范围是从它出现的位置到文件尾。习惯上我们尽可能将预处理指令写在源程序开头,这种情况下,它的作用范围就是整个源程序文件
4.C语言提供的预处理指令主要有:宏定义(define)、文件包含(include)、条件编译
****** 宏定义:类似java中的final,一定定义不变的变量,如
#define NUM 6
#define KEY “Name"
还可以定义一个方法,如:
//若定义带参数的宏方法,最好用括号括起来(每个元素及整体都需要),因为宏计算只是简单的拼接计算
//#define mul(a,b) a*b
//如mul(3-2,4-3)为3-2*4-3,则计算错误
#define mul(a,b) ((a)*(b))
[NOTE:但与函数不一样,因为宏是在编译前已经计算,而函数是在使用时再计算,故为了效率,简单的运算可放在宏方法了;另,宏定义不会检测数据类型,也没有返回值;名字如上mul不是函数的地址;]
条件编译:在很多情况下,我们希望程序的其中一部分代码只有在满足一定条件时才进行编译,否则不参与编译(只有参与编译的代码最终才能被执行),这就是条件编译。
#if条件1 ..code1.. #elif2 ..code2.. #else ..code3.. #endif
就只是编译满足条件的代码,是编译而不执行。与平时用的if-else不一样。
(一定要写#endif,表示和#if这间的内容才是条件编译,一般与宏定义的值配合使用,只编译部分代码,效率很高。)
#if NUM>6
printf("num>6");
#elif NUM==6
printf("num=6");
#else
printf("num<6");
#endif
其他用法:
#if defined(MAX)//是否定义过某个宏
...code...
#endif
或者:
#ifdef MAX //同上,表示是否定义过宏MAX
…code...
#endif
#ifndef MAX//同上,表示没有定义过宏MAX
...code...
#endif
- include 允许嵌套包含,但不允许递归包含;可能导致多次包含同一个头文件,降低编译效率。 为了解决重复导入包,使用条件编译:
- //若没有该宏,就定义该宏,然后在该条件内定义函数名,宏名一般同头文件名
#ifndef Header_h
#define Header_h
...
#endif /* Header_h */
变量类型
变量的存储类型:指变量存储在什么地方。有3个地方可以用于存储变量:普通内存(静态)、运行时堆栈(动态)、硬件寄存器。变量的存储类型决定了变量何时创建、何时销毁以及它的值能保持多久,也就是决定了变量的生命周期。
C语言根据变量的存储类型的不同,可以把变量分为:自动变量、静态变量、寄存器变量。
— 自动变量:存储在堆栈中的;
哪些是自动变量:被关键字auto修饰的局部变量都是自动变量,但是极少使用这个关键字,基本上是废的,因为所有的局部变量在默认情况下都是自动变量。
生命周期:在程序执行到声明自动变量的代码块(函数)时,自动变量才被创建;当自动变量所在的代码块(函数)执行完毕后,这些自动变量就会自行销毁。如果一个函数被重复调用,这些自动变量每次都会重新创建。
— 静态变量:静态变量是存储在静态内存中的,也就是不属于堆栈。(与java中的静态变量不同)
哪些是静态变量:
• 所有的全局变量都是静态变量(不用static修饰)
• 被关键字static修饰的局部变量也是静态变量(只改变了其生命周期,但未改变其作用域)
生命周期:静态变量在程序运行之前创建,在程序的整个运行期间始终存在,直到程序结束。
— 寄存器变量:存储在硬件寄存器中的变量,称为寄存器变量。寄存器变量比存储在内存中的变量访问效率更高(默认情况下,自动变量和静态变量都是放在内存中的)
哪些是寄存器变量:
• 被关键字register修饰的自动变量都是寄存器变量
• 只有自动变量才可以是寄存器变量,全局变量和静态局部变量不行
• 寄存器变量只限于int、char和指针类型变量使用
生命周期:因为寄存器变量本身就是自动变量,所以函数中的寄存器变量在调用该函数时占用寄存器中存放的值,当函数结束时释放寄存器,变量消失。
使用注意:
• 由于计算机中寄存器数目有限,不能使用太多的寄存器变量。如果寄存器使用饱和时,程序将寄存器变量自动转换为自动变量处理
• 为了提高运算速度,一般会将一些频繁使用的自动变量定义为寄存器变量,这样程序尽可能地为它分配寄存器存放,而不用内存
关键字 extern 与 static
不仅可用在变量上,还可以用在函数上。
**extern与函数 **
• 外部函数:如果在当前文件中定义的函数允许其他文件访问、调用,就称为外部函数。C语言规定,不允许有同名的外部函数。
• 内部函数:如果在当前文件中定义的函数不允许其他文件访问、调用,只能在内部使用,就称为内部函数。C语言规定不同的源文件可以有同名的内部函数,并且互不干扰。
[extern即外部函数,但默认的都是外部函数,故可省略extern]
++ static void one(){..};内部函数,需要使用static修饰;外部不能直接调用;只能是内部调用,相当于private;
总结:
—— static——
在定义函数时,在函数的最左边加上static可以把该函数声明为内部函数(又叫静态函数),这样该函数就只能在其定义所在的文件中使用。如果在不同的文件中有同名的内部函数,则互不干扰。
static也可以用来声明一个内部函数
—— extern ——
在定义函数时,如果在函数的最左边加上关键字extern,则表示此函数是外部函数,可供其他文件调用。C语言规定,如果在定义函数时省略extern,则默认为外部函数。
在一个文件中要调用其他文件中的外部函数,则需要在当前文件中用extern声明该外部函数,然后就可以使用,这里的extern也可以省略。
[定义只保证编译时通过]
extern、static与变量的关系
C与Java都有全局变量的概念;但有一些不同:
- 默认情况下,一个函数不可访问在它后面定义的全局变量;
extern int a;//此处是用来声明一个变量,不能直接赋值和使用,只保证语法正确,声明后要以调用后去定义该变量
int a;//此处是用来定义一个变量可直接使用
在方法中使用 extern int a;//表示引用的是方法外部的全局变量
—** extern **—:只能用来声明全局变量,不能用来定义全局变量。
C中不同的源文件中可以有相同名字的变量,同一个文件中也有可多个相同名字的变量。默认情况下这些变量代表着同一个变量(默认情况下都是外部变量)[也可以文件a中的声明,文件b中定义]
— static —:
static int a;//定义的全局变量,相当于定义了私有的全局变量(类似于java中的private);在不同一源文件下相同的变量名,若用static修饰,即限制全局变量的作用域,则互不影响,是不同的变量。
+结构体
问题:当一个整体由多个数据构成时,我们可以用数组来表示这个整体,但是数组有个特点:内部的每一个元素都必须是相同类型的数据。但在实际应用中,我们通常需要由不同类型的数据来构成一个整体。
为解决这个问题:C提供了一种构造类型来解决,就是结构体。它允许内部的元素是不同类型的。
struct Student {
char *name; // 姓名
int age; // 年龄
float height; // 身高
};
[只定义了一个结构体类型并没有对其分配存储空间,只有在其定义一个结构体变量后才会分配一个存储空间]
定义结构体变量的3种方式:
- struct Student stu;
- struct Student{…}stu;
- struct{…}stu;
赋值:
struct Student stu={“tom”,12,1.67};
或stu.age=12;
注意点: - 不能对结构体本身递归定义;
- 结构体内可以包含别的结构体;
- 结构体变量占用的内存空间是其所有成员所占内存之和,而且各成员在内存中按定义的的顺序依次排列。
//要么全部放在大括号中,在定义的时候就赋值;要么所有的都单独赋值;不能一部分在定义的赋值,一部分单独赋值
struct Student stu={13,"tom",1.76,{1990,10,10}};
定义结构体数组同一般类型;也能作为方法参数;
若将结构体做函数参数,只改变函数内结构体的值,传入的结构体值为变(同java传入类作形参);
要改变原结构的值,可传入结构体变量的指针;
void changPoint(struct Student *stu){
(*stu).age=9;
(*stu).name="jim”;
(*stu).height=2.01;
}
//调用: changPoint(&stu);
访问方式(3种):
// 方式1:结构体变量名.成员名
printf("name=%s, age = %d \n", stu.name, stu.age);
// 方式2:(*指针变量名).成员名
printf("name=%s, age = %d \n", (*p).name, (*p).age);
// 方式3:指针变量名->成员名
printf("name=%s, age = %d \n", p->name, p->age);
+**枚举 **
同Java,一般用于固定常量;enum Season{spring,summer,autumn,winter};
便不同于java的是,枚举变量可直接赋其他值,而非枚举值。
//enum Season{spring,summer,autumn,winter}s;
//定义枚举类型的同时定义一个枚举变量s
enum Season{spring,summer,autumn,winter}s=spring;
enum Season{spring=1,summe=2,autumn,winter};
注意:C编译器一般会将枚举作为整型常量处理,称为枚举常量;
枚举的值取决于定义时向枚举元素的先后顺序,默认情况下,第一个枚举元素值为0;可自己设置;
+typedef
用于为各种数据定义一个新名字(别名)。
如:typedef unsigned int UInteger;//无符号整型
可在另外的基础上再定义一个别名:
如:typedef UInteger MyInteger;
可修饰指针;
如:typedef char * String;
可定义结构体及指针;
typedef struct Student Stu;
typedef struct Student * StuPoint;
typedef struct{
float x;
float y;} Point;
typedef struct Point{
float x;
float y;} *PP;
修饰枚举:typedef enum {spring,summer,autumn,winter} Season;
Season s=spring;
修饰函数的指针;(不是很常用)
static int sum(int a,int b){
return a+b;
}
static int cul(int a,int b){
return a*b;
}
typedef int (*CountPoint)(int,int);
void testtypedef(){
CountPoint sunpoint = sum;
CountPoint culpoint = cul;
printf("sum:%d\n",(*sunpoint)(4,5));
printf("cul:%d\n",(*culpoint)(4,5));
}
typedef与#define
区别:typedef,是新定义了一个类型,而宏定义是将变量拼接到定义后;若起别名一般使用typedef,而非define
感谢李明杰老师@M了个J的讲解及时详细的课件(http://www.cnblogs.com/mjios)
博客地址:IOS基础学习之C(二)