C语言基础(转载自大海笔记)

# C语言基础
2015年03月26日10:04:41
1、    语言排行榜
C——java——objective-C
2、    进制:
进制:进位机制。用普通的话讲,应该为人为的定义一种度量来标识一样东西。
计算机常用的进制有:十进制、二进制、八进制和十六进制。
➢    十进制:0-9(十个指头)(进制表示基数:10)
➢    二进制:0,1(基数为2)
➢    八进制:0-7(基数为8)
➢    十六进制:0-9,A-F(基数为16)
    可以有很多进制,比如分钟为60进制等等。
3、    位权
为了进制之间转换而出的一个概念。
位:一个通用的描述方式。最低位为第0位。之后依次+1。
例如:9AF(F为第0位,A为第1位,9为第2位)
权:进制是几,权就是几。

位权:某位上的数*权^该位
例如:231,权为:10,位是:2,位权1为10^0,位权3为10^1,位权2为10^2
4、    进制转换
    十进制转X进制:连除倒取余数(取余数是从下往上取)

    用通俗的话讲:十进制的某个数转化为其他进制时,将该数除以进制,依次将余数写在除法每一步的旁边,一直除完为止。则所对应的转化后的值,从下往上按顺序记录余数,该余数即为转化后的结果。



X进制转十进制:按权求和

用通俗的话讲:用位权的计算方法,将某进制的数,从第0位开始,把第0位上得数*进制(权)的0次方,加上第1位上得数*进制(权)的1次方….以此类推,直至该进制的数的最高位,所得到的和,即为转化所得的十进制的结果。

进制修饰(区别什么数是什么进制):
➢    十进制:什么都不加修饰 如:111
➢    八进制:在前加0  如:0111
➢    十六进制:在前加0X   如:0X111
➢    二进制:在前加0b 如:0b111
当程序员不想自己用笔来计算较大的进制转换时,点击右上角搜索按钮spotlight 搜索“计算器”。command+1(简单型计算器)2为科学型,3为编程型




2015年03月26日14:31:45
5、    基本数据类型
记忆基本数据类型的时候,这样记——三大类:
➢    字符:
(关键字)char :字符型  占字节:1 = 8位 写得出来的都是字符’1’ ‘2’ ‘A’ 。对应十进制0~255之间任意 任意一个数都可以表示,但是>256的不能存储。

➢    整型:
(关键字)short 短整型 : 字节:2 = 16位 0~65535
(关键字)int 整型 :      字节:4 = 32位 0~2^33-1
(关键字)long 长整形 : 字节:8或4(C语言没有给一个明确的数字,而是这样定义的,long型的数不小于int型的数。Int型的数不小于short型的数)

➢    浮点:
(关键字)float 单精度浮点型 :  字节:4
(关键字)double 双精度浮点型 : 字节:8

字节:1字节 = 8位

看一个数是正数还是负数,看最高位是啥:0为正,1为负


6、    常量
不可改变的量。
如: 719,‘A’,‘a’等等。        
7、    变量
变量:变量相当于一个容器或位置.
//定义一个变量
        //整型
        int num1 = 10;
        //短整型
        short s1=111;
        //长整型
        long l1=123456789;
        //浮点型(单精度)
        float f1=1.2;
        //浮点型(双精度)
        double d1=2.22;
        //字符型
        char c1= '1';
        


变量命名规则

➢    数字0~9,下划线_,字母 a~z,A~Z 组成,不能以数字开头
➢    不可以用系统保留字
➢    不能使用重复变量名
➢    见名之意。(规范)定义一个变量,要明显知道变量名的意思。

赋值运算符:=  可以给一个变量赋值。

//赋值运算符:=,可以给一个变量赋值。
        //交换两个变量中的值。
        int number1=10;
        int number2=20;
        //准备另一个变量,用于临时存放
        int temp=0;
        printf("前number1:%d \n",number1);
        printf("前number2:%d \n",number2);
        
        temp=number1;
        number1=number2;
        number2=temp;
        
        printf("后number1:%d \n",number1);
        printf("后number2:%d \n",number2);
        
课后思考题,交换两个变量的值,不使用第三个变量。
提示:有两种方式。




8、    算术运算符
算数运算符,主要是看什么,主要是看+,-,*,/ ,++,--,这几个运算符的用法,+,-,*就不说了,/(除法)主要是要看除数不能为0,虽然计算机不报错(因为语句都符合C语言的标准,程序会执行。),但是在运行后,给出的结果不正确。
++和—运算比较特殊,应该这样记:++在前则先++,++在后则后++。--和++一样。
要明白一点就是,运算过程中,会出现自动数据类型转换。
//+号
//        //n1=30
//        int n1 = 10+20;
//        //n2=40;
//        int n2 = n1+10;
//        //n3=70
//        int n3=n1+n2;
//        //n4=140;
//        int n4= n1+n2+n3;
        
        // -号
//        // n1=10;
//        int n1 = 30-20;
//        //n2=0;
//        int n2 = n1-10;
//        //n3=-10;
//        int n3 = n2-n1;
//        //n4=-20;
//        int n4 = n3-n2-n1;
        
        // *号
        
        //除法:/
        
//        //使用除号/,注意0不能坐除数! xcode不报错,只提出警告,并能运行。
////        int n1 = 10 / 0;
//        //除号两边都是整数,结果也是整数,不论除尽的小数点后面是什么值,都舍去。
//        int n2 = 3 / 2;
//        //除号两边都是浮点型,结果也是浮点型。n3=1.5
//        float n3 = 3.0/2.0;
//        //参与运算的数,如果 运算符 两边,只要有一个是浮点型,结果都为浮点型。n4=1.5
//        float n4 = 3/2.0;
        
        //取余 %
        
//        //n1 = 1
//        int n1 = 3 % 2;
//        //n2 = 0
//        int n2 = 4 % 2;
//        
//        //取余运算符两边的数,必须都为整型;
////        int n3 = 5.0 % 2.0;
//        
//        //取余之后的结果 < 除数,一定小于除数。
        
        
        //++在后
//        //n1=0
//        int n1 = 0;
//        //n1=0
//        //如果++在后面,只要这条语句没有执行完,n1++都表示原来的值。
//        //n3=0;
//        int n3 = n1++;
//        //n2= 1
//        int n2 = n1;
        
        //++在前
        
//        int n1 = 0;
//        //++在前,先加1,再赋值。n2=1
//        int n2 = ++n1;
        
        //符合运算符
//        int n1 = 10;
//        int n2 = 30;
//        n2 = n2 + n1;
////        n2 += n1;与n2 = n2 + n1;等效;


9、    表达式
表达式:常量、变量与运算符的组合。
例如:3,3+1,n1+1,3+(也是表达式,但是是错误的表达式)

表达式都会有返回值。

语句是程序执行的最小单位,以分号结尾。
例如:1; 1+7;

不以分号结尾的,不管前面写的再好,再标准,都不是语句。
10、    基本输入输出
基本输入输出函数    ,在C语言的第一个hello world程序里,有printf(“hello world”);这个语句,printf是标准输出函数,虽说打印语句都是程序里最简单的一个方法,但是在C语言中,printf函数输出,不仅仅是一个函数而已,它是由先人用汇编语言把计算机内存和显存的各种机理封装在了printf函数中,这个函数是在stdio.h这个库文件中的。
库文件:即为前人写好的代码,放在了一起,我们可以直接用。
输出:printf();
形式是这样的:printf(“%d”,100)这个是输出整型;printf(“%f”,100.0)这个是输出浮点型;printf(“%c”,’100’)这个是输出字符型的数据。
这个时候,输出整形还可以分:printf(“%sd”,100)这个输出短整型;printf(“%ld”,100)这个输出长整型。
输出函数printf还有一个要注意的地方:
➢    %和d中间的数字,代表预留几个位置:从右往左算。
➢    如果预留位置小于实际位置,按照实际位置来。
➢    如果预留位置大于实际位置,在输出的左边补上空格。
➢    如果数字前加0,则把空格变为补0;
例子:printf("%05d",12);输出结果:00012
➢    .X表示保留小数点后X位,%4.2f表示保留小数点后2两位。.前为代表预留几个位置。
例如:printf("%4.2f",1.2345);//1.23
➢    小数点也站位。
例如:printf("%4.1f",1.2345);//_1.2(下划线代表空格)
➢    正数表示右对齐(如果给出预留位大于实际位,左边空格代替),负数表示左对齐(如果给出预留位大于实际位,右边空格)。
例如:printf("%-4dabc",123);//123_abc(下划线代表空格)
另外C语言推出一些解决输出转义字符的办法
printf("123\n456");//换行。
        
printf("\\");//打印\
        
printf("%%");//打印%

输入:scanf()
在用户输入一个数据时,需要在程序里定义一个变量来接收这个输入进来的值。
//        int n1 = 0;
//        printf("请输入一个数字,按回车结束:");
//        scanf("%d",&n1);
//        printf("n1 = %d",n1);
值得注意的是,scanf("%d",&n1);中变量需要用&n1表示。&表示取地址。后面会介绍。
关于输入数据类型,也需要注意,如果输入的数据类型和程序里定义的变量数据类型不匹配,则定义的变量拒绝接收输入进来的数据。
在输入函数中,与输出函数还有一个区别就是:输出函数的double类型占位符,用%f,但是输入函数的double类型占位符是%lf。
//        double d1 = 0;
//        scanf("%lf",&d1);
输入函数接收多个参数时,可以这样写:但是不推荐。
        //接收多个输入值。
//        int num1 = 0;
//        int  num2 = 0;
//        printf("请输入两个数字,按回车结束:");
        //不推荐这么写
//        scanf("%d%d",&num1,&num2);
//        printf("num1 = %d , num2 = %d",num1,num2);

推荐的时分开接收:
        //推荐分开写
//        int num1 = 0;
//        int  num2 = 0;
//        scanf("%d",&num1);
//        scanf("%d",&num2);

scanf中,不推荐写除了占位符以外的东西,因为输入的时候需要一一对应输入,否则不接收输入数据。
例如:scanf(“sum = %d”,&n1);则需要在控制台输入:sum=100,才能匹配,n1才能接收到数据,否则拒绝接收数据。
Scanf中的占位符,不能加\n,否则在输入数据时匹配回车将会和\n(换行)进行匹配后,无法得出结果。

















——大海
2015年03月26日21:05:54 北京

2015年03月27日09:44:50
11、分支结构
在学分支结构之前,要知道一点就是C89标准中没有BOOl这个类型,因此需要将标准升级为C99标准。

C99标准在Xcode中,在新建一个工程的时候,选择type时,选择doundation。
创建工程之后,会发现,与之前的main.c的后缀已经变为main.m。这是objective-C环境下的后缀。
与之前.c的页面中,还有不同的是:
将#include<stdio.h>换成
#import <Foundation/Foundation.h>
//import是oc语法,作用和include是一样的。都是引入一个文件。

//Foundation/Foundation.h 是系统提供的一些功能,可以直接用,比stdio.h所包含的东西多得多。


12、bool数据类型
bool数据类型,只有yes和no两个值,非真即假。在C语言中,认为,非0即为真。
用处:用于if语句的时候和循环判断的时候。

在编译过程中,C语言将bool的yes值编译成1存在内存中,将no值编译成0,保存在内存中。
#define yes  1
#define no  0


13、关系运算符
关系运算符主要就是两者之间的比较,> (大于),>= (大于等于),< (小于),<= (小于等于),== (等于),!=(不等于) 。比较得出的结果是bool类型的值,用bool类型变量来存储。其他没什么了。
14、逻辑运算符

逻辑运算符就三个:与(&&),或(||),非(!)
参与运算的数,在符号两边都是bool值 。得出的结果不是真就是假。

&& 逻辑与:只要有一个为假,全都为假。
|| 逻辑或:只要有一个为真,结果都为真。
! 逻辑非:取反。
短路现象:
逻辑与运算的时候,如果系统检测到&&前的值是no,那么将不再去判断&&号后面的值,此时,如果&&后的值有赋值运算,赋值运算也不执行。
逻辑或运算的时候,如果系统检测到 || 号前的值是yes ,那么将不再去判断 || 号后面的值,此时,如果 || 后的值有赋值运算,赋值运算也不执行。
    //短路现象
    //逻辑与,如果前面判断是假,后面的判断不再执行。
//    int num1 = -1 ;
//    int num2 = -1;
//    BOOL b3 = (num1 = 0) && (num2 = 2);//假
//    
//    printf("%d",b3);//0
//    printf("num1 = %d \n",num1);// 0
//    printf("num2 = %d]\n",num2); // -1
    
    //短路现象
    //逻辑或,如果前面判断是真,后面的判断不再执行。
//    int num1 = -1 ;
//    int num2 = -1;
//    BOOL b3 = (num1 = 2) || (num2 = 0);//真
//    
//    printf("%d",b3);//1
//    printf("num1 = %d \n",num1);// 2
//    printf("num2 = %d)\n",num2); // -1


15、if语句
程序的三种结构:
顺序结构:按顺序执行程序,直到完事为止。
分支结构:if语句和switch
循环结构:循环的执行一段语句。直到循环条件结束为止。
分支结构有两个: if语句和switch语句
if语句有三种方式:
(1)if(条件表达式){
}
    //分支结构
    //if的(第一种形式)
    
//    BOOL b1 = NO;
//    if (/*条件表达式:要明确的表明条件是真是假*/b1){
//        printf("你个大骗子。。。");
//    }
    //练习
    /*
     从控制台输入一个整数。
     判断这个整数是否>100。
     如果大于100,输出:“这个数大于100”
     */
//    int num = 0;
//    printf("请输入一个整数,按回车结束:\n");
//    scanf("%d",&num);
//    if(num > 100){
//        printf("你输入的数为:%d \n",num);
//        printf("这个整数大于100\n");
//    }
//    char c1 = 0;
//    printf("输入一个字符,按回车结束:\n");
//    scanf("%c",&c1);
//    BOOL b1 ='m' == c1;
//    if(b1){
//        printf("输入的是男性");
//    }


(2)if(条件表达式){
}else{
}

/    char c2 = 0;
//    printf("请输入字符:");
//    scanf("%c",&c2);
//    if ('m' == c2) {
//        
//        printf("正太");
//    }else{
//        printf("萝莉");
//    }
    
