C_数据结构与算法(一):C语言基础

致初学者的我:一切都是由浅入深。

233533295.png

每种语言都有每种语言的特性,基本的特性是相同的,下面依照惯例写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,有点混乱,谢谢。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