标签:
# 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语言告一段落。
标签:
原文地址:http://www.cnblogs.com/gnhxsk/p/5170222.html