//    int year = 0;
//    printf("请输入年份,按回车结束:");
//    scanf("%d",&year);
//    
//   //判断闰年的条件
//    BOOL b1 = (year % 400 == 0 ) ;
//    BOOL b2 =(year % 100 !=0) && (year % 4==0);
//    if(b1 || b2){
//        
//        printf("%d\t是闰年\n",year);
//        
//    }else{
//        printf("%d\t是平年!",year);
//    }


(3)if(条件表达式){
}else if(条件表达式){
}else{
}
    
//    char c3 = 0;
//    printf("输入一个字符:");
//    scanf("%c",&c3);
//    
//    if('m' == c3){
//        printf("男性");
//    }else if ('f' == c3){
//        printf("女性");
//    }else{
//        printf("春哥");
//    }
    
    /*
     输入一个字符,判断
     s:春天
     m:夏天
     a:秋天
     w:冬天
     其他:输入有误。
     */
//    char c1 = 0;
//    
//    printf("输入一个字符,按回车结束:");
//    
//    scanf("%c",&c1);
//    
//    if('s' == c1){
//        printf("我是春天!");
//    }else if('m' == c1 ){
//        printf("我是夏天!");
//        
//    }else if('a' == c1){
//        printf("我是秋天!");
//        
//    }else if ('w' == c1){
//        printf("我是冬天!");
//    }else{
//        printf("输入有误!!");
//    }


16、条件表达式
    //条件表达式
//    int num1 = 10;
//    int num2 = 20;
//    
//    int num3 = num1>num2 ? num1 : num2;
//    
//    printf("num3 = %d",num3);
    
    //课后练习题
    /*
     使用条件运算符,选择出三个数中,哪个最大。
    */
洞房花烛夜 我 两小无猜
字符型就是整型。

Switch 分支语句
Switch分支语句就是为了优化if(){}else if(){}过多条件。
    //switch分支语句
//    switch (apple) {
//        case apple:
//            printf("我是苹果也!");
//            //break:终止往后继续执行
//            break;
//        case banana:
//            printf("我是香蕉也!");
//            break;
//        case orange:
//            printf("我是橘子也!");
//            break;
//        case watermelon:
//            printf("我是西瓜也!");
//            break;
//        default:
//            printf("我不是水果也!");
//    }

与else if一样的效果。
注意的是:switch ()括号中的条件必须为整型条件表达式。在每一个case下的语句代码,最后都要加上break,用来跳出switch语句。
    //练习
    
//    int season = 0;
//    printf("请输入一个数字,按回车结束:");
//    scanf("%d",&season);
//    
//    switch (season) {
//        case 1:
//            printf("春天啊我是!spring");
//            break;
//        case 2:
//            printf("夏天啊我是!summer");
//            break;
//        case 3:
//            printf("秋天啊我是!autumn");
//            break;
//        case 4:
//            printf("冬天啊我是!winter");
//            break;
//            
//        default:
//            printf("啥也不是啊我!nothing");
//            break;
//    }

17、枚举enum
#import <Foundation/Foundation.h>
//枚举,建议写在这个地方。
enum season{
    //第一个元素,默认为0.后面元素依次+1.spring 自动被赋值为0,summer 为1.
    //每个值都可以改变默认值,后面的数还是依次+1.初始化过的默认值,后面的数还是依次+1
    spring = 100, //100
    summer = 200, // 200
    autumn, //201
    winter //202

};
// 练习,定义一个枚举

//定义五个水果:苹果(10),香蕉,橘子,西瓜
enum fruits{
    apple = 10,
    banana,
    orange,
    watermelon
};
int main(){
}




















——大海
2015年03月27日20:38:00 北京



18、循环
特点:在满足特定条件的情况下,反复执行某程序段。
While循环
While循环的格式是这样的:while(条件表达式){语句代码},意思是满足括号内的条件时,执行花括号内的语句代码。或者更专业一点来说,当括号内的条件表达式返回值为真时,执行花括号内的代码,一直到条件表达式的返回值为假时,跳出循环。

While循环很容易出现死循环这种状况,就是因为忘记写了“增量”这个东西。
//死循环
int count = 0 ;
//    while (count < 100) {
//        printf("今天我真帅...\n");
//    }
上面的代码就是少了count++,这个增量,所以,条件表达式一直满足,就一直执行,就造成了死循环。
此时,应该这样改进:
    //循环变量 :控制循环次数
//    int count = 0;
//    while (/* ++ 在前,打印两次 */count/* ++ 在后,打印三次*/ < 3 ) {
//        
//        printf("今天我真帅...\n");
////        count = count +1;
//        
//        //此处,++ 在前在后都不影响打印次数。
//        
//        //循环增量
//        count ++;
////        ++ count;
//    }
一些例子:
    //练习
    //打印1~100
//    int num = 0;
//    while (num < 100) {
//        printf(" %d \n",(num + 1));
//        
//        num ++;
//    }
    
    //用while 打印能被7整除的1~100之间的数。
//    int num = 1;
//    while (num <= 100) {
//        
//        if(num % 7 == 0){
//            printf("%d \t",num);
//        }
//        num ++;
//    }
    
    //用while循环打印出1~100之间各位为7的数。
//    int num = 0;
//    
//    while (num < 100) {
//        
//        if(num % 10 == 7){
//            printf("%d \t",(num));
//        }
//        num ++ ;
//    }
    
    //用while循环打印出1~100之间十位为7的数。 num / 10 == 7
    
//    int num = 0;
//    while (num < 100) {
//        if(num / 10 ==7){
//            printf("%d \t",num);
//        }
//        num ++;
//    }

Do-while循环
与while不同的只有一个地方,就是先执行一遍代码,再进行判断。也就是说,不管你while的条件表达式成不成立,返回值为不为假,我都执行一遍循环体的代码。
        // do while
    
//    do{
//        printf("至少执行一次,不管后面的判断条件是真还是假");
//    }while (1) ;// ; 分号不能丢

随机数arc4random()
产生随机数的方法,arc4random()可以产生int范围内的任意一个随机数,包括有正数和负数,为什么会出现负数呢?因为,在存储的时候,生成的数的最高位的数字为1,此时,会认为这个1是符号位的负,因此会出现负数。这时,我们应该把这些情况避免掉,在输出之前,用unsigned 来定义一个变量来接收产出的随机数;在输出的过程中,占位符不再用%d,而是用%u来代替。
另一个值得注意的是,随机数产生的时候,数字会很大,而我们在代码过程中,不需要这么大的数,此时,想要把这个数减小,可以用取余的办法来限制。
    //随机数 arc4random(),
    //用%u来打印,表示无符号。
    //由于随机函数给的值很大,我们采用取余的方法,使值变小。 取余的随机数的范围就是,0~被除数-1
    
    
//    printf("%u \t", arc4random() % 10);//打印 只有0~10的数

在产生一个随机数的时候,可以让它在固定的区间内产生,那么就会用到这个公式:
    //取某区间[a,b]的数,用公式:arc4random() % (b-a+1)+a
若是规定在负空间生成随机数,那么就将公式改成:
/arc4random() % (b-a+1)-a
一些例子:

//用户输入一个N,用while打印N个随机数(10~30)
//    int num = 0;
//    int count = 0;
//    printf("输入一个数:");
//    scanf("%d",&num);
//    printf("产生 %d 随机数为: \n\t",num);
//    while (count < num) {
//        
//        //unsigned 声明一个无符号int 类型。
//        unsigned unum = (arc4random()%((30-10+1)+10));
//        printf(" 第 %d 个数为:%d \t",(count+1), unum);
//        count ++;
//    }
    
    //输入一个数,用while打印n个随机数(30~70),找出随机数中最大值。
    
//    int num = 0;
//    printf("输入一个数:\n");
//    scanf("%d",&num);
//    int count = 0;
//    int max = 0;
//    while (count < num ) {
//        unsigned unum = (arc4random()%((70-30+1)+30));
//        printf(" 第 %d 个数为:%d \n",(count+1), unum);
//        
//        if(max < unum ){
//            max = unum;
//        }
//        count ++;
//    }
//    printf("\n ");
//    printf("最大的数为:%d \n",max);

Break和continue
这两个关键字在开发过程中经常遇见,他们的区别如下:
break:在某个循环的时候,用上break,意思就是停止这个循环,不再执行这个循环,我要跳出这个循环。
continue :在某个循环的时候,在过程中用上continue,意思就是,我在的这个循环还在运行,但是我不运行这一次的循环,我要跳到下一次的循环去,但是还是在这个循环里,没有跳出去,只是不执行这次罢了。
   //循环变量
//    int count = 0;
//    while (count <10) {
//        
//        count ++;
//        
//        if(count == 3){
//            //结束本次循环
//            continue;
//            /*
//             输出结果:
//             
//             第 1 天
//             第 2 天
//             第 4 天
//             第 5 天
//             第 6 天
//             第 7 天
//             第 8 天
//             第 9 天
//             第 10 天 */
//        }
//        if(count == 5){
//            //结束循环
//            break;
//            
//            /*
//             输出结果:
//             
//             第 1 天
//             第 2 天
//             第 4 天 */
//        }
//        
//        printf("第 %d 天 \n",count);
//        
//    }


for循环
for循环和while差不多,但是是将条件和增量,循环变量都一起放在了小括号里。
值得注意的是:while与for的比较时,for的一个好处
相比于while 循环:while 中存在浪费内存的情况,因为循环变量在while循环外边,直到它所在的花括号结束,才释放内存。而 for循环 的循环变量 在for循环结束后,即释放。
for循环的执行过程是这样的:
for(①int i= 0 ; ②i < 100;④i++){
    ③循环体
}
在运行到本处时,先进行①赋初值,然后判定条件,满足则跳进循环体执行代码③,执行循环体代码结束后,对i进行自增④i++,然后进行②判断,执行③,自增四。。。如此循环下去。

   // for 循环
//    int count = 0;
//    while (count < 5) {
//        printf("\n我是 while 循环。。。");
//        count ++;
//    }
    
    // for(循环变量 ; 条件 ; 增量){ 循环体 }
//    for (int i = 0;i < 5; i++) {
//        printf("\n我是 for 循环哦。。。");
//    }
    
    //练习
    
    //打印 0 ~100
    
//    for (int i = 0; i <= 100; i ++) {
//        printf("%d \t",i);
//    }
    //打印1~100
    
    
    // 相比于while 循环:while 中存在浪费内存的情况,因为循环变量在while循环外边,直到它所在的花括号结束,才释放内存。
    // 而 for循环 的循环变量 在for循环结束后,即释放。
//    for (int j = 0; j < 100; j ++) {
//        printf("%d \t",(j + 1));
//    }
    
    //打印 ***
//    for (int i = 0; i < 3; i ++ ) {
//        printf("*");
//    }


循环嵌套
当我们发现,一个循环自身又要循环多次时,用循环嵌套:循环里有循环。
    //打印
    /*
     ***
     ***
     ***     
     */
    
    //两层for循环
    //外层for:负责控制行
//    for (int j = 0; j < 3; j++) {
//        //内层 for 负责每行打印多少个
//        for (int i = 0; i < 3; i ++ ) {
//            printf("*");
//        }
//        printf("\n");
//    }
    
    //打印
    /*
     1
     1 2
     1 2 3
     */
    // 里边的个数跟行数一样,(第一行有一个,第二行有2个。。。)那么只要 i <= j 就可以了。
    
    
//    for (int j = 1; j <= 3; j++) {
//        for (int i = 1; i <= j; i ++) {
//            printf("%d ",i);
//        }
//        printf("\n");
//    }
    
    
    //打印乘法口诀表
//    for (int i = 0; i < 9; i ++) {
//        //列
//        for (int j = 0; j <= i ; j ++) {
//            printf(" %d * %d = %d \t",j+1,i+1,(j+1)*(i+1));
//        }
//        printf("\n");
//    }    
    //三维数组合:
    // 三个for循环
    //百位 1~9
    for (int i = 1; i <= 9 ; i++) {
        //十位 0~9
        for ( int j = 0 ; j <= 9; j++) {
            //个位 0~9
            for (int k = 0 ; k <= 9 ; k++) {
                printf("%d\n",i * 100 + j * 10 + k);
            }
        }
    }

















——大海
2015年03月30日20:14:46 北京











19、数组
数组:相同的数据类型成员组成的数据。
如:整型数组,浮点型数组。
数组的形式为:
类型修饰符 数组名[常量表达式] = { 1,2,3……..};

说明:数组在定义的时候,[ ]里必须用常量表达式,不能用变量表达式来代替。但是数组在使用的时候,[ ]中可以用变量来代替。

数组和循环是一对好基友。

数组的几个注意的点:
1、只有定义时,[ ]内为常量表达式。不能放变量。
//    int n =3;
//    int arr1[n] = {10,20,30};//报错

2、[ ]可以放算术式。
    
//    int arr2[1+2] = {10,20,30};

3、初始化时可以放多余空间的元素(但是这是不对的)
//    int arr3[3] = {10,20,30,40};

4、定义时,可以不给出常量表达式。(数组可以通过元素个数推算空间)。给出多少个元素,会自动给你分配多少空间
//    int arr4[] = {10,20,30};
5、前三个空间值分别为10,20,30,后面的7个空间全部清0,为0。

//    int arr5[10] = {10,20,30};
6、数组初始化
//    int arr6[3] = {0};
//    //错误的
//    int arr7[] = {0};

一些例子:
    //练习
    
//    float arr_f[3] = {3.1,5.2,7.9};
    
    
    //使用数组
//    int arr8[3] = {10,20,30};
    
//    printf("%d",arr8[0]);
//    printf("%d",arr8[1]);
//    printf("%d",arr8[2]);

数组取值:
    //使用数组可以用下标取值
    //下标可以使用变量
//数组可以使用循环取出所有值
//    for (int i = 0; i < 3; i ++) {
//        printf("%d \n",arr8[i]);
//    }
越界
1、存储的时候越界:
    
    //第一种,存储的时候越界
//    int arr9[3] = {10,20,30,40};//此时,40 已经被写入内存,但是取值的时候,40所在的内存空间已经被修改,所以取值的时候,很难取到40
//    
//    for (int i = 0; i < 4; i ++) {//循环4次,打印10,20,30,0(不同的计算机打印出来的值不同)
//
//        printf("%d\n",arr9[i]);
//    }
//  

2、使用的时候越界
    //第二种,使用的时候越界
    
