IOS基础学习之C(二)

++ 指针 ++
引用的概念:赋值时都是先将变量名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(二)

你可能感兴趣的:(IOS基础学习之C(二))