c语言枚举变量自增报错,C_数据结构与算法(1):C语言基础
C_数据结构与算法(一):C语言基础致初学者的我:一切都是由浅入深。每种语言都有每种语言的特性,基本的特性是相同的,下面依照惯例写hello world,相关编译后面再介绍。// C语言用“//”和“/**/”来写注释,注释、空格等不影响语义(程序的意思)的符号会在程序编译阶段会被删除。#include //#include 预编译处理,这里把stdio的C函数库
C_数据结构与算法(一):C语言基础
致初学者的我:一切都是由浅入深。

每种语言都有每种语言的特性,基本的特性是相同的,下面依照惯例写hello world,相关编译后面再介绍。
// C语言用“//”和“/**/”来写注释,注释、空格等不影响语义(程序的意思)的符号会在程序编译阶段会被删除。
#include //#include 预编译处理,这里把stdio的C函数库载入进程序;
main() //main() 主程序入口;
{
printf("hello world!"); //printf() stdio函数库中的函数,表示格式化输出;
return 0; //return 0 返回状态0,表示程序正常结束。
}
part1 基本数据类型及表达式
变量可以视为一个无限大的黑箱,你可以往里面装你想装的东西,三个要素:
定义(或声明),根据数据类型在内存中申请空间,如int age;
赋值:修改变量的值,如 age = 30;
使用:将变量的值从内存中读出来进行使用,如 printf("%d",age),或运算 age = age + 1;
int age = 23数据类型 变量名 赋予变量的值
C基本数据类型(计算机中位指的是'0'或‘1’其中之一,而八位称为一个字节,sizeof()函数可以查看数据类型具有多少个字节):
整数型
short n = 10; //内存中占16位
int n = 10; //占32位
long n = 10; //占64位
浮点数
float f = 1.1; //占32位,有效位数6~7
double d = 1.1; //占64位,有效位数15~16
字符型
char c = 'a'; //占8位
进制转换,计算机惯用的进制为:二、八、十、十六
理解进制(以十进制138为例,可以用程序员计算器来帮助日常换算)
二进制: 138 = 128(即1*2^7) + 8(1*2^3) + 2(1*2^1) ----->10001010八进制:138 = 128(2*8^2) + 1(1*8^1) + 2(2*8^0) ----->212十进制:138 = 100(1*10^10) +30(3*10^1)+ 8(8*10^0) ----->138十六进制:138 = 128(8*16^1) + 10(10*16^0) ----->8A//十六进制中 10-15 分别以A-F来表示
自动类型转换
(unsigned ) char、 short、int、long、float、double等类型之间可以互相赋值,系统会进行自动类型转换:
short s = -1;
unsigned short us = s; // us = 65535
short s2 = 65;
char c = s2; //c = 'A'
精度损失:如果把存储大的类型赋值给小的类型,会面临精度丢失的问题。
变量相关信息
命名规则:变量的命名规则可以让程序更容易理解,个人采用Camel-Case命名法,如:personalInformation
作用域:指包括此变量声明的最小代码块,在这个作用域中,变量声明后的所有代码行都可以访问此变量。变量在其作用域内被创建,离开其作用域时被撤销。
{int age = 10;
...
....以上这些行都可以访问age
}
这里不能访问age
常量
一般我们在代码中通过符号来代表常量,这个符号被称为符号常量,通过#define或const定义,例如:
const double PI = 3.14; //定义圆周率的符号常量
#define PI 3.14; //用宏定义的方式,分不出数据类型
printf("PI=%f\n",PI);
//注:尽量使用const来定义符号常量
注意:尽量避免使用魔法数,指的是在程序中多处出现的常量值,应该使用上述方法将其赋值给一个恒定的变量。
表达式
表达式由操作符和操作数构成,通过操作符将一个或多个操作数连接到一起,如:
id = 3 //赋值表达式, =是赋值操作符
1+2 //计算表达式,+是算术运算符
++i //计算表达式,++是算术计算符
2>1 //逻辑表达式,>是关系运算符符
(double)a //类型转换表达式
1 && 0 //逻辑表达式,&&是逻辑运算符
...
自增自减运算符
++、–表示对变量执行加1(减1)操作,同时参与表达式运算。例如
int a = i++ + 3; //先让i参与表达式运算,然后将i+1
int a = ++i + 3; //先将i+1,然后让i参与运算
位运算符
&:位与运算,将二进制每一位进行与运算,都是1则为1,如
3 & 5 = 1
0011 & 0101 = 0001
|:位或运算,将二进制每一位进行或运算,都是0则为0,如
3 | 5 = 7
0011 | 0101 = 0111
^:位异或运算,将二进制每一位进行异或运算,不相同为1,相同为0,如
3 ^ 5 = 6
0011 ^ 0101 = 0110
~:按位取反,如
~3 = -4
<
3<<1 = 6
011 --> 0110
>>:右位移,对有符号数,最左边补原符号数,其它左边位补0,右边被挤掉,如
3>>1 = 1
011 -->01
赋值表达式
左值与右值:对一个变量(或表达式)来说,有两个值与其关联:
char[] fullName = "Rudolph_Browne";//fullName为左值,Rudolph_Browne为右值
1)数据:变量的数据值,存储在地址中,这个值被称之为右值(rvalue),r是read(可读)的意思。2)地址:存储数据值的内存地址,地址被称之为左值(lvalue),l是location(可寻址)的意思。
“每一个表达式要么是lvalue,要么是rvalue”。左值指表达式结束之后依然存在的持久对象,而右值指表达式结束之后就不复存在的(临时)对象。
其他表达式
sizeof运算符:返回变量或类型的字节数,如
sizeof(float); //4
sizeof(double); //8
int n;
sizeof(n); //4
条件表达式: 条件?值1:值2,条件成立时取值1,否则取值2,如:
int a = 1?5:10; //a为5
int b = (a%2==0)?1:0; //a为偶数时,b=1,否则为0
逗号表达式:由多个表达式以逗号连接而成,如
a=3, b=a+1, c=b+1;
x= (a=3, b=6*3); //逗号表达式的值为最后一个表达式的值
(a=3,b) = 5; //最后表达式为左值时,逗号表达式也可为左值
part2程序结构
基础输入输出函数
printf函数概述
printf("格式控制字符串"[,输出列表]);
注意:输出列表与格式控制字符串之间有一个逗号分隔(如果输出列表存在的话)。
格式字符串:%[标志][宽度][.精度][h | l],例如
printf("%05hd\n",10); //h表示短类型,l表示长类型
输出短整型数据10,宽度为5位,前面补0 //00010
格式字符
d(i):十进制整数,负数带-号
u:无符号十进制整数
o:无符号八进制整数,无前导字母o
x(X):无符号十六进制整数,无前导0x,X表示大写
f:小数形式的浮点数,默认6位小数
e(E):指数形式的浮点数
g(G):自动选择e和f中宽度较小者,不打印无效的0
c:单个字符
s:字符串
putchar函数
putchar函数用来将单个字符输出到标准输出设备,通常是显示器
putchar(c); //c可以是字符或整型数字
scanf函数
scanf函数用于接收输入,以回车作为一次函数调用结束,格式为
scanf("格式控制字符串",地址列表);
scanf("%d",&a); //&a表示变量a的地址
注意:通常在用c库函数输出提示语句时都会适当地缓存,要输出缓存时使用下面语句。
setbuf(stdout,NULL); //这句代码是为了先输出提示语句
getchar函数
getchar函数从键盘输入一个字符,返回该字符的ASCII码。例如
char c = getchar(); //等待键盘输入一个字符给变量c
线性结构
线性结构的程序没有太多的特殊性,程序按定流水线的方式被执行。
选择结构
if-else if-else结构
if(条件1) //如果条件为真
{ //那么//当条件成立时执行这里的代码
}else if(条件2)
{//或者//当条件2成立时执行这里的代码
}else{//否则//执行这里的代码
}
switch结构(注意是等值判断:必须用int或char的整数值);注意每个case后面有一个break,忽略后面其它case。
switch(rank){
case 1:
//奖励iphone;
break;
case 2:
//奖励ipad;
break;
case 3:
//奖励ipod
break;
default:
//无视
}
循环结构
while语句不断判断循环条件,当循环条件为真(非0)的时候,就执行循环操作,一直到循环条件变为假(0)才跳出。
while(循环条件){//循环操作
}
do-while先执行一遍操作,然后持续判断循环条件,如果条件为真则继续执行,直到条件为假退出。
do{//执行循环操作
}while(循环条件); //注意有分号
注意:while和do-while是在程序不知道具体停止位置而使用的,如果知道具体位置,请使用for。
for循环一般用于循环次数确定的情况,它将条件的初始化、条件判断、条件变化抽取出来,和循环体分离。
for(参数初始化;条件判断;参数更新){//循环操作
}
在while、do-while、for等循环语句中,break语句用来跳出循环从而执行循环后面的语句。
while(...){
....break;//下面的语句执行不到了,因为break跳出了循环
printf(...);
}//break以后,执行这个语句
continue用于跳过本次循环体剩余语句,进入下一次循环的条件判断。
for(int i=0; i< 100; ++i){if(如果是奇数){continue;
}
sum+=i;
}
用break可以跳出一层循环,如果想跳出多重循环,可以使用goto语句。例如
#include
int main(){
int i,j;
for(i=0;i<4;++i){
printf("%d\n",i);
for(j=0;j<4;++j){
if(i*j==4){
goto l1;
}
printf("\t%d:\n",j);
}
}
l1:
printf("循环跳出,i=%d,j=%d\n",i,j);
return 0;
}
//注:在C语言中,由于goto的灵活性,一般不提倡使用goto进行跳转。
part3 高级数据类型
数组
数组是一个变量,用来存储相同类型的一组数据。数组四要素:变量名称、元素、下标(从0开始)、类型,通过下标访问数组的元素。
类型 变量名称int size = 3;int nArray[3] = {1,2,4};int d1 = nArray[0];//0是下标,nArray[0]是下标为0的元素//注意下标不能超出数组的长度-1
for(i=0;i <= size;i++){//循环体
}
数组的存储
数组在内存里顺序存放,每个元素存放的大小按数组元素类型而定(以字节为最小单位)
int a[4]; //a[0](假设地址为2000H)、a[1]、a[2]、a[3]
2000H 2004H 2008H 200CH
a[0] a[1] a[2] a[3]
下标越界:数组在声明赋值的时候,编译器会检查是否越界,例如
int a[4]={1,2,3,4,5}; //编译提示越界
但在运行时访问下标的时候,编译器不会检查越界,此时程序员要自己注意。例如:
int a[4]={1,2,3,4};
printf("%d",a[4]); //a[4]指向2010H地址,内存的值未知
a[4] = 100; //如果修改了a[4]的值,实际是修改了别人的数据,可能导致程序出错或崩溃
局部数组变量声明后,并没有自动初始化,此时如果2000H~200CH内存地址里面已经有值,就会被拿来使用,导致程序运行混乱。所以局部数组变量需要进行初始化,例如:
1)int a[4]={};2) #include
int a[4];
memset(a,0, 4*sizeof(int)); //以字节数为单位赋值
3)for循环将每个元素设置成0
注:static变量或全局变量自动会初始化成0。
数组地址分配的规则,用&显示变量的地址,注意结果地址的增加字节数。
#include
int main(){
int i,a[4];
for(i=0;i<4;++i){
printf("a[%d]的地址是:%p\n",i,&a[i]);
}
printf("数组变量a的地址是:%p\n",&a);
return 0;
}
输出结果为:
=====运行开始======a[0]的地址是:0x7fff1d9e5ae0a[1]的地址是:0x7fff1d9e5ae4a[2]的地址是:0x7fff1d9e5ae8a[3]的地址是:0x7fff1d9e5aec数组变量a的地址是:0x7fff1d9e5ae0
=====运行结束======
数组值求解
最大最小值:循环数组,将每个元素和max变量比较,如果元素比max大,则将元素赋值给max,循环完成以后max就是全数组的最大值。
int i, max=arr[0];
for(i=1;i
if(max
max = arr[i];
}
}
//循环完成以后,得到最大值max
均值:
int i,sum=0;
for(i=0;i
sum += arr[i];
double avg = (double)sum/len;
查找:用下标来判断是否找到
int value = 5; //要查找的数
int i;
for(i=0;i
if(arr[i] == value){
break;
}
}
//如果i
删除:删除指定下标的元素:将此下标后面的元素依次往前移动一位,最后一位设置为’\0’,例如删除下标为3的元素。
for(i=3; i
arr[i] = arr[i+1];
//依次往前移动一位,下标3的被4的覆盖了
}
arr[len-1] = '\0'; //最后一位设置为0
删除给定值的元素:先通过查找获得下标,然后执行1。
int find=0;
int removeIndex;
for(removeIndex=0; removeIndex
if(arr[removeIndex] == 81){
find = 1;
break;
}
}
if(find){
for(i=removeIndex; i
arr[i] = arr[i+1];
}
arr[LEN-1] = '\0';
}else{
printf("没有找到要删除的元素");
}
在数组的指定位置插入一个新元素,前提是数组长度足够。
算法:将此位置及之后的元素都往后移动一位,然后将此位置的元素设置为新元素的值。例如,在下标2处插入一个10
int a[6] = {1,2,3,4,5}; //注意数组长度要够
for(i=len-1; i>2; --i){ //注意i从大到小
a[i] = a[i-1]; //依次往后移动,空出a[2]
}
a[2] = 10;
拷贝:循环赋值或用memcpy函数。
for(i=0; i
newArr[i] = arr[i];
}
//或:memcpy(newArr, a, len*sizeof(a[0]));
合并:同样可以用循环赋值或memcpy函数。
int a[3]={1,2,3}, b[4]={4,5,6,7};
int c[7];
for(i=0; i<7; ++i){
c[i] = (i<3)?a[i]:b[i-3];
}
//或:memcpy(c, a, 3*4);
//memcpy(c+3, b, 4*4);
二维数组a[i][j],可以把i视为行,把j视为列:
int scores[2][4] = {{1,2},{2,3},{3,4,5,6}};
for(int i=0; i<2; i++){
for(int j=0; j<4; j++){
printf("%d",scores[i][j]);
}
printf("\n");
}
结果为
=====运行开始======
1200
2300
//因为2行中赋值中有{1,2,(0,0)}和{2,3,(0,0)},即不足的以0来填补。
=====运行结束======
二维数组的存储与矩阵的简单表示
在逻辑上是二维的,其两个下标在两个方向变化,但实际的存储是连续的,C语言中,按照行排列的方式存储:存放完一行后,再存放下一行的元素,如:
scores[3][5],三个行如下:
scores[0] -->一班的分数:5个元素的一维数组
scores[1] -->二班的分数:5个元素的一维数组
scores[2] -->三班的分数:5个元素的一维数组
共15个元素,按照顺序存储,如:
2000H 2001H 2002H 2003H 2004H00 01 02 03 042005H 2006H 2007H 2008H 2009H10 11 12 13 142010H 2011H 2012H 2013H 2014H20 21 22 23 24
矩阵算法可以使用二维数组进行运算,如a[3][3]:
1 2 3 --> a[0]4 5 6 --> a[1]7 8 9 --> a[2]
字符串
概念:在C语言中,将字符串作为字符数组处理,以’\0’作为字符串的结束标志,’\0’占内存空间,但不计入字符串长度。
"Hello" ---> {'H','e','l','l','o','\0'}
//字符串长度为5,数组长度为6
输出字符串数组变量时,遇到’\0’结束,’0’是ASCII码的0,可以通过循环输出看到,例如
char a[] = "Hello";
int len = sizeof(a)/sizeof(a[0]); //6
printf("%s\n",a); //Hello
for(int i=0; i
printf("%d ",a[i]); //--> 最后一个是 0
字符数组的定义和赋值:
char a[] = "Hello";
char a[] = {"Hello"};
char a[] = {'H','e','l','l','o','\0'};
char a[];
a = "Hello"; //错误,定义后就是常量,不能再整句赋值
字符串函数(C语言提供了专门处理字符串的函数库:#include
拷贝函数strcpy:strcpy(目标字符数组, 源字符数组),将源字符数组的内容(包括结束符\0)拷贝到目标字符数组中,函数返回目标字符数组。
char s[10]
strcpy(s, "Hello"); //s-->Hello\0
strcpy的覆盖性:如果目标字符数组中已经存在内容,将被覆盖。
char s[10]="123456789"
strcpy(s, "Hello"); //s-->Hello\0789
printf("%s",s) //Hello
strncpy:strncpy(目标字符数组,源字符数组,拷贝长度),将源字符数组的前n个字符(不一定包括结束符\0)拷贝到目标字符数组中,函数返回目标字符数组。
char s[10]="123456789"
strncpy(s, "Hello",3); //s-->Hel456789
printf("%s",s); //Hel456789
strcpy的溢出性:如果目标字符数组的长度比源字符数组的长度短,strcpy函数会导致溢出,即目标字符数组地址后面的内存地址会被写入多出的字符,产生不可预期的后果。
char a = 'a';
char s[3];
strcpy(s,"Hello"); //s-->Hello\0 --> 可能会导致a被修改
连接函数strcat(目标字符数组,源字符数组),将源字符数组的内容连接到目标字符数组后面,第一个连接过去的字符替代了目标字符数组的’\0’,函数返回目标字符数组。
char s1[20] = "i am";
char s2[] = "a boy";
strcat(s1,s2); //s1-->i am a boy\0
printf("%s",s1); //i am a boy
比较函数:C语言中用strcmp函数对字符串进行比较:strcmp(字符数组1,字符数组2),返回值是:
字符数组1 = 字符数组2, 返回值=0字符数组1> 字符数组2, 返回值>0字符数组1< 字符数组2, 返回值<0
当需要判断两个字符串是否相等,例如判断密码是否为“137000”,代码如下:
char pwd[] = "137000";
int b = strcmp(pwd,"137000"); //b == 0
长度计数函数strlen:strlen(字符数组)返回字符串的长度,计算到\0为止(不包括\0),如
char s[20] = "hello";
printf("%d",strlen(s)); //5
strlen与sizeof的区别
char s[10] = "hello";
printf("%d",strlen(s)); //5
printf("%d",sizeof(s)); //10
1)sizeof是编译时计算,关注分配了多少空间,不关注实际存储的数据2)strlen是运行时计算,关注存储的数据内容,不关注分配的空间。
函数
概念:函数是程序的一个包含单元,用于完成某一特定的任务,比如一类计算、一种操作等等,使用函数对这些任务进行封装,减少重复编码,使程序更加模块化,增强可读性和可维护性。例如
int main(){
//打印三个*行
int i;
for(i=0; i<3; ++i)
printStar();
}
函数的调用:函数是一个黑匣子,外部无需知道函数里面具体怎么执行的,只需要调用函数,获取返回的结果即可。
函数定义的语法:返回值类型、函数名、形参列表、函数体,例如
返回值类型 函数名(参数列表)
void printStar(int n, char c){
//在这里输出*号组成的行
}
函数需要先定义、后调用,即函数的定义应当在主调函数之前,如
void printStar(int n, char c){}
int main(){
printStar(5,'*');
...
}
有时候我们想先调用函数,之后再定义函数,则需要在调用之前先声明,这和变量的声明、使用、赋值类似,如
int main(){
void printStar(int n, char c); //声明,注意没有函数体
printStar(5,'*');
}
void printStar(int n, char c){//定义这里有函数体}
调用系统库函数,要使用#include,在c语言中,库文件使用*.h文件,例如
#include
#include //使用库文件
int main(){
printf("%d的绝对值是%d", -3,abs(-3)); //输出绝对值
return 0;
}
函数的执行过程
程序的执行入口是main函数,从main函数体的起始处开始执行,main函数体的所有代码都执行完成后会自动退出程序。
遇到对其它函数的调用,则暂停当前执行,保存当前的状态现场和返回地址,转到被调用函数的入口地址进行执行,当遇到return语句或函数执行结束时,恢复之前保存的现场,从返回地址处开始继续执行。例如
#include
int add(int a, int b){
printf("\t开始执行add函数,参数%d,%d\n",a,b);
int sum = a + b;
printf("\tadd执行完成,准备返回%d\n",sum);
return sum;
}
int main(){
printf("开始执行main函数\n");
printf("调用add函数\n");
int a = add(3,5);
printf("调用add函数返回:%d\n",a);
a = add(1+2,2*4);
printf("调用add函数返回:%d\n",a);
printf("main函数执行结束\n");
return 0;
}
=====运行开始======
开始执行main函数
调用add函数
开始执行add函数,参数3,5
add执行完成,准备返回8
调用add函数返回:8
开始执行add函数,参数3,8
add执行完成,准备返回11
调用add函数返回:11
main函数执行结束
=====运行结束======
参数列表
形参:在函数定义时,每个参数定义是“类型 变量名”的声明格式,由逗号分隔。
int add(int a, int b){
//在这里可以使用变量a和b
}
参数列表的定义相当于定义了局部变量,其作用域是函数体,在函数没有被调用时,形参没有内存分配,当函数调用时被分配内存,函数结束后,内存被释放。
实参:在函数调用时,传入与形参类型一致、个数一致的参数列表,如果不相同会执行自动格式转换。调用时不需要在前面加变量类型。
int main(){
int sum = add(3.2, 5); //3.2传给a(自动转换),5传给b
}
值传递
值传递:实参传入数据时,传递给形参(函数内局部变量)的是实参的值而不是变量本身(地址),对形参的任何修改不会影响到实参。
int change(int a){
return ++a;
}
int main(){
int a = 5;
change(a);
printf("%d",a); //a还是5,不会变成6
}
内存和存储类型
C语言中,程序占用的内存分为代码区、数据区(静态存储区)、动态存储区(又分为栈和堆)。
1)静态存储区:主要存放全局变量、静态变量(static)、字面常量,其生存期是从程序开始到程序结束;2)动态存储区:主要存放局部变量、参数等,在程序运行期间动态分配,其中栈和堆的使用又有所不同。
自动变量:说明符为auto,凡是函数内未加存储类型说明的都是auto类型,例如
{
int a; ---> auto int a;
}
自动变量属于动态存储方式,当函数被调用时才分配存储,函数结束即自动释放。作用域是定义它的单元,如函数或代码块。
静态变量:说明符为static,在函数中的static变量属于静态存储方式,程序启动时被分配,程序结束后释放。作用域取决于其定义的位置,函数内定义的作用域就是函数体,函数外定义的作用域就是本文件。例如
{
static int a; //函数结束不会释放,下次还可以继续用
}
static的理解:具备静态存储特性,又具备函数的作用域。
注:全局变量或函数前面加static,表示作用域在本文件内部,不能被其它文件引用。
外部变量:说明符为extern,全局变量的默认方式,当文件中要引用另一文件中的全局变量,或者在全局变量定义之前要引用它时,可以用extern做说明。例如
file1.cpp file2.cpp
int global;
寄存器变量:说明符为register,建议将变量存储在CPU的寄存器中,效率非常高。例如
int main(){
register int i, sum=0;
}
函数的嵌套调用和递归调用
在函数定义中允许再调用其它函数,即函数可以嵌套调用。
int sum(int a, int b){return a+b;}
int avg(int a, in b){return sum(a,b)/2;}
函数内调用自身称之为递归调用,这种函数称为递归函数。
int sum(int a){return a+sum(a+1);}
栈溢出:当调用函数时,当前状态现场、返回地址要被保存到“栈”(STACK)里,等到函数调用结束时恢复现场,从返回地址继续执行,如果递归函数没有控制,一直递归下去,就会导致栈溢出(StackOverflowException),所以递归函数切记要考虑使用计数或状态来终止递归循环。
递归的2个条件:
1)有反复执行的过程(自己调自己)
2)有跳出反复过程的条件(递归出口)
把数组作为函数参数
数组名作为函数的参数,传递的是数组的地址,实参和形参指向同一个地址,所以形参元素被修改会影响到实参。如
//由于传递的是地址,不需要指定个数,需要传入个数的参数
void changeArr(int a[], int len){
a[0] = 100;
}
int main(){
int a[5] = {1,2,3,4,5};
changeArr(a,5); //传入的是数组名
printf("%d",a[0]);
}
由于传入是地址,数组参数不需要指定个数,需要另外传入数组的长度参数(防止越界),如计算数组最大的数:
int max(int a[], int len){
int i, max = a[0];
for(i=0; i
if(max
max = a[i];
return max;
}
结构体
结构体概述
定义:将不同类型的数据组合在一起,构造出一种新的数据类型,这种形式称为“结构体”,声明结构体的语法关键字是struct:
struct student //student是新定义的结构名
{
int no; //学号
char name[10]; //姓名
int age; //年龄
char address[30]; //地址
}; //注意分号
结构体体现了数据的封装性,用结构体变量进行信息传递,一方面可以减少参数的数量,另一方面当业务逻辑有变化时,可以很方便的修改结构体的定义,不需要修改传递接口(函数定义)。例如:
struct car {
...
}
//当汽车增加新成员字段时,不需要修改buyCar的函数定义
void buyCar(struct car c){
....
}
结构体变量作为函数参数时,采取的是值传递的方式,即形参产生了和实参同样数据的一个内存拷贝。一方面,结构体数据多的时候内存开销大,另一方面,函数里面对数据的修改不会影响到实参。如
void changeCar(struct car c){
c.price = 200;
}
struct car c = {"宝马","X5","3.0","红色",130};
changeCar(c); //在函数里修改价格
printf("%d",c.price); //价格还是130
如果想让结构体变量在函数中被修改,跟其他类型一样,可以使用指针,如
void change2(struct car *c){
c->price = 200; //指针引用成员用 -> 运算符
}
change2(&c);
结构体作为返回值:当函数需要返回多个值时,可以用结构体变量。例如
struct point {
int x;
int y;
};
struct point createPoint(){
struct point p = {0,1};
return p;
}
联合体
将不同的数据类型组合起来,并且共享同一内存,这样的数据类型称之为联合体(union),联合的定义声明方式和结构体(struct)类似,例如。
union test {
int i;
char c;
float f;
} a, b, c;
注意:一个联合体变量一次只能给一个成员赋值,后赋值会覆盖前面的赋值,只有最后赋值的成员才有意义。
#include
union test{
int i;
char c;
float f;
};
int main(){
union test t ;
printf("i=%d,c=%d,f=%f\n",t.i,t.c,t.f);
t.i = 10;
printf("i=%d,c=%d,f=%f\n",t.i,t.c,t.f);
t.c = 'a';
printf("i=%d,c=%d,f=%f\n",t.i,t.c,t.f);
t.f = 1.0f;
printf("i=%d,c=%d,f=%f\n",t.i,t.c,t.f);
return 0;
}
=====运行开始======
i=1314011120,c=-16,f=881720320.000000
i=10,c=10,f=0.000000
i=97,c=97,f=0.000000
i=1065353216,c=0,f=1.000000
=====运行结束======
枚举:如果一个变量的值只有几种可能,可以将这些值列举出来,定义为枚举类型(enum)。例如:
enum weekday {sun,mon,tue,wed,thu,fri,sat};
enum weekday day1 = sun; //变量的值只能是枚举元素之一
#include
enum weekday {sun,mon,tue, wed,thu,fri,sat};
int main(){
enum weekday day1 = sun, day2;
printf("day1 = %d\n",day1);
day2 = (enum weekday)(day1 + 3);
printf("day2 = %d\n",day2);
if(day2 == wed)
printf("星期天过3天是星期三\n");
return 0;
}
使用typedef可以为已有类型定义新的类型名,往往可以使用我们自己定义的结构,这样很好地具现化出真正的对象结构。如
typedef struct student{
int no;
string name;
} STUDENT; //定义新的结构体类型STUDENT
STUDENT zhangsan;
指针
概述
变量的内存结构:类型、变量名、地址、数据:
int n = 10;
2001H~2004H ----> 10
&n (int有4个字节)
指针:变量的指针就是变量的地址(内存中的首地址)
指针变量:指针变量是一种特殊的变量,它的数据是内存的一个地址(unsigned long int),通过这个内存可以找到此内存本身存储的数据。如:
int a = 10
int *p; //定义一个指针变量,指针的类型是int
p = &a; //p是指针变量,p的数据是a的地址
p p的数据是地址 地址指向的值
2005H ---> 2001H --> 10
*p --> a的地址里面的数据 --> 10
*是“间接运算符”,用来定义一个指针变量,以及取指针变量指向的数据。赋给指针变量的值必须是地址常量或变量,不能为普通整数(可以为 0表示空指针),此地址中存放的数据类型必须与指针类型相符。如
1)int *p; //定义指针变量 p
int *p = &a; ---> int *p; p=&a
2)printf("%d\n",*p); //取指针变量p指向的数据
*(&a) --> a //取&a这个地址(指针)指向的数据
指针修改值
*p = 5; //把指针的值修改为5,n == 5
void类型的指针可以赋予任何类型对象的地址,如
int n; char c;
void *p;
p = &n;
p = &c;
注意:void类型指针不能直接输出或运算,需要转换成指定类型。
void *pv;
int *pint, n;
pv = &n;
pint = (int*)pv; //强制类型转换
指针的算术运算
指针相减:指向同一个数组的两个指针相减,返回他们之间的数组元素个数。如
int n[5];
int *p1=n,*p2=&n[3];
p1-p2 --> 3
注:不是同一数组的指针相减没有意义。
指针与整数的加减:指针变量与整数的加减是一个算术表达式,返回新的指针变量,指向的地址为原指针地址加减指针指向类型的整数倍,例如
int n, *p=&n; //int是4字节
p+1 --> (n的地址 + 4 )的新指针
自增自减:++和--可用于指针运算,、++、--的优先级相同,都是右结合性。例如:p++ –> 先计算*p,再将p指向p+1个类型长度的地址。
int a=10,b=20,c=30;
int *p=&b;
*p --> 20
*p++ --> 20,但此时p指向 p+1的 地址(4个字节)
指针的关系运算
当两个指针p和q指向同一数组中的元素时,可以进行比较:
p
p>q p指向的元素在q指向的元素之后,返回1,否则0
p==q 两者指向同一元素,返回1,否则0
p!=q 不指向同一元素,返回1,否则0
与NULL比较:
p==NULL 空指针返回1,否则0
p!=NULL 不是空指针返回1,否则0
指针与常量
指向常量的指针:指针变量指向一个常量,此时指针所指向的值不能修改,但指针本身(存储的地址)可以指向另外的对象,例如
int a = 3, b = 10;
const int *p = &a;
*p = 5; //报错,不能修改
p = &b; //可以修改
指针常量:声明指针常量,则指针本身不能改变。例如
int a=3, b=10;
int *const p = &a;
p = &b; //报错,不能修改
*p = 5; //可以修改
理解:看const后面是什么,如果是*p则表示指针的值不能修改,如果是p则表示指针不能更换指向。
指针与结构体
结构体类型的指针变量,定义方式为
struct 结构体类型 *变量名;struct student stu, *p;
p= &stu;
成员的引用:访问成员有以下三种方式:
stu.name;
(*p).name;
p->name; //指针专用,指向运算符,优先级比单目运算符高
p->age++; //先获得age成员,再++
指针作为函数参数
函数参数是指针变量时,传递的是指针存储的地址,而变量做参数时传递的是具体的值(会产生形参的拷贝)。指针做参数时,地址(指针自己的值)不能被修改,但地址指向的值可以被修改。例如
void change(int *p){
*p = 20;
}
int main(){
int a=5;
change(&a); //传入指针,在函数里a的值被修改
}
函数指针:回调函数,又称callback,是一种事件驱动的编程模式,将函数指针变量作为参数传递给另外一个函数,函数里面当某些事件满足时会调用此函数指针变量指向的函数,例如。
void print(int n){ //回调函数,打印信息
printf("回调函数调用:%d\n",n);
}
void loop(int max, int (*p)(int n)){
//遍历1..n,如果7的倍数就打印信息
int i;
for(i=0;i
if(i%7==0) p(i);
}
}
这篇文章属于学习笔记,学习的网站为itbegin.com,希望读CS的朋友能有更多的交流。:)
1楼chons学习了Re: Rudolph_Browne@chons,有点混乱,谢谢。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)