//    int arr10[3] = {10,20,30};
//    
//    arr10[0] = 50;//自己定义的数组,可以随便改。
//    
//    printf("%d",arr10[0]);//打印50
//    
//    
//    arr10[3] = 50;//可以访问之后的任意空间,本数组的第4个内存空间位置,被改动。
//    
//    printf("%d",arr10[3]);//打印50

C语言可以通过我们定义的数组,操作本数组之后的所有内存空间,那些空间不属于这个数组,谁都可以去用。因此可以在上面进行读写。这样就会使得程序不安全。

数组越界是一件很危险的事情,我们无法把控,因此我们在操作程序是,尽可能的避免越界产生。
数组是一个整体,不能直接参与运算,只能对各个元素进行处理。

一些代码:
//练习
    
    //1、定义一个具有20个元素的整型数组,30~70 间的随机数,求和。
    
//    //定义数组,并清零,初始化。
//    int array[20] = {0};
//    // 求和变量
//    int sum = 0;
//    printf("20个随机数为:\n");
//    //产生20 个随机数
//    for (int i = 0; i < 20; i++) {
//        unsigned num = arc4random() % (70-30+1)+30;
//        printf("%d\t",num);
//        array[i] = num;
//        //不要在这个地方求和,一个for一个功能。增加代码的重用性。
////        sum = sum + array[i];
//    }
    
//    //求和
//    for (int i = 0; i < 20 ; i ++) {
//        sum += array[i];
//    }
//    
//    
//    printf("\n\n20个数的和 sum = %d",sum);
    
    
    //2、复制数组。
//    int array1[5] = {10,20,30,40,50};
//    int array2[5] = {0};
//    for (int i = 0; i < 5; i ++) {
//        array2[i] = array1[i];
//        printf("%d\t",array2[i]);
//    }
    


排序
冒泡排序
从第一个数开始,用第一个数分别与后面的数进行比较,若是大于后面的数,则将该数放在后面。然后再用第二个数跟后面的数比较,若大于后面的数,则将该数放在后面,依次类推,一直到最后一个数比较完毕为止。此时,排序已经完成。
    //数组排序
    
//    //冒泡排序(只适合小范围的数据排序)(20遍)
//    int sort_arr[5] = {5,4,3,2,1};
//    //外层循环,控制排序趟数,趟数为 :数组元素个数-1
//    for (int i = 0; i < (5-1)/*5-1,表示数组有n个数比较,只比较n-1趟*/; i ++) {
//        //内层for循环,控制比较次数
//        for (int j = 0; j < 5 - (i+1)/*本来应该是5-i,但是i是从0 开始的,我们要得是从1开始,因此要5-(i+1)。此处的意思是每一趟,比较多少次。*/; j ++) {
//            //判断,并且交换。
//            if (sort_arr[j] > sort_arr[j+1]) {
//                //交换,不使用第三个变量交换,用亦或 ^ 的方法最好。
//                sort_arr[j] = sort_arr[j] ^ sort_arr[j+1];
//                sort_arr[j+1] = sort_arr[j] ^ sort_arr[j+1];
//                sort_arr[j] = sort_arr[j] ^ sort_arr[j+1];
//            }
//        }
//    }
//    //打印
//    printf("冒泡排序后:");
//    for (int i = 0; i < 5; i ++) {
//        printf("%d\t",sort_arr[i]);
//    }
    
    //练习
//    //随机产生10个[20,40]间的数,排序
//    
//    int array[10] = {0};
//    printf("排序前的10个数为:\n");
//    //取随机数
//    for (int i = 0; i < 10 ; i ++) {
//        unsigned num = arc4random() % 21+20;
//        array[i] = num;
//        printf("%d\t",array[i]);
//    }
//    
//    //排序开始
//    //外层for循环,控制比较多少趟。
//    for (int i = 0; i < 10-1; i ++) {
//        //内层for循环,控制比较多少次
//        for (int j = 0; j < 10 -(i+1); j ++) {
//            //冒泡排序
//            if (array[j] > array[j+1]) {
//                //亦或,用来交换两个变量的值。
//                array[j] = array[j] ^ array[j+1];
//                array[j+1] = array[j] ^ array[j+1];
//                array[j] = array[j] ^ array[j+1];
//            }
//        }
//    }
//    printf("\n排序后的10 个数为:\n");
//    for (int i = 0 ; i < 10 ; i ++) {
//        printf("%d\t",array[i]);
//    }


字符数组
第一种定义:
char array[5] = {'h','e','l','l','o'};
第二种定义:
char array1[6] = "hello";
第二种定义在定义的时候,自动添加一个'\0'。这个\0有着特定的意义,在接下来输出这个数组时,将会用%s来作为占位符,当%s在输出数组时,遇到了’\0’这个特定意义的“结束标识符”时,就会终止打印,停止输出。

此时,数组的实际长度为:字符数组元素个数+1,要留出'\0'的位置。
//    //%s占位符
////    for (int i = 0; i <5 ; i++) {
////        printf("%c",array[i]);
////    }
//    printf("%s",array);//打印:hello
一些情况:
//    //自动推算出有6个位置
//    char array2[] = "hello";
//    
//    
//    //前面补满,后面补0
//    char array3[10] = "hello";
    
    //存储打印中文
//    char array3[20] = "蓝欧🍰🐤";
//    printf("%s",array3);//打印:蓝欧🍰🐤
字符串操作函数
长度:
    //1、长度
//    char s1[] = "Lanou";//
//    unsigned long num1 = strlen(s1);//不包括\0
//    
//    printf("%lu",num1);//输出:5
    
//    char s1[] = "蓝欧";//数组占7个字节,字符占6个字节。
//    unsigned long num1 = strlen(s1);//不包括\0
//    
//    printf("%lu",num1);//输出:6,汉字占3个字节
拷贝

    //2、拷贝
    
//    char s2[] = "Lanou";//6个字节
//    //接收的字符数组,一定不能比原数组小。
//    char s3[6] = {0};
//    //第一个:拷贝到哪
//    //第二个:从哪拷贝
//    strcpy(s3, s2);
//    printf("%s",s3);
字符串拼接
    //3、字符串拼接
    //被拼接的字符串,空间一定要够大。
//    char s4[10] = "Lanou";
//    
//    char s5[] = "V5";
//    
//    //第一个是被拼接的是谁,s4(被拼接的字符数组一定要够大)
//    //第二个是拼接什么,s5
//    
//    strcat(s4, s5);
//    printf("%s",s4);//输出:LanouV5
比较
    //4、比较
    //找到第一组不相等的两个字符,比较ASCII码值
    //结果为正:第一个字符数组大
    //结果为负:第二个字符数组大
    //结果为0:两个字符数组相等
//    char s6[] = "bba";
//    char s7[] = "baa";
//    
//    int i = strcmp(s6, s7);//
//    printf("%d",i);




——大海
2015年03月31日22:30:08 北京


20、二维数组
⼆维数组:有两个下标的数组。
定义:类型 数组名[常量表达式1] [常量表达式2] = {值1,值2...};
用普通的话来说,就是,二维数组的第一个[ ]表示行,第二个[ ]表示 列。
➢    数组的第一种定义方式:
//    //定义二维数组 的 第一种方式(不直观,不能直接看出第一行是在哪里开始结束)
//    int array[2][3] = {1,2,3,4,5,6};
数组第二种定义方式:

    //定义二维数组 的 第二种方式(直观)
    
    //花括号{},可以区分行{1,2,3}为一行,{4,5,6}为一行
//    int array[2][3] = {{1,2,3},{4,5,6}};
//    
//    for (int i = 0; i < 2; i++) {
//        for (int j = 0 ; j < 3; j ++) {
//            printf("%d",array[i][j]);
//        }
//        printf("\n");
//    }
数组第三种定义方式:(注意:定义二维数组时,可以省略行数,但是一定不能省略列数。元素个数不⾜,会⾃动补0。)
    // 第三种:可以省略行,但是一定不能省略列
//    int array[][] = {1,2,3,4,5,6};//报错
//    int array[][] = {{1,2,3},{4,5,6}};//错误的
//    int array[2][] = {1,2,3,4,5,6};//只给行,报错
    
//    int array[][3] = {1,2,4,5,6};//只给列,正确
//    //打印
//    for (int i = 0; i < 2; i++) {
//        for (int j = 0 ; j < 3; j ++) {
//           printf("%d",array[i][j]);
//        }
//            printf("\n");
//    }
//    //打印结果: 124
//    //           560 自动补0
//    int array1[][3] = {{1,2},{3,4},{5,6}};
//    
//    for (int i = 0; i < 3; i++) {
//        for (int j = 0 ; j < 3; j ++) {
//            printf("%d",array1[i][j]);
//        }
//        printf("\n");
//    }
//    //打印
//    //120
//    //340
//    //560


一些练习:
定义一个三行两列的维维数组
    //练习
    //定义一个三行两列的维维数组
//    int array[3][2] = {{75,60},{62,90},{60,50}};
//
//    for (int i = 0; i < 3; i++) {
//        for (int j = 0 ; j < 2; j++) {
//            printf("%d\t",array[i][j]);
//        }
//        printf("\n");
//    }
//    //打印结果
//    //500    450
//    //62    90
//    //66    50
//    //求和
//    int total = 0;
//    for (int i = 0; i  < 3; i ++) {
//        for (int j = 0 ; j < 2; j ++) {
//            total += array[i][j];
//        }
//    }
//    printf("我们组的总体重为:%d 克",total);
将一个数组的行和列交换,放在另外一个新的数组中去:
    //将⼀个⼆维数组的⾏和列交换,存储到另外⼀个数组中去。
    
//    int arr1[2][3] = {{1,2,3 },{4,5,6}};
//    int arr2[3][2] = {0};
//    
//    for (int i = 0;  i < 3 ; i ++) {
//        for (int j = 0 ; j < 2;  j++ ) {
//            arr2[i][j] = arr1[j][i];
//            
//            printf("%d\t",arr2[i][j]);
//        }
//        printf("\n");
//    }

关键点:主要是搞清楚规律。这里的规律就是原来的数组的行和列调换位置,就可以得到新的数组。for循环,可以用后面数组来循环,也可以用前面的数组来循环。

定义一个3行4列的数组,找出数组的最大的元素,打印出来,并打印所在的行和列。
    //3行4列的数组,找出最大值,输出所在行列。
//    int arr[3][4] = {{1,2,3,4322},{5,6,777,8},{99,10,11,12222}};
//    //记录最大值和行列
//    int x = 0; // 行
//    int y = 0;  //列
//    int max = 0; //最大值
//    for ( int i = 0 ; i < 3; i ++) {
//        for ( int j = 0;  j < 4; j ++) {
//            if (arr[i][j] > max) {
//                max = arr[i][j];
//                x = i;
//                y = j;
//            }
//        }
//    }
//    printf("最大值为:%d",max);
//    printf("\n最大的数在第 %d 行,第 %d 列",x,y);
与普通的找最大值一个样子。
21、字符串数组
字符串数组本质上是一个二维数组
1、访问某个字符串:数组名[行号]
2、访问某个字符:数组名[行号][列号]
定义方式:
    //字符串数组
//    char arr1[3][10] = {"iPhone","Android","Windows"};
//    
////    printf("%c",arr1[0][0]);//输出:i
//    
////    printf("%s",arr1[2]);//只给行号就行。
//    
//    for (int i = 0 ; i < 3; i ++) {
//        printf("%s\n",arr1[i]);
//    }
//    //打印结果:iPhone
//                Android
//                Windows
打印字符串数组时,直接在for里给出行数,就可以了。不用两个for。
一些练习:
创建一个字符串数组(内容是你周围一圈人的姓名),输出最长的字符串长度。
    // 练习
    
    //创建一个字符串数组(内容是你周围一圈人的姓名),输出最长的字符串长度。
//    unsigned long length[20] = {0};
//    unsigned long max  = 0;
//    int x = 0;
//    char names[6][20] = {"nongdahai","qinxiaoqiang","sunqing","qinwei","lvlifeng","liuxiaoqing"};
//    for (int i = 0 ; i < 6; i ++) {
//        length[i] = strlen(names[i]);
//    }
//    for (int i = 0; i < 20 ; i ++) {
//        if(max < length[i]){
//            max = length[i];
//            x = i;
//        }
//    }
//    printf("%d\n",x);
//    printf("%lu\n",max);
//    printf("%s\n",names[x]);



字符串数组从小到大排序
    //字符串数组从小到大排序
//    char names[6][20] = {"nongdahai","qinxiaoqiang","sunqing","qinwei","lvlifeng","liuxiaoqing"};
//    
//    for (int i = 0 ; i < 6 -1 ; i ++) {
//        for (int j = 0; j < 6 -(i+1); j++) {
//            if(strcmp(names[j], names[j+1]) > 0){
//                char temp[20] = {0};
//                strcpy(temp, names[j]);
//                strcpy(names[j], names[j+1]);
//                strcpy(names[j+1], temp);
//                
//            }
//        }
//        
//    }
//    for (int i = 0 ; i < 6 ; i ++) {
//        printf("%s\n",names[i]);
//    }
//    //liuxiaoqing
//    //lvlifeng
//    //nongdahai
//    //qinwei
//    //qinxiaoqiang
//    //sunqing
22、三维数组
    //三维数组
    //2层,3行,4列
    int arr[2][3][4] = {0};
    //右左法则










——大海
2015年04月01日20:26:22 北京



23、函数
函数:具有特定功能的代码段。
函数的使用,可以省去很多重复代码的编写,大大简化程序,提高开发效率。
函数包括库函数和自定义函数(开发者自己写的函数)。
函数的定义,有以下四种:
1、无返回值无参型
//函数定义有四种形式
//函数是为了完成某项任务。
//任务材料: 参数
//交任务:返回值

//第一种:无参数,无返回值。

void sayHi(){
    printf("HI,约吗?");
}

2、有返回值无参型

//第二种:无参数,有返回值。

int numberOne(){
    
    //将结果返回
    //函数执行到return就结束。
//    return 10.10 ;////如果返回类型不匹配,不报错,但是结果不准了。(注意)
    return 1;
    //return后面的代码不会被执行
    printf("我还没出场呢。。");
    
}


3、无返回值有参型
//第三种:有参数,无返回值。
// 参数可以任意多个,
// 但是调用函数的时候,必须一一赋值。

void printSum(int num1,int num2){
    printf("%d + %d = %d",num1,num2,num1 + num2);
    
}


4、有返回值有参型
//第四种:有参数,有返回值。
// num1,num2(int ) 返回sum
//不知道num1,num2叫做形参
int sum(int num1 , int num2){
    
    return num1 + num2;
    
}

求1~n的加和:

#import <Foundation/Foundation.h>
//练习
//1、
//int sumValue(int n){
//    int sum = 0;
//    for (int i = 0; i <= n ; i ++) {
//        sum = sum +i;
//    }
//    return sum;
//}
int main(int argc, const char * argv[]){
//    printf("%d",sumValue(9));
return 0;
}

输入年月日,输出是在一年内的第几天:
switch方式:
//switch方式
/*
int dayOfYear(int year,int month,int day){
    
    int total = 0;
    
    switch (month) {
        case 1:
            total = day;
            break;
        case 2:
            total = 31 + day;
            break;
        case 3:
            total = 31 + 28 + day;
            break;
        case 4:
            total = 31 + 28 + 31 + day;
            break;
        case 5:
            total = 31+ 28 + 31 + 30 + day;
            break;
        case 6:
            total = 31+ 28 + 31 + 30 + 31 + day;
            break;
        case 7:
            total = 31+ 28 + 31 + 30 + 31 + 30 + day;
            break;
        case 8:
            total = 31+ 28 + 31 + 30 + 31 + 30 + 31 + day;
            break;
        case 9:
            total = 31+ 28 + 31 + 30 + 31 + 30 + 31 + 31 + day;
            break;
            
        case 10:
            total = 31+ 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + day;
            break;
        case 11:
            total = 31+ 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + day;
            break;
        case 12:
            total = 31+ 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + day;
            break;
        default:
            return 0;
            break;
    }
    
    return total;
}

数组方式:
*/
//数组方式
/*
int dayOfYear(int month , int day){
    int array[12] = {31,28,31,30,31,30,31,30,31,31,30,31};
    int sum = 0;
    for (int i = 0; i < month - 1; i ++) {
        sum += array[i];
    }
    sum += day;
    return sum;
}
*/
int main(int argc, const char * argv[]){
//    printf("%d",dayOfYear(12,11));
return 0;
}
函数调用:
我们在定义一个函数后,需要在其他函数里调用我们定义好的这个函数。才能实现这个函数的功能。
如上面的一些代码,我们在定义了函数后,在main()函数里调用了我们定义好的函数。这样就能把我们定义的功能体现出来。
函数声明:
函数声明的出现,是因为我们有时候在main()函数后面定义我们的函数,但是在main()函数里调用我们定义的函数,这样的话,在编译的时候,系统会报错,说是这个函数没有定义。此时,我们应该在main()函数前对我们定义的函数进行声明。声明形式为:我们自定义的函数的返回类型 函数名 参数:
void sum(int num);这个就是我们自定义的函数的函数声明。加上声明之后,main()函数调用自定义函数才不会报错。
//函数声明。
//若不声明,则下面会报错。
//void sayHi();

int main(int argc, const char * argv[]){
    
    //2015年04月02日14:31:27 北京
    
    //函数的调用
//    sayHi();
//    
    
    return 0;
}
//函数的定义
//void sayHi(){
//    printf("今天天气不错,约吗?");
//}
//int sum (int num1, int num2){
//    return num1 + num2;
//}
函数的声明,在Xcode里,这样隔开会比较好:新建一个Objective-C class的文件,命名好后,会生成一个.h和一个.m文件,我们将所有的函数声明放在.h的文件里,将函数的定义,放在.m文件里。
在main函数调用之前,需要将.h文件引用进main.m文件中,方式为:

//引入.h文件
#import "Hi.h"//自己定义的文件,用" "来引入。意思就是把Hi.h里的东西全部原封不动的复制到这个地方。
//.h文件不参与翻译。
//若干个.m文件在翻译时,是连成一块的。

#import "operator.h"
系统的东西用< >来引入
#import <Foundation/Foundation.h>
一些例子:
定义4个函数,实现 + — * /四个运算。
operator.h文件
//函数声明

int sum (int num1 , int num2);
int sub (int num1 , int num2);
int total (int num1 , int num2);
int mod (int num1 , int num2);
operator.m文件

//函数定义
//函数名不能重复。
//函数名的命名规则和变量是一样的。


//+
int sum (int num1 , int num2){
    return num1 + num2;
}

//-
int sub (int num1 , int num2){
    if(num1 > num2){
        return num1 - num2;
    }else{
        return num2 - num1;
    }
}

// *

int total (int num1 , int num2){
    return num1 * num2;
}

int mod(int num1 , int num2){
    if(num2 != 0){
        return num1 / num2;
    }else{
        printf("除数不为0");
        return -1;
    }
    
}


main.m文件

#import <Foundation/Foundation.h>// 系统的东西用< >来引入

//引入.h文件
//自己定义的文件,用" "来引入。意思就是把Hi.h里的东西全部原封不动的复制到这个地方。
//.h文件不参与翻译。
//若干个.m文件在翻译时,是连成一块的。

#import "operator.h"

//函数声明。
//若不声明,则下面会报错。
//void sayHi();

//函数声明时,参数名可以去掉,只留参数类型。
//int sum (int , int num2);//

int main(int argc, const char * argv[]){
    
    //2015年04月02日14:31:27 北京
    
    //函数的调用
    printf("加法:%d\n",sum(10,20));
    printf("减法:%d\n",sub(10,20));
    printf("乘法:%d\n",total(10,20));
    printf("除法:%d\n",mod(10,20));
    
    return 0;
}
编译过程:
在编译时,不对.h文件翻译,只对.m文件惊醒翻译。翻译时,将所有的.m文件连接成一块,然后进行翻译。
数组作为函数参数:
数组作为函数参数的时候,注意几点:
1、数组作为参数,不用给定一个数组长度(元素个数)。因为就算给定一个长度,在我们传进来的数组时,长度会根据我们传进来的数组的长度来确定实际长度。所以不用给定长度。
2、再在后面定义一个变量,用来装我们传过来的数组的长度。
形式如下:
void printArray(int arr[],int n){}
一些例子:
定义一个数组,用函数打印出来。用数组作为参数传递。
#import <Foundation/Foundation.h>
//打印数组
//数组作为参数的时候,不用给元素个数。
//再定义一个变量n,用来给出要便利多少次。
//void printArray(int arr[],int n){
//    for (int i = 0; i < n ; i++) {
//        printf("%d\t",arr[i]);
//    }
//}
int main(int argc, const char * argv[])
{

    //2015年04月02日15:55:41 北京
    /*
//    int array[5] = {5,4,3,2,1};
//    
//    printArray(array, 5);
    return 0;
}
定义一个随机数组,用函数进行冒泡排序:
#import <Foundation/Foundation.h>

//函数冒泡排序
void sortArr(int arr[],int n){
    
    for (int i = 0; i < n - 1 ; i ++) {
        for (int j = 0; j < n - (i+1); j++) {
            if (arr[j] > arr[j+1]) {
                arr[j] = arr[j] ^ arr[j+1];
                arr[j+1] = arr[j] ^ arr[j+1];
                arr[j] = arr[j] ^ arr[j+1];
               
            }
        }
    }
    for (int i = 0; i < n  ; i ++) {
        printf("%d\t",arr[i]);
    }

}
int main(int argc, const char * argv[])
{

    //2015年04月02日15:55:41 北京
    
    int array[10] = {0};
    for (int i = 0 ; i < 10 ; i ++) {
        array[i] =  arc4random() % 100;
    }
    sortArr(array,10);
    
    return 0;
}
函数的嵌套:
函数的嵌套,就是函数里调用函数。
函数允许函数嵌套调用,但是不允许函数嵌套定义。
一些例子:
简单嵌套调用:
#import <Foundation/Foundation.h>
//声明:
void sayHi();


void sayWhat(){
    sayHi();
}
void sayHi(){
    printf("Hello,Kitty");
}


//函数不允许嵌套定义,但是可以嵌套调用。
//函数嵌套调用:函数里边调用函数


int main(int argc, const char * argv[]){
    sayWhat();    
    return 0;
}
比较几个数的大小:
#import <Foundation/Foundation.h>
//函数不允许嵌套定义,但是可以嵌套调用。


//函数嵌套调用:函数里边调用函数

//求两个数最大值
int twoNumberMax (int num1 , int num2){
    return num1 > num2 ? num1 : num2;
}
//求三个数的最大值
int threeNUmberMax(int num1 , int num2,int num3){
    
    int temp = twoNumberMax(num1, num2);
    return temp > num3 ? temp : num3;
}
//比较4个数
int fourNumberMax(int num1 , int num2,int num3,int num4){
    
    int temp = threeNUmberMax(num1, num2, num3);
    return temp > num4 ? temp : num4;
}
递归:
递归就是函数自己调用自己。
递归慎用,因为如果自己不清楚声明情况下才能让递归停止的话,或者需求给定的样本太大,则不用。小样本,并且能控制条件让递归停止,则可以用,否则会沾满内存,使程序崩溃。
在能用非递归来解决,尽量用非递归的方式来解决。
#import <Foundation/Foundation.h>
//递归:自己调自己..(慎用)样本小得时候可以用,大得话,再说把。
//void sayHi(){
//    printf("Hello,六娃\n");
//    sayHi();
//}

int mul(int n){
    //非递归
//    int num = 0;
//    for (int i = 1 ; i <= 5 ; i ++) {
//        num *=i;
//    }
//    return num;
    
    //递归
    
    //使用递归必须能够结束。
    if (n == 1) {
        return 1;
    }else{
        return n*mul(n -1 );
    }
    
    
}
int main(int argc, const char * argv[])
{

//    sayHi();
    int num = mul(3);
    printf("%d",num);
    return 0;
}

作用域:
作用域,就是说一个变量的生命周期。
注意一下几点:
1、局部变量:
就是变量在一对花括号内,出了这对花括号,则内存释放这个变量,这个变量的声明结束。括号内的变量在和括号外的变量同名时,按照强龙不压地头蛇的思路,就是以花括号内的变量来使用,出了花括号,就以外面的变量来使用。
2、全局变量:
全局变量就是在花括号外的变量,它的生命从开始创建到程序结束才会结束。全局变量不仅仅可以在一个.m文件里使用,也可以跨文件使用,但是要在使用全局变量的另一个.m文件里头加上这句话:
extern int n1;//n1在本文件中没有,他就去外面的.m文件中去找。
例子:main.m文件
//
//  main.m
//  CLesson6_作用域
//
//  Created by lanou3g on 15/4/2.
//  Copyright (c) 2015年 xy2蓝翔. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "test.h"

//全局变量作用域
//生命周期是整个程序。
//跨文件使用
int n1 = 10;




int main(int argc, const char * argv[])
{
    //2015年04月02日17:15:12 北京
    
    //局部变量的作用域
    //凡是出现在花括号{}里边的,都是局部变量。
    //他们的作用域,就在这对花括号{}里边。    
    //在同一个作用域中,变量名不能重复。
    //不同的作用域,变量名可以重复。
    
//    int num1 = 10;
//    int num1 = 40;
//    {
//        //强龙不压地头蛇
//        int num1 = 40;
//        int num2 = 20;
//    }    
    say();    
    return 0;
}
test.m文件
////  test.m
//  CLesson6_作用域//
//  Created by lanou3g on 15/4/2.
//  Copyright (c) 2015年 xy2蓝翔. All rights reserved.
extern int n1;//n1在本文件中没有,他就去外面的.m文件中去找。

void say(){
    printf("%d",n1);
}







——大海
2015年04月02日20:53:05 北京




24、结构体
结构体,就是一种自定义的数据类型。
说是数据类型,意思就是,跟其他的基本数据类型一样,可以像基本数据类型一样,定义变量。
例如:
与int i = 20;一样,我们如果自定义了一个结构体,名字叫做student,那么意思就是,我们有一个student类型,可以定义一个student类型的变量:student s = {…..};
结构体的声明:
struct student{
    char name[20];
    int age;
};

如上面的例子所说的,我们用结构体的关键字:struct 来规定 student 类型,花括号里是student类型的成员变量,这些变量都是基本数据类型修饰的变量,可以是普通变量,可以是数组。
注意的是:成员变量不能赋初值。(可以这样想,一个数据类型,怎么能给值?)
一些例子:


#import <Foundation/Foundation.h>
//这个时候,我们已经自定义好了一个CPoint类型
//这只是个类型,没有初值

struct CPoint{
    
    //不能赋初值,会报错
//    float x = 0;
//    float y = 0;
    //成员变量
    float x;
    float y;
    
    
};//结构体后面有分号,不能丢掉。
int main(int argc, const char * argv[]){

//    //创建结构体变量,并按顺序赋值
//    struct CPoint p = {1.5,2.5} ;
//    //打印结构体,得一个一个打印出来。
//    printf("%f",p.x);//1.50000
//    printf("%f",p.y);//2.50000
    return 0;
}
结构体定义:
例子里已经声明了一个CPoint结构体类型,里边有两个成员变量:float型的x和y。我们在main()函数里,对CPoint结构体进行定义变量:
//    //创建结构体变量,并按顺序赋值
       struct 结构体名 变量名 = {初值};
//    struct CPoint p = {1.5,2.5} ;
注意:
初始化:开辟空间时,把值赋上,然后变量可以直接使用
赋值:开辟玩空间后,再往变量里赋值。但是不能对已经开辟好的结构体定义的变量里整个赋值。
//struct test{
//    int num;
//};
int main(int argc, const char * argv[]){
 //   struct test t1 = {1};    
//    t1 = {222};//这里会报错
//    return 0;
}

结构体的使用:
结构体的使用,通过我们定义的变量p来访问成员变量x和y。形式为:
p.x和p.y。

//    //打印结构体,得一个一个打印出来。
//    printf("%f",p.x);//1.50000
//    printf("%f",p.y);//2.50000

一些练习:

#import <Foundation/Foundation.h>
//这个时候,我们已经自定义好了一个CPoint类型
//这只是个类型,没有初值


struct CPoint{    
    //不能赋初值,会报错
//    float x = 0;
//    float y = 0;
    //成员变量
    float x;
    float y;
    
    
};//结构体后面有分号,不能丢掉。

//练习
//定义一个学生的结构体
struct Student {
    
    short num;//学号
    char name[20];//姓名
    char sex;//性别
    float score;//分数

};
//考虑定义一下结构体
//汽车
struct car {
    char brand[20];
    int c_number ;
    int seats;
    float price;
};
//人
struct person{
    
    char name[20] ;
    char sex[4];
};

//电脑
struct computer{
    char brand[20];
    float price;
    char cpu[20];
    int disk;
};

//书
struct book {
    char bookName[20];
    char isbn[50];
    char publish[50];
    char publishDate[20];
    float price;
};
int main(int argc, const char * argv[]){

//    //创建结构体变量,并按顺序赋值
//    struct CPoint p = {1.5,2.5} ;
//    //打印结构体,得一个一个打印出来。
//    printf("%f",p.x);//1.50000
//    printf("%f",p.y);//2.50000
    
//    struct Student s = {1,"王铁柱",'m',99};
//    //同类型结构体之间可以直接赋值。
//    struct Student s1 = s;
//    //吧s1的所有成员变量打印出来。
//    printf("%d\n",s1.num);
//    printf("%s\n",s1.name);
//    printf("%c\n",s1.sex);
//    printf("%.1f\n",s1.score);
//    //不要把字符串赋值给字符变量(若写字符串,则要用字符数组)
//    struct person p1 = {"王大锤","男"};
//   //结构体一旦创建好之后,字符数组成员变量不能再直接赋值了,需要使用strcpy();
////    p1.name = "田二牛";//报错
//    strcpy(p1.name, "田二牛");
//    printf("%s",p1.name);
return 0;
}
创建的结构体变量,赋初值时,要按成员变量的顺序来赋初值。
相同类型的结构体,可以直接把另外一个结构体直接赋值给这个结构体。
若是再创建结构体时,成员变量需要用到字符串,则应该定义一个char型数组,而不能直接将字符串赋值给字符型变量。结构体一旦创建好之后,字符数组成员变量不能再直接赋值了,需要使用strcpy();
匿名结构体:
匿名结构体,没有名字,只能在定义结构体的时候,定义变量。如果再创建一个结构体的同时,没有定义结构体变量,那么这个匿名结构体就算一个没有用的结构体。
struct{
    //成员变量
    char name[20];

}p1 = {"刘哇"},p2 = {"小金刚"};//匿名结构体,再花括号后面配上一个结构体变量,并且可以直接定义多个变量,赋值。。
typedef:
typedef 这个关键字是为了一个已经有的类型重新取一个别名。
Typedef有两个作用:
1、提示一下,定义出来的变量是干什么用的。
typedef int age;//给int类型取别名age,提示这个类型是用来放年龄age的。
2、简化类型
typedef struct student student;//这个是告诉我们,将student这个类型取一个别名student。
简化结构体的类型有两种方法:
(1)、typedef struct student student;
(2)、一边定义我的结构体,一边同时简化我的类型。(作业:20遍)
typedef struct cat{
    char color[20];
}Cat;
这种方式,是在声明cat结构体的同时,给它取了一个名字Cat。
简化类型有是好处呢?好处就是不用再在用struct这个关键字。

一些例子:

//练习

//float类型定义成money
typedef float  money;
//int类型定义成 height
typedef int height;
//用两种方式
//struct student 定义成student
//1.
typedef struct Student student;
//2.
typedef struct stu{
    char name[20];
    int age;

}Stu;
//struct Cpoint 定义成Cpoint
//1.
typedef struct  CPoint cpoint;
//2.
typedef struct CPoi {
    float x;
    float y;

}cpoi;
int main(int argc, const char * argv[]){
    //使用int的新名字定义变量
    age num = 10;
    printf("%d",num);
    
    //简化类型。不用再加上struct
    Person p2;
    //一边定义我的结构体,一边同时简化我的类型。
    Cat c1 = {"白色"};
    
    //练习
    money money = 100;
    height h = 123;
    
    student s = {};
    Stu stu = {"王大锤",90};
    
    cpoint cpoint = {1,2};
    cpoi cpoi = {3,4};
    
    return 0;
}
结构体内存占用:
结构体的内存占用情况,分为如下几点:
1、当结构体内没有定义任何成员变量时,这个结构体的占内存为0。
2、分配空间按照成员变量中,最大的分配。逐个使用。
3、内存对齐原则。(因此再定义结构体的时候,占内存大的放前面定义,然后逐个减小定义)
第3点说的明白点,就是结构体内定义一个类型变量时,会给这个类型分配固定字节的内存,但是当第二个成员变量的类型占内存更大时,这个第一个变量的内存会自动扩充到跟较大的那个内存一样。又因为,后一个变量类型在前一个变量的空间里,已经放不下,它不会接在前一个变量内存的后面,而是重新开辟一个内存,给自己用。当后面还有其他成员变量类型时,也不会接到这个变量内存的后面,而是重新开辟一个空间,用来放自己,但是开辟的空间还是跟最大的那个变量的类型占的字节一样大。因此如果在第三个变量后面还定义一个较小类型变量时,如果能在第三个变量的内存空间装完,则接在第三个变量内存后面,如果装不完,则开辟另一个空间。
例子:
结构体内,
先定义一个char 型变量(本身1字节),再定义一个double类型变量(本身8字节),则此时char类型空间开辟了8字节,由于double自己本身有8字节,接在char的后面不够装,因此它要开辟一个新的8字节内存。此时,再定义一个int类型的变量(本身4字节),此时会判断一下,再double那还能接下去装我的int吗?由于装不下,此时就会另外开辟一个8字节的内存空间来装int,还剩4个字节,如果再定义第4个char变量,则可以接到int类型的空间中去,还剩下3个字节。后面以此类推。
结构体的嵌套:
结构体的嵌套,就是结构体里有结构体。
例子如下:
#import <Foundation/Foundation.h>

//2015-04-03 16:17:15 北京

//点的结构体
typedef struct cpoint{
    float x;
    float y;
    
}cpoint;
//尺寸结构体
typedef struct csize{
    
    float w;
    float h;
    
}csize;
//矩形结构体
//结构体嵌套:结构体里有结构体
typedef struct cRect{
    cpoint point;
    csize size;
    
    
}cRect ;

//练习

typedef struct Student{
    
    int score;
    char name[20];

}Student;

int main(int argc, const char * argv[])
{
//    //赋值
//    cRect r = {{100,100},{50,50}};
//    //使用
//    printf("%.1f\n",r.point.x);
//    printf("%.1f\n",r.point.y);
//    printf("%.1f\n",r.size.h);
//    printf("%.1f\n",r.size.w);
    
//    cpoint p1 = {10,20};
//    cpoint p2 = {30,40};
//    cpoint p3 = {50,60};
    
    //结构体数组
    //数组中所有元素,都已同一类型
//    cpoint pointArray[3] = {p1,p2,p3};
    
//    printf("%.2f\n",pointArray[0].x);
    
    //练习
    Student stu1 = {92,"王大锤1"};
    Student stu2 = {91,"王大锤2"};
    Student stu3 = {89,"王大锤3"};
    Student stu4 = {99,"王大锤4"};
    Student stu5 = {79,"王大锤5"};
    
    
    //初始化:开开辟空间时,把值赋上,然后变量可以直接使用
    //赋值:开辟玩空间后,再往变量里赋值。
    //这两个过程是不一样的。

    

 
    Student stuArr[5] = {stu1,stu2,stu3,stu4,stu5};
    
    for (int i = 0; i < 5-1; i ++) {
        for (int j = 0; j < 5-(i+1); j++) {
            if(stuArr[j].score < stuArr[j+1].score){
                Student temp;
                temp =  stuArr[j] ;
                stuArr[j] = stuArr[j+1];
                stuArr[j+1] = temp;
            }
        }
    }
    printf("成绩从高到低为:\n");
    for (int i = 0; i < 5 ; i ++) {
        printf("%s: %d分\n",stuArr[i].name,stuArr[i].score);
    }
    printf("成绩最高的人为:%s:%d 分",stuArr[0].name,stuArr[0].score);
    
    return 0;
}

结构体数组:
将多个结构体变量放到数组中,构成结构体数组。其实跟普通的数组一样,但是在遍历的时候,或者按照某个成员变量排序的时候,要注意的就是,比较有结果后,可以反回这个成员变量,也可以反回整个结构体,看个人方便,想要什么信息,就返回声明信息。
struct student students[10] = {0};
可以通过下标访问结构体变量,例如:
students[0].name; // 第⼀个学⽣的姓名

一些练习:
#import <Foundation/Foundation.h>

//2015-04-03 15:03:18 北京

//第一题

//1、创建学生结构体
//2、成员变量有:姓名,年龄,成绩
//3、五娃  17 88
//   六娃 16 70
//   七娃 9  75

//4、找出成绩最高
//5、找出年龄最小

struct student{

    char stuname[20];
    int age;
    int score;

};
//测试内存结构体
typedef  struct Test{
    //结构体内没有东西的时候,结构体占内存为0.
    
    //结构体内存,有两条规则。
    //1、分配空间按照成员变量中,最大的分配。逐个使用。
    //2、内存对齐原则。(因此再定义结构体的时候,占内存大的放前面定义,然后逐个减小定义)
}Test;

int main(int argc, const char * argv[])
{
    //第一题
    //创建三个学生
    struct student student1 = {"五娃",17,88};
    
    struct student student2 = {"六娃",16,70};
    
    struct student student3 = {"七娃",9,75};

//    int maxScore = 0;
//    if (student2.score < student1.score && student3.score < student1.score) {
//        maxScore = student1.score;
//        printf("成绩最高的是:%s , %d分",student1.stuname,maxScore);
//    }
//    if (student1.score < student2.score && student3.score < student2.score) {
//        maxScore = student2.score;
//        printf("成绩最高的是:%s , %d分",student2.stuname,maxScore);
//    }
//    if (student1.score < student3.score && student2.score < student3.score) {
//        maxScore = student3.score;
//        printf("成绩最高的是:%s , %d分",student3.stuname,maxScore);
//    }
//    
//    int minAge =20;
//    
//    if (student2.age < student1.age && student2.age < student3.age) {
//        minAge = student2.age;
//        printf("\n年龄最小的是:%s , %d岁",student2.stuname,minAge);
//    }
//    if (student1.age < student2.age && student1.age < student3.age) {
//        minAge = student1.age;
//        printf("\n年龄最小的是:%s , %d岁",student2.stuname,minAge);
//    }
//    if (student3.age < student1.age && student3.age < student2.age) {
//        minAge = student3.age;
//        printf("\n年龄最小的是:%s , %d岁",student3.stuname,minAge);
//    }
    
    //student1 和 student2 中分数最高
//    struct student temp =  student1.score > student2.score ? student1 : student2;
//    //temp和student3中分数最高
//    struct student max = temp.score > student3.score ? temp : student3;
//   
//    printf("成绩最高的是:%s , %d分",max.stuname,max.score);
//
//    
//    
//    struct student temp1 =  student1.age < student2.age ? student1 : student2;
//    
//    struct student min = temp1.age < student3.age ? temp : student3;
//    
//    printf("\n年龄最小的是:%s , %d岁",min.stuname,min.age);
    
    
//    //sizeof用来计算占内存多少字节。
//    int size = sizeof(double);
//    printf("%d",size );//8
    printf("%lu",sizeof(Test));
    
    
    
    
    
    return 0;
}






















——大海
2015-04-03 22:30:56 北京


25、指针:
存储:
字节是最小的存储单元。每一个内存单元有一个编号,这个编号就是地址。
指针:
指针就是地址。
指针变量:
指针在没有放入地址之前,叫地址;放入地址之后叫指针。
指针变量的定义:
(1)、类型:通过类型,我们知道从这个地址取出来的数据是什么类型的,也可以知道取出来多少个字节。
(2)、*:*号是一个标识作用,在定义的时候,告诉你这个变量是指针类型的。
(3)、NULL:指针变量的初值,表示空,没有指向任何内存。
通常我们再程序中,把指针变量叫做指针。
指针变量定义的形式:
int *p1 = NULL;

定义一个char类型、double类型、long类型、float类型的指针变量。

//    char *p2 = NULL;
//    double *p3 = NULL;
//    float *p4 = NULL;
//    long *p5 = NULL;
区分一下:定义时的*号和取值符*号的区别,再定义一个指针时,*号表示的意思是,这个变量是指针变量;在定义完事之后,要取出这个指针变量所指向的内存所存储的值,就用*p来取值,此时的*号为取值符。
关于指针的一些符号:
& :表示取地址符,用来取一些变量的地址。
* :取值符。用来取对应地址所存储的内容。
%p:打印地址占位符。打印地址的时候,用%p来作为占位符。
一些例子:
//    获取num1的地址
//    int num1 = 10;
//    
//    p1 = &num1;
//    打印地址 p1
//    printf("%p\n",p1);//0x7fff5fbff834
    
//    通过地址取值
//    printf("%d\n",*p1);//10

注意:
不要直接给变量赋地址编号,虽然不报错,但是这是不合理的,系统会判断为你访问了不该访问的内存编号,在运行的时候,会因为内存出错而导致程序崩溃。
用上面的例子来说:

//    int *pt = 0x1;//若把地址编号改成0x7fff5fbff834,则可以访问,因为这个地址编号,就是系统给你用的。
//    printf("%d\n",*pt);//10
若把地址编号改成0x7fff5fbff835,即把pt + 1 也能使用,因为系统给你分的内存,不仅仅是一块,而是给你4块,若是在这4块之外,即pt + 5,则会直接导致结果直接崩掉。
指针的移动:(指针的算数运算,只有+ -)
指针可以移动,但是移动多少个字节,需要根据它所指的数据的类型来决定。
例如,它指向的数据是int型(占4个字节)的,那么指针在每一次移动的时候,是移动4个字节。
例子:
//    for (int i = 0; i < 10; i ++) {
////        p1没有移动。
//        printf("%d\n",*(p1+i));
////        p1移动了。结束后,p1已经不指向原来的位置了。
//        printf("%d\n",*(p1 ++));
        
//        printf("%p\n",p1+i);
        /*
        输出:
        0x7fff5fbff854
        0x7fff5fbff858
        0x7fff5fbff85c
        0x7fff5fbff860
        0x7fff5fbff864
        0x7fff5fbff868
        0x7fff5fbff86c
        0x7fff5fbff870
        0x7fff5fbff874
        0x7fff5fbff878
        每次移动4个字节,从上到下,每次+4
        */
//    }
    

指针重指向:
指针重指向,即为给一个指针重新赋值。
//    int num2 = 20;
//    //指针重指向:给指针变量再赋值。
//    p1 = &num2; //p1指向了num2的地址。
指针与数组的关系:
数组:用连续的内存空间来存储数据的构造类型。
数组名本身就是整个数组的首地址,也就是第1个元素所在的地址,因此,再取地址时,不需要再用&取值符了。
    //数组名就是整个数组的首地址
    //数组名本身就是地址,不用再取地址了。
//    int array[3] = {1,3,8};
//    printf("%p\n",array);//0x7fff5fbff840
//    数组的首地址,就是数组中,第一个元素的地址
//    printf("%p\n",&array[0]);//0x7fff5fbff840
一个指向int类型数组的指针,是int类型。在一个指针指向一个什么类型的数组时,这个指针对应的就是什么类型的。
使用指针和数组名取值:
(1)、用数组名取值
//    一个指向int类型数组的指针,是int类型
//    int *pi = array;
//    printf("%d\n",*pi);//1
//    printf("%d\n",*(pi+1));//3
    //使用指针和数组名取值
    //数组名取值。
//    printf("%d\n",array[0]);//1
    
//    printf("%d\n",*array);//1
//    printf("%d\n",*(array + 2));//8

(2)、使用指针变量取值:
    //使用指针变量取值
//    printf("%d\n",pi[0]);//1
//    printf("%d\n",pi[2]);//8
//    printf("%d\n",*pi);//1
//    printf("%d\n",*(pi+1));//3
注意的是:
指针在取值时,可以当做数组名来使用(即可以按照数组取值那样取值)。
p[ x ] = * ( p + x ) 是等价的。(x)表示任意一个整型数,p表示指针变量或者表示数组名。
数组名和指针的区别:数组名是地址常量,指针变量是变量。常量的值是固定的,不能再改变,指针变量的值是可以改的。
    int array[3] = {1,3,8};
    int *pi = array;
//    数组名和指针的区别。
//    数组名是地址常量
//    指针变量是变量
//    pi ++;//合法
//    array ++;//报错
指针的内存空间:
指针本身占内存空间的大小,与它所指向的数据的类型无关,只与它所在的操作系统的位数有关。32位系统和64位系统的指针占内存不同。
例子:
//    内存空间
//    printf("%lu\n",sizeof(array));//12
//    printf("%lu\n",sizeof(pi));//8,指针本身的占内存空间的大小,不关其他类型的事。指针占内存的大小,只与操作系统的位数有关。
一个练习:
    //给你一个数组:元素都是int类型的,但是你不知道有多少个元素。
//    int array1[] = {1,2,3,4,5,6};
//    
//    int arr_length = sizeof(array1);
//    printf("%lu",arr_length / sizeof(int));//6
注意的一些问题:
不同类型的指针,相互之间相互赋值,是不合法的,虽然可能可以取到正确的值,但是很多情况下,取值会出现问题。
具体来说,若定义了一个int型的数,然后定义一个char型的指针变量p,此时通过 p 来取值,则可以取到正确的值。原因是:int类型的数据,再内存中占有4个字节32位,即系统分配了4个连续的存储空间,每个空间1个字节(8位)给这个int型的数,但是此时这个数比较小,只在第一个存储空间就可以装完,则其他三个字节空间都为0。此时,char类型占一个字节,正好可以取到int型数据的第一个字节空间的值,也正好能存完。此时取到的值是正确的。(画图便能说明白)。若定义了short型的数据(占2个字节),分配了2个连续的内存空间,此时定义的指针是int型,则指针此时取值的时候,取到的是连续4个字节空间的值,此时,就会把这4个连续的内存空间的所有2进制数加起来变成一个很大的10进制数。那么取到的值就是错误的。
例子:
//    int num1 = 3;
//    //不同类型指针,取值会出问题。
//    char *pn = &num1;
//    
//    printf("%d\n",*pn);
    
//    short a[4] = {3,7,9,1};//short 类型占2个字节
//    int *p1 = a;           //int 类型占4个字节
//    char *p2 = a;          //char 类型占1个字节
//    
//    printf("%d\n",*p1);//458755,因为p1取了4个字节共32位的所有0、1,计算结果即为458755。
//    printf("%d\n",*p2);//3  ,因为p2只取了一个字节的值,这个字节里正好存了3,正好可以存进char,所以可以取到3。
指针和字符串的关系:
首先记住一个东西:栈区的值是可以改变的。常量是放在常量区的,常量区的所有值是不能改变的。那么就好办了。
字符指针可以操作单个字符,也可以操作字符串。
看例子就能明白了:
char string[] = “iPhone”; //string为数组名
char *p = string; //指向字符数组⾸地址
*(p + 1) = ‘v’; //操作单个字符
printf(“%s”,p + 1); //操作整个字符串

////    指针和字符串的关系
////    string1 再栈区存放,栈区的值是可以改的。
//    char string1[] = "Ipad";
//    char *pc = string1;
//    printf("%s\n",pc);//Ipad
////    改变字符数组的位置
////    pc[0] = 'i';
//    *(pc + 0) = 'i';
//    
//    printf("%s\n",pc);//ipad
    //常亮字符串存在常量区,常量区的所有数据只读不可写(不能改变值)
//    char *ps = "Iphone";
//    printf("%s\n",ps);//Iphone
    
//    ps[0] = 'i';
//    printf("%s\n",ps);//出错
//    printf("%c\n",ps[0]);//I
    
//    printf("%c%c%c",*(ps+3),*(ps+4),*(ps+5));//one
//    ps += 3;
    //%s要的是字符串的首地址和结束的标志"\0"
//    printf("%s",ps);//one
//    printf("%s",ps + 3);//one
   

一些练习:
//    练习:
//    通过指针计算字符串的长度
    
    char string2[] = "Iphone";
    char *ps = string2;
    int n = 0;
//    while (ps[n] != '\0') {
或者
    while (*(ps + n) != '\0') {
        n ++;
    }
    printf("%d\n",n);//6
指针数组:(跟那个指针与数组的关系区分一下)
指针数组就是一个数组,这个数据里的所有元素,都是指针(也就是地址,其实是每个字符串的首地址)。
注意:这些字符串,不是存在这个指针数组中,而是存在另外一个字符数组中,这个指针地址存的是那个字符数组的每一个字符串的首地址。
    //指针数组
    
    //指针数组是一个数组,数组中的元素,都是指针(地址,其实是每一个字符串的首地址)。
    //注意:这些字符串,不存在指针数组中。
//    char *strarray[3] = {"iOS","Android","Windows10"};
//    
//    for (int i = 0; i < 3; i ++) {
//        printf("%s\n",strarray[i]);/*
//                                    iOS
//                                    Android
//                                    Windows10
//                                    */
//    }

指针作为函数参数:
这个就是精华所在:指针作为参数,可以跨作用域取改值。
//指针作为参数。
//(重点):指针可以跨作用域改值。

int swap (int *n1,int *n2){
    *n1 = *n1 ^ *n2;
    *n2 = *n1 ^ *n2;
    *n1 = *n1 ^ *n2;
    return 0;
}
//函数声明:
int swap(int *num1,int *num2);
//主函数
int main(int argc, const char * argv[])
{
//    例子:写一个函数
//    有两个参数,交换这两个参数
    int num1 = 10;
    int num2 = 20;
    printf("交换前:\n");
    printf("num1 = %d\nnum2 = %d\n",num1,num2);
    swap(&num1,&num2);
    printf("交换后:\n");
    printf("num1 = %d,num2 = %d",num1,num2);//num1 = 20,num2 = 10
    return 0;
}
26、结构体指针
结构体:
就是一种自定义的类型。
typedef :
给一个已经存在的类型取一个新的名字。

//2015-04-07 10:12:19 北京

//结构体是一种自定义的类型

//CPoint
//typedef 给一个类型取一个新的名字。
typedef struct CPoint{
    float x;
    float y;
}CPoint;
//练习
//声明一个student 结构体,姓名,年龄,班级
//定义一个stu的变量("王铁柱",18,5)
//使用结构体指针,打印成员变量
typedef struct Student{
    char name[20];
    int age;
    int s_class;
}Student;

结构体指针:
就是指向结构体的指针变量。
定义结构体变量:
CPoint c = {1.2,2.3};
定义结构体指针:
CPoint *cp1 = &cp;
用结构体指针取值:
结构体指针取值,先通过地址取到结构体,然后通过结构体,取到成员变量。(*cp1).x
//    //取x,y的值
//    //先通过地址,取到结构体
//    //再通过结构体,取到成员变量
//    printf("x = %f\n",(*cp1).x);//打印1.2,(*p1)这个小括号不能丢
//    printf("y = %f\n",(*cp1).y);//打印2.3
除了上面这种取值方法,还有另外一种方法:用箭头—>
cp1—>x

例子:
    //练习
    //声明一个student 结构体,姓名,年龄,班级
    //定义一个stu的变量("王铁柱",18,5)
//使用结构体指针,打印成员变量
typedef struct Student{
    char name[20];
    int age;
    int s_class;
}Student;
int main(int argc, const char * argv[]){
//    Student stu = {"王铁柱",18,5};
//    Student *sp = &stu;
////    printf("姓名:%s",(*sp).name );
////    printf("\n年龄:%d",(*sp).age);
////    printf("\n班级:%d",(*sp).s_class);
//    
//    //指针可以直接取成员变量的值 ->
//    printf("姓名:%s\n",sp->name);
//    printf("年龄:%d\n",sp->age);
//    printf("班级:%d\n",sp->s_class);
    return 0;
}
一些练习:
定义一个点结构体,成员变量有俩,x,y都是float型,再main函数定义两个点,求这两点的距离。
#import <Foundation/Foundation.h>
typedef struct CPoint{
    float x;
    float y;
}CPoint;
int main(int argc, const char * argv[])
{
    //练习
    
//    CPoint1 m = {4.5,5.6};
//    CPoint1 n = {3.2,8.8};
//    
//    CPoint1 *pm = &m;
//    CPoint1 *pn = &n;
//    if (pn->x > pm->x && pn->y > pm->y) {
//        float length = sqrt((pn->x - pm ->x) * (pn->x - pm ->x) + (pn ->y - pm ->y) * (pn ->y - pm ->y));
//        printf("%f\n",length);
//        return length;
//    }
//    if (pn->x < pm->x && pn->y < pm->y) {
//        float length = sqrt((pm->x - pn ->x) * (pm->x - pn ->x) + (pm ->y - pn ->y) * (pm ->y - pn ->y));
//        printf("%f\n",length);
//        return length;
//    }
//    if (pn->x < pm->x && pn->y > pm->y) {
//        float length = sqrt((pm->x - pn ->x) * (pm->x - pn ->x) + (pn ->y - pm ->y) * (pn ->y - pm ->y));
//        printf("%f\n",length);
//        return length;
//    }
//    if (pn->x > pm->x && pn->y < pm->y) {
//        float length = sqrt((pn->x - pm ->x) * (pn->x - pm ->x) + (pm ->y - pn ->y) * (pm ->y - pn ->y));
//        printf("%f\n",length);
//        return length;
//    }
return 0;
}

定义一个student结构体,定义一个student变量,把第一个字符改成大写。


#import <Foundation/Foundation.h>
typedef struct Student{
    char name[20];
    int age;
    int s_class;
}Student;
int main(int argc, const char * argv[])
{    //把第一个字符的小写变成大写。
    Student stu = {"lan ou",18,5};
    
    Student *sp = &stu;
    sp->name[0]的意思是:访问了成员变量name,这个name是char型数组,然后取这个数组的第一个值。
    sp->name[0] ^ 32的意思是把小写的改成大写,或者将大写的转换成小写。
sp->name[0] = sp->name[0] ^ 32;    
printf("%s\n",sp->name);
    return 0;
}
字符的大小写转换:
用到的运算符有:
1、&(按位与):有清0的效果,当一个字符与233用&时,这个字符会转换成大写。
2、|(按位或):有置1的效果,当一个字符与32用 | 时,这个字符会转换成小写。
3、^(异或):一个字符与32用 ^ 时,小写变大写,大写变小写。
//    //字符的大小写转换
//    // &:清0,与223用 &,全部变成大写
//    // |:置1,与32用 | ,全部变成小写
//    
//    char c = 'A';
//    printf("%c\n",c^32);//a
//    printf("%c\n",c | 32);//a
//    printf("%c\n",c & 223);//A
//    
//    char c1 = 'a';
//    
//    printf("%c\n",c1^32);//A,大写变小写,小写变大写
//    printf("%c\n",c1 | 32);//a,变成小写
//    printf("%c\n",c1 & 223);//A,变成大写
    
//    int arr1[5] = {1,2,3,4,5};
//    int *p1 = arr1;
//    printf("%d\n",*(p1 + 1));
结构体数组:
一个数组用来存放结构体,这个数组就是结构体类型的数组。
结构体本身所占的字节,是根据结构体内的成员变量的定义后才确定的。具体的情况,看那个结构体数组那节。
结构体数组指针:
给结构体数组定义指针时,指针直接指向数组的首地址即可。
结构体数组指针本身所占的字节为8个字节,与它所指向的东西无关,只与它所在的操作系统的位数有关。
   #import <Foundation/Foundation.h>
  //Person
  typedef struct Person {
    char name[20];
    int age;
  }Person;
int main(int argc, const char * argv[])
{
    Person person1 = {"Mike",20};
    Person person2 = {"Joe",18};
    Person person3 = {"Kitty",17};
    
    
    //存放结构体的数组,是结构体类型的。
    Person arr2[3] = {person1,person2,person3};
    
//    printf("%lu\n",sizeof(Person));//占了24个字节
    //结构体指针也是占8个字节
    Person *pp = NULL;
    pp = arr2;
    //对结构体指针进行+、-的时候,一次性越过的字节数,是根据结构体所占内存来决定的。
//    printf("%s\n",pp->name);//Mike
//    printf("%s\n",(pp+1)->name);//Joe
//    printf("%s\n",(pp+2)->name);//Kitty
    return 0;
}
结构体数组取值:
结构体数组取值,有三种方式了:
    //存放结构体的数组,是结构体类型的。
    Person arr2[3] = {person1,person2,person3};
    //结构体指针也是占8个字节
    Person *pp = NULL;
pp = arr2;
    // -> 这个箭头是给结构体指针用的。
    // . 这个是给结构体用的。
    for (int i = 0; i <3 ; i++) {
        //结构体数组取值,有三种方式。
        printf("%s\n",(pp+i)->name);
        printf("%s\n",(*(pp+i)).name);
        printf("%s\n",pp[i].name);
        
//    }
结构体数组作为函数参数:
当定义一个函数的时候,需要用到结构体数组作为函数参数的时候,再定义函数时,参数里定义一个结构体类型的数组(不用给元素个数),再定义一个int型变量,用来传递这个数组的元素个数。
#import <Foundation/Foundation.h>
void persons(Person arr[],int count){
    for (int i = 0; i < count ; i ++) {
        printf("%s\n",arr[i].name);
    }
}
int main(int argc, const char * argv[])
{
    persons(arr2, 3);    
    return 0;
}
语法糖:参数里的Person arr[],可以写成:Person *arr。
一些练习:
#import <Foundation/Foundation.h>
//练习
//使⽤指针查找学员数组中性别为男的学员,成绩增加10分,超过100分的记为100分)
//如果要存汉字,一定要用字符数组,char不可以存汉字。
typedef struct Score {
    char name[20];
    char sex ;
    float score;
}Score;

int main(int argc, const char * argv[])
{    //练习
    Score s1 = {"王大催",'f',89.0};
    Score s2 = {"李大催",'m',97.0};
    Score s3 = {"找打催",'m',81.0};
    
    Score score_arr[3] = {s1,s2,s3};
    Score * sp = score_arr;
    for (int i = 0; i < 3; i ++) {
        if('m' == (sp + i)->sex  ){
            (sp + i)->score = (sp + i)->score + 10;
            if ((sp + i)->score > 100) {
                (sp + i)->score = 100;
            }
             printf("%s是男的,加上10分后的成绩是:%.2f\n",(sp + i)->name,(sp + i)->score);
        }
    }
    return 0;
}

27、预编译指令:
宏定义:
就是在程序编译前,对一些代码进行原封不动地替换。
注意的几点:
(1)、宏定义不是定义变量;
(2)、宏定义,是在编译前对一些代码原封不动地替换;
(3)、宏定义的规范:a、全部字符大写;b、k+驼峰命名法
//宏定义
//宏定义不是定义变量
//原封不动的替换
//明明规范:1.全部大写,2.k + 驼峰命名法
#define PI 3.1415926//全部大写

#define kPi 3.1415926//k + 驼峰命名法

#define kInt int i = 10 //替换语句
int main(int argc, const char * argv[])
{
//    2015-04-07 16:24:27 北京
    float pi1 = PI;
    float pi2 = PI;
    //魔数:如果不给注释,我不知道这个数是啥意思。
    float s = PI*5*5;
    
    kInt;//将上面的宏定义的int i= 10;替换掉这个kInt
//    printf("%d\n",i);//10
    return 0;
}
带参宏定义:
#define MUL(A,B) A*B
注意的一点,在这个宏定义被使用的时候,A和B是原封不动的被替换。例如,我在main函数中,调用这个MUL(A,B),这时候我传的参数是MUL(3,5),此时,是这样替换的:3替换A,5替换B,计算3*5的值。如果此时,我传的参数是:MUL(3+3,5+5),此时替换的情况是这样的:3+3替换A,5+5替换B,计算的情况是3+3*5+5,先计算3*5,然后再计算+和。所以,为了避免出现这样的情况,在宏定义的时候,要对A*B加上括号(),即
#define MUL(A,B) ((A)*(B))
此时计算的情况就是:先3+3,5+5,然后在算乘积。

条件编译:
三种形式:
1、#ifdef
//条件编译
#ifdef PI//如果我宏定义过PI,我就执行int a = 10;如果没有宏定义过 PI,则执行else中的代码
int a = 10;

#else

int a = 20 ;

#endif

2、ifndef:
#ifndef BB//如果我没有宏定义过BB,我就执行int b = 30;如果宏定义过 BB,则执行else中的代码

int b = 30;

#else

int b = 40;

#endif
3、#if 常量表达式
//常量表达式
//如果 是0,说明是假,执行else
//如果是非0,说明是真,执行if
//不能使用浮点型
#if 6 > 7//(常量表达式)

int c = 50;

#else

int c = 60;

#endif








——大海
2015-04-07 22:57:19 北京
28、动态内存分配
存储区的划分:
在计算机的内存中,可以分成5个区,每个区都有着不一样的效果。按内存编号从小到大的顺序,分别是:
(1)、代码区:
计算机将我们写的代码通过二进制转换后,放进了这个代码区。
(2)、常量区:
在我们写代码时,所有的常量,都放在常量区,常量区的所有值都是可读不可写的。也就是说,常量区的所有值都是不能改变的。若强行对其赋值,则在运行的时候,直接导致程序崩溃。
常用的一个关键字:const。
const可以把一个变量声明成常量:const int i = 10; 此时,就应该把num1当成常量来使用。此时再给num1赋值,会报错。如果用指针取到num1的地址,然后再用 * 取值后给其赋其他值,虽然可以成功赋值,但是此时的num1已经不是原来的num1,而是原来的num1用const修饰后,直接转移到了常量区,用指针取地址后,取到的是num1在栈区的地址,改动的也是原来栈区内的num1。也就说,有两个num1,一个在常量区,一个在栈区。因此,为了保证安全,不能这样写。
//    常量区
    
//    程序中出现的所有常量,保存到常量区
//    常量区的所有值:只读不可写    
//    const
//    可以将变量声明成常量    
//    int num1 = 10;
//    num1 = 20;//可以改值
//    //const的意思:只读,num1的值不能改变了。
//    const int num1 = 10;//定义成常量,要当做常量使用。
//    num1 = 20;//报错
    //不要这样写。这样写会破坏安全性。
//    int *pi = &num1;
//    *pi = 20;
    //num1的值在常量区有一份,在栈区有一份。
//    printf("%d\n",num1);//10,这个num1已经放到了常量区。
//    printf("%d\n",*pi);//20,这个值是num1的值,但是这个num1的位置是在栈区。
    
    //const 放在不用的位置,效果不一样(作业)
//    const int *p = NULL;


(3)、静态区:
用来存放静态值的地方。在代码中,用static来标记。用static来标记的语句,在整个程序运行过程,只进行一次初始化,不论程序是否循环到该语句,都不再执行。如果初始化的时候,没有给初始值,系统会自动给0。静态区的东西常驻内存,直到 程序运行结束才会释放。全局变量也放在静态区,也叫全局区。这个时候,其他文件可以用到这个全局变量。此时,如果我们在这个全局变量加上static修饰,这时候的static会限制作用域,限制其他文件不能用。static 依然受到作用域的约束。
#import <Foundation/Foundation.h>
//也在静态区,也叫做全局区
//int n2 = 10;
//如果全局变量加了static 会限制作用域,其他文件不能使用。
static int n2 =10;

//静态区的变量,可以不做初始化,自动清0.
//可以计数使用
int test(){
    static int count = 0;
    return  ++ count ;
}

int main(int argc, const char * argv[]){
 //静态区
//    static int num = 10;
//    printf("%d\n",num);
//    
//    num = 20;
//    
//    printf("%d\n",num);
    
//    while (1) {
//        int i = 0;
//        printf("%d\n",i++);//全部打印0
//    }
//    while (1) {
//        //静态变量只被初始化一次。
//        static int i = 0;//这条语句执行过一次后,这条语句就失效了,不再执行
        printf("%d\n",i++);//全部打印0
    }
    //依然受作用域的约束
    i = 10;//报错    
    printf("%d\n",test());//1
    printf("%d\n",test());//2
    return 0;
}
(4)、堆区:
用malloc等函数分配的内存。
堆区是唯一一个由我们自己可以掌管内存大小,并对内存回收的区。即:⼿动分配,⼿动释放。
堆,是内存中,最大的一块。因此,大部分情况下,类型的内存申请是在堆中完成的。
malloc函数:动态分配内存函数:void *malloc();
void * 表示任意指针类型。也就是返回值可以是任意类型的指针。
括号内的参数,意思是分配多少字节的大小。
    //参数的意思是:写多少,就分配几个字节的内存。
    //void * 任意指针类型
//    char *p1 = malloc(1);//在堆中分配了一个字节内存
//    char *p2 = malloc(10);
    
//    int *ip = malloc(sizeof(int));
//    *ip = 20;
//    printf("%d\n",*ip);//20
一些例子:
//    //练习
//    //数组:20个元素,都是int类型的。
//    //在堆区分配空间
//    //使用随机数填值。[0~30]后打印
//    
//    int *arr = malloc(sizeof(int) * 20);//在堆中分配了20个int类型的内存。用arr指针来找到在堆中的位置。
//    
//    for (int i =0; i < 20; i ++) {
//        *(arr + i) = arc4random() % 30;
//    }
//    
//    for (int i = 0; i < 20 ; i ++) {
//        printf("第 %d 个元素:%d\n",(i+1),arr[i]);
//    }
//    //申请的内存,如果不手动释放,会一直存在。造成内存泄露。
//    //释放内存
//    free(arr);

free()函数:与malloc等动态分配内存的函数使用,对分配号的内存进行释放,回收内存。但是这里的释放,是标记删除。意思是对编号进行释放,但是内容仍然存在,只是告诉系统,这个内存已经可以回收,可以重新分配给其他人用。
注意:不能像下面那样写
    //切记,不能这么写,是错误的
    //原因是:先分配了6个字节,还没有释放,就去申请另外的空间,这样导致6个空间的内存不能释放。
//    int *p4 = malloc(6);
//    p4 = malloc(10);
例子:
    //练习:
//    //找出字符串中的所有数字,用malloc来存放这些数字
//    char *str = "a1b2c3d4";
//    //统计数字
//    int count = 0;
//    //循环增量
//    int i = 0;
//    //收集数字
//    char tempArr[100] = {0};
//   
//    while (*(str+i) != '\0') {
//        if (str[i] >= '0' && str[i] <='9') {
//            tempArr[count] = str[i];
//            count ++;
//        }
//        i ++;
//    }
//    //加上'\0'表示结束
//    tempArr[count] = '\0';
//    //分配空间
//    char *arr = malloc(sizeof(char) * (count+1));
//    //赋值:把栈区的数据,拷贝到堆区。
//    strcpy(arr, tempArr);
//    printf("%s\n",arr);//1234
//    //释放
//    free(arr);//释放的意思是标记删除
//    
//    printf("%s\n",arr);//仍然为1234
//    
//    arr = NULL;
//    
//    printf("%s\n",arr);//打印 (null)

由于free是标记删除,并不对内存里的内容进行清除,此时,还需要将我们的指针指向NULL:arr = NULL;
练习:

//    char *words[3] = {0};
//    for (int i = 0; i < 3; i ++) {
//        char temp[20] = {0};
//        printf("输入单词,回车结束:\n");
//        scanf("%s",temp);
//        words[i] = malloc(sizeof(char) * (strlen(temp)+1));
//        strcpy(words[i], temp);
//    }
//    
//    for (int i = 0; i < 3; i++) {
//         printf("%s\n",words[i]);
//    }
////    free(words); 三个地址要逐个释放。其实words是数组名,数组名就在栈区,不能用free来释放。words[i]的值才是在堆中开辟的空间的地址。能用free释放
//    for (int i = 0 ; i < 3; i ++) {
//        free(words[i]);
//        words[i] = NULL;
//    }

calloc函数:与malloc差不多,只是有两个参数,第一个参数是告诉有多少个后面的类型,第二个参数是告诉有多少字节。那么全部分配下来的内存为:第一个参数 * 第二个参数。calloc在分配好内存后
    //calloc
    
//    //第一个参数:有多少个
//    //第二个参数:有多少字节
//    //全部清0
//    //使用规则和malloc分配的内存一样
//    char *p2 = calloc(10, sizeof(int));//有10个int类型,4字节的空间,即分配了10 * 4 = 40个字节的空间,并全部清0
//    char * p1 = calloc(10, 4);//有10个4字节的空间,即分配了10 * 4 = 40个字节的空间,并全部清0
//    
//    for (int i = 0; i < 40 ; i ++) {
//        printf("%d",p1[i]);//0000000000000000000000000000000000000000
//    }
//    free(p1);
//    p1 = NULL;
//    free(p2);
//    p2 = NULL;

realloc 函数:改变一块内存。
    //realloc
    
//    char *p2 = malloc(100);
//    
//    //第一个参数:你要改变哪一块的内存
//    //第二个参数:要变成多大
//    char *p3 = realloc(p2, 120);
//    
//    free(p3);
//    p2=NULL;
//    p3 = NULL;

内存操作函数:
(1)、memset:设置内存给定一些内容:

    //memset内存设置
//    char *p3 = malloc(10);
//    //第一个参数:设置哪块内存
//    //第二个参数:设置成什么
//    //第三个参数:设置多少个字节
//    memset(p3, 2, 10);//设置p3所指向的内存,把里边的东西设置成2,设置10个字节。
//    for (int i = 0; i < 10; i ++) {
//        printf("%d",p3[i]);//2222222222
//    }
(2)、memcpy:内存拷贝函数
    //memcpy内存拷贝
//    char *p1 = malloc(10);
//    char *p2 = malloc(10);
//    
//    memset(p1, 5, 10);
//    //第一个参数:拷贝到哪
//    //第二个参数:从哪拷贝
//    //第三个参数:拷贝多少字节
//    memcpy(p2, p1, 10);//拷贝到p2,从p1拷贝过来,拷贝10个字节。
//    for (int i = 0; i < 10; i ++) {
//        printf("%d",p2[i]);//5555555555
//    }
//    free(p1);
//    p1 = NULL;
//    free(p2);
//    p2 = NULL;
(3)、memcmp: 内存比较函数
    //memcmp内存比较
    
//    char *p1 = malloc(10);
//    memset(p1, 2, 10);
//    
//    char *p2 = malloc(10);
//    
//    memset(p2, 3, 10);
//    //第一个参数:第一块内存
//    //第二个参数:第二块内存
//    //第三个参数:比较多少个字节
//    //内存比较和字符串比较一样,找到第一个不相等的字节,求差值。
//    //如果所有字节逗相等,返回0。
//    //返回 >0 ,第一个比第二个大,
//    //返回 <0 ,第一个比第二个小。
//    int n = memcmp(p1, p2, 10);
//    printf("%d",n);//-1

(5)、栈区
栈区的东西,有个特性,就是在栈内,数据是先进后出的。
在函数里定义的变量,基本上都是在栈区的,因此,一旦函数执行完毕,所有栈释放(也叫出栈),只是删除编号(地址),并没有清除数据,这时候,这些栈的使用权回归系统所有,系统可以再次分配,对里边的值重写。若是没有分配,那么在下一次遇到一个结构,类型一样的函数,里边的变量如果不给初值,那么这个变量就会使用上次出栈后遗留在栈中的数据。
#import <Foundation/Foundation.h>

int a(){
    int num1 = 100;
    return num1;
}
int b(){
    int num2 ;
    return num2;
}
int c(){
    int num3 ;
    return num3;
}
int main(int argc, const char * argv[])
{ //    2015-04-08 09:36:38 北京
    
//    栈区
    
    
//    int n1 = 10;
//    int n2 = 20;//
//    内存不具有清空的功能
//    
//    //每一个函数都有一个叫做 栈帧 的东西
//    //由于a,b两个函数结构相同,使用内存情况相同,b其实使用的是a的内存,所以原来的值被使用。
//    a();
//    printf("%d\n",b());//打印100
//    //在c函数使用之前,调用了printf,导致b残留的数据被覆盖,原来的值无法使用。
//    printf("%d\n",c());//打印0
//    int n3 = 30;
    return 0;
}
一个错误的例子:
#import <Foundation/Foundation.h>
////错误
//char *backStr(){//    
//    //str在backStr的栈内,一旦函数执行完毕,所有的栈内释放(也叫出栈),这时候,你的str字符数组的内存使用权回归系统,系统可以再分配。这个时候,原来的数据会被覆盖,导致无法使用。
//    char str[] = "iPhone";
//    return str ;
//}

int main(int argc, const char * argv[])
{ //    char *ps = backStr();
//    printf("%s\n",ps);    
    return 0;
}
此时,应该想到,跨作用域改值的只有指针作为函数参数的时候,才能实现。所以重新定义函数:
#import <Foundation/Foundation.h>
void backString(char str[],int length){
    char s[] = "iPhone";
    if (strlen(s ) > length) {
        strcpy(str , s );
    }else{
        return;//经常用来终止函数
    }
}

int main(int argc, const char * argv[])
{
    char st[20] = {0};
    backString(st,20);    
    printf("%s",st );
    return 0;
}
29、链表
#import <Foundation/Foundation.h>
//节点结构体
typedef struct Node{
    int data;//数据
    struct Node *next;//节点
}Node ;

//添加节点
void addNode(Node *n,int num){
    //临时指针
    Node *temp = n;
    while (temp->next != NULL) {
        temp = temp->next;
    }
    temp->next = malloc(sizeof(Node));
    temp->next->data = num;
    temp->next->next = NULL;
}

int main(int argc, const char * argv[])
{

    // 2015-04-08 17:19:41 北京
    Node *first = malloc(sizeof(Node));
    first ->data = 0;
    first ->next = NULL;
    
    addNode(first,1);
    addNode(first,2);
    addNode(first,5);
    
    return 0;
}

30、函数指针
在C语言里,与数组的数组名是数组的首地址一样,一个函数的函数名,也是该函数的首地址。该怎么使用呢?
例子:定义一个函数如下:
int maxValue(int a,int b) {
 return a>b?a:b;
}
那么这个函数的地址就是maxValue。
此时引出“函数指针”的定义方式:
int( *p)(int , int)=NULL;
这个该怎么解释?
“定义一个p变量,这个变量是一个指针,这个指针指向一个函数,函数的参数有两个,都是int类型的,这个函数的返回值是int类型的。”
当任意多个函数的参数个数、参数类型、返回值类型都一样时,这个函数的类型就是一样的。因此在定义函数指针的时候,可以定义一个指向NULL的函数指针,然后分别将各个函数名赋值给这个指针。即:
int( *p)(int,int)=NULL;
p = maxValue;
定义好后,就可以直接使用这个p了,与普通调用函数一样,只需要将其写下来,把参数给上,接收返回值就行了。
int num = p(3,5);
一些练习,定义几个函数,分别定义这些函数的函数指针。
#import <Foundation/Foundation.h>
//2015-04-09 09:47:33 北京
//返回最大值

//函数的地址:函数名
//函数的类型:返回值类型 + 参数个数和参数类型。
int maxValue(int num1,int num2  ){
    return num1 > num2 ? num1 : num2;
}

//a函数和maxValue的函数类型相同,可以使用相同的函数指针。
int a(int n1,int n2){
    return 1;
}
//把一下函数的函数指针写出来
void b(float n1,float n2){
    
}

void c(){
    
}

float d(int n1){
    return 1.2;
}
int main(int argc, const char * argv[])
{
    //2015-04-09 09:47:33 北京
    
//    //定义一个函数指针的方式
//    int(*p1)(int,int) = maxValue;
//    //a函数和maxValue的函数类型相同,可以使用相同的函数指针。
////    p1 = a;
//    //b函数的指针
//    void (*p2)(float , float) = b;
//    //c函数的指针
//    void (*p3)() = c;
//    //d函数的指针
//    float (*p4) (int) = d;
//    
//    printf("%d\n",maxValue(10, 20));//20
//    
//    int n1 = p1(10,20);//指针名调用
//    printf("%d\n",n1);//20
    return 0;
}
一个练习:
void printHello();
定义⼀个可以指向上述函数的函数指针,并通过函数指针实现调⽤该函数。
#import <Foundation/Foundation.h>
//练习1
void printHello(){
    printf("hello world!\n");
}

int main(int argc, const char * argv[])
{
    //l练习
//    //函数指针p1
//    void(*p1)() = printHello;
//    //指针调用
//    p1();//hello world!
    return 0;
}
函数回调:
一个练习:
定义两个函数,⼀个求最⼤值,⼀个求和,输⼊max或sum分别求3,5的最⼤值或和(提⽰,定义⼀个函数指针,根据输⼊内容指向不同函数,最后⼀次调⽤完成)。
#import <Foundation/Foundation.h>
//练习2
int sum(int num1,int num2){
    return num1 + num2;
}
//获得一个值
//函数指针做参数,可以屏蔽核心代码
int getValue(int num1, int num2,int (*pv)(int,int)){
    
    //核心代码
    //通过指针再去调用函数,称为回调。
    return pv(num1,num2);
}
int main(int argc, const char * argv[])
{
//    char str[20] = {0};
//    printf("请输入maxValue或者sum完成不同功能:");
//    scanf("%s",str);
//    
//    int(*pf)(int,int) = NULL;
//    
//    if (strcmp("maxValue", str) == 0) {
//        pf = maxValue;
//    }else if(strcmp("sum", str) == 0){
//        pf = sum;
//    }else{
//        printf("输入有误!");
//    }
//    //调用函数
//    if (pf != NULL) {
//        int n = pf(3,5);
//        printf("%d\n",n );
//    }else{
//        return 0;
//    }
    //函数回调:函数只在乎你作为函数参数
//    int (*p1) (int ,int ) = maxValue;
//    int (*p2) (int ,int ) = sum;
//    
//    getValue(3, 5, p1);//5
//    getValue(5, 6, p2);//11
    return 0;
}
在一个函数中,如果将一个函数指针作为它的一个参数,那么这个函数指针可以起到一个保护核心代码的作用。
如上个例子,在getValue()中,定义了int num1,num2,int(*pv)(int int)三个参数,其中第三个参数为函数指针。那么此时调用getValue()这个函数的时候,接收到三个参数3, 5, p1。p1 = maxValue。此时,进到函数内,执行pv(num1,num2);pv为函数指针,此时,我们的p1将自己所指向的函数地址,给pv。那么此时的pv将num1和num2两个参数给到它所指向的那个函数int sum(int num1,int num2)去执行,得到的结果再返回给用户。这样就可以起到保护核心代码的作用,为什么这么说?因为我可以不用改那个函数里边的代码,只是在这个函数的参数预留你想要的接口(一般是函数指针)。此时,只需要给这个参数一个函数地址,即可找到一个函数实现你想要的结果,不需要再改变原来函数的代码。
一些例子:
写⼀函数查找成绩90分以上的学员,使⽤回调函数在姓名后加”⾼富帅”。
#import <Foundation/Foundation.h>
//练习

typedef struct Student{
    char name[20];
    float score;
}Student;
void search(Student stuArr[],int count,void (*ps)(Student *)){
    for (int i = 0; i < count ; i++) {
        if ((stuArr+i)->score >=90) {
            //添加"高富帅"
            ps(&stuArr[i]);
        }
    }
}

void setName(Student *stu){
    strcat(stu->name, "高富帅");
}
int main(int argc, const char * argv[]){
    //练习
//    Student s1 = {"王大锤",90};
//    Student s2 = {"王大",89};
//    Student s3 = {"王大",99};
//    Student s4 = {"王大",91};
//    
//    Student stu_arr[4] = {s1,s2,s3,s4};
//    void (*p1)(Student *) = setName;
//    
//    search(stu_arr, 4,p1);
//    
//    for (int i = 0; i < 4; i ++) {
//        printf("%s\n",stu_arr[i].name);
//    }
    return 0;
}
上面这些,将函数指针作为函数参数的做法,叫做函数回调。
说白了,就是在函数里调其他函数,只是不是明调罢了,只是将地址给它,让它根据地址去指定的地方执行。
动态排序:
动态排序的意思是根据不明确的需求(即会根据不同的条件,进行不同的排序),来对数据进行排序。这个是函数回调(函数指针做函数参数)的一个经典例子。这类问题的关键是,根据不同的条件,这些“不同的条件”就是关键。
#import <Foundation/Foundation.h>
//从小到大排序
void sort (int arr[],int count,BOOL(*pf)(int,int)){
    for (int i = 0; i < count -1 ; i ++) {
        for (int j = 0; j < count - (i + 1); j ++) {
            if( pf(arr[j] , arr[j+1]) ){//判断是关键
                
                arr[j] = arr[j] ^ arr[j + 1];
                arr[j+1] = arr[j] ^ arr[j+1];
                arr[j] = arr[j] ^ arr[j + 1];
            }

        }
    }
    
}


BOOL cmpFunc(int num1,int num2){
return num1 > num2;
}
int main(int argc, const char * argv[])
{
    //动态排序
//    int arr[5] = {4,3,1,2,5};
//    
//    BOOL (*p1)(int,int) = cmpFunc;
//    
//    
//    sort(arr,5,p1);
//    
//    printf("小到大排序:\n");
//    for (int i = 0 ; i < 5 ; i ++) {
//        printf("%d\t",arr[i]);
//    }
    return 0;
}

冒泡排序,在if这个条件中,放入pf(arr[j] , arr[j+1])这个函数指针,可根据不同的返回值,进行不同的排序。
一个例子:
#import <Foundation/Foundation.h>
//学生结构体
typedef struct Student{
    char name[20];
    float score ;
    int age;
}Student;

void sortStudent(Student stu[],int count,BOOL (*p)(Student *,Student *)){
    for (int i = 0; i < count -1 ; i ++) {
        for (int j = 0; j < count - i -1; j++) {
            if(p(&stu[j],&stu[j+1])){
                Student temp = {0};
                temp = stu[j];
                stu[j] = stu[j+1];
                stu[j+1] = temp;
            }
        }
    }
}

//按成绩排
BOOL byScore(Student *s1 , Student *s2){
    return s1->score > s2->score;
}
//按年龄排
BOOL byAge(Student *s1 , Student *s2){
    return s1->age > s2->age;
}
//按姓名排序
BOOL byName(Student *s1 , Student *s2){
    if (strcmp(s1->name , s2->name)>0) {
        return YES;
    }else{
        return NO;
    }
}
int main(int argc, const char * argv[])
{

//    2015-04-09 15:13:52 北京
    
    Student stu[5] = {{"a",99,16},{"b",70,19},{"c",88,20},{"d",60,15},{"e",80,0}};
//    BOOL (*sp)(Student *,Student *) = byScore;
    BOOL (*ap)(Student *,Student *) = byAge;
    BOOL (*np)(Student *,Student *) = byName;
    
//    sortStudent(stu, 5,sp);
    sortStudent(stu, 5, ap);
    sortStudent(stu, 5, np);
//    sortStudentByName(stu, 5, np);
    for (int i = 0; i < 5; i ++) {
        printf("%s:%.2f:%d\n",stu[i].name,stu[i].score,stu[i].age);

     }
    
    return 0;
}
函数指针作为返回值:

这里函数指针作为返回值,用到了typedef的第二种用法,专门给一个函数指针类型取一个名字。这样就可以直接用这个名字来作为另一个函数的返回值。
其实,* 号可以写在typedef int *pFun(int,int); 也可以写在定义函数的时候pFun *fun2(char str[])。看情况,没有具体要求。

//typedef的第二种用法,专门给函数指针类型取一个心名字
typedef int pFun(int,int);

pFun *fun2(char str[]){
    return NULL;
}
一个例子:
#import <Foundation/Foundation.h>
//typedef的第二种用法,专门给函数指针类型取一个心名字
typedef int pFun(int,int);

pFun *fun2(char str[]){
    return NULL;
}
// + - * /
//定义了加减乘除四个函数,每个函数首地址都为函数名。
//四个函数类型相同。
int sum(int num1 , int num2){
    return num1 + num2;
}
int sub(int num1 , int num2){
    return num1 - num2;
}
int mul(int num1 ,int num2){
    return num1 * num2;
}
int di(int num1,int num2){
    if (num2!=0) {
        return num1 / num2;
    }else{
        return 0;
    }
}
//给函数类型起了一个新名字。Cal。
typedef int Cal(int ,int );
//定义一个结构体,用来一一对应名字和函数指针匹配(名字与函数建立对应关系)
typedef struct FunNamePair{
    char name[20];
    Cal *pFun;
}FunNamePair;

Cal *searchFun(char str[]){
    
    FunNamePair f[4] = {{"sum",sum},{"sub",sub },{"mul",mul },{"di",di }};
    for (int i = 0; i < 4; i ++) {
        if (strcmp(str, f[i].name) == 0) {
            return f[i].pFun;
        }
    }
    return NULL;
}
int main(int argc, const char * argv[])
{
//    2015-04-09 16:00:49 北京
    Cal *p =  searchFun("mul");
    if(p != NULL){
        printf("%d\n",p(3,5));
    }else{
        printf("输入有误。");
    }
    return 0;
}
这样,C语言告一段落。


posted @ 2016-01-29 22:22  哎呦,金毛  阅读(625)  评论(0编辑  收藏  举报