编译预处理
预处理概念:在编译之前进行处理
在C语言中,预处理行为宏定义,文件包含,条件编译
指令 | 用处 |
# | 无 |
#define | 定义一个宏 |
#undef | 取消定义一个已经定义的宏 |
#include | 包含一个头/源文件 |
#if | 若为真,编译以下代码 |
#ifdef | 若宏已定义,则编译以下代码 |
#ifndef | 若宏没有定义,这编译以下代码 |
#elif | 若前#if为假,此处为真,则编译以下代码,相当于elseif |
#endif | 结束一个#if...#else条件编译快 |
#error | 停止编译并显示错误信息 |
#line | 强行改变的文件号和行号 |
宏定义:指的是批量处理,即使用简单的操作来替代复杂的操作,在C语言中,宏定义可以使用一个标识符替代一个字符串
一般形式:
#define 标识符 [(参数)] 字符串
优点:方便程序编辑与修改,与使用自定义函数相比,使用宏定义无需“保留函数现场”,可提高运行效率,
缺点:嵌套影响可读性,带参定义容易出现参数非法的问题,宏定义不易执行复杂问题。
#include #define x1 3*x*x#define x2(r) (3*(r)*(r)#define x3 "明天星期几?"
字符串
以0(整数0)结尾的一串字符
0或‘\0’是一样的,但是和‘0’不同
0标志字符串的结束,但它不是字符串的一部分
计算字符串长度的时候不包含这个0
字符串以数组的形式存在,以数组或指针的形式访问
更多的是以指针的形式
string.h里有很多处理字符串的函数
指针
计算机系统中运行的程序和数据存储在计算机内部存储器中,把存储器中的一个字节成为一个存储单元也成内存单元。
内存是由线性连续的存储单元组成。
C语言中的每一个实体,都要在内存中占有一个可标识的存储区域,称为存储空间
在内存中每一个存储单元都有一个地址,根据地址可以找到存储单元的值,将这个地址称为存储单元指针,简称指针
存储空间的第一个字节的地址(起始地址,首地址)称为变量的地址,也称变量的指针。
指针变量的值是内存中的地址值,那么C语言中的存储空间`的首地址就可以赋值给指针变量,一个存储空间的首地址可以称为该实体的地址,也称为该实体的指针。由于每个实体都是连续存储的,当定义了指针变量后,就可以把某个实体的地址赋值给指针变量,然后通过指针变量的值来简介访问该实体。
指针可以利用地址,直接指向内存中的任意一个地址,并通过该地址找到地址当中存储的数据。
指针可以直接修改内存,因此指针的使用时快捷性,高效性,自由的,危险的。
注意:指针不仅可以修改C程序中的数据,更可以修改计算机内存的一切值,在不清楚后果的情况下,这种修改是危险的
指针可以指向一个变量对应的地址,但不能直接将这个地址按照整数形式赋值给指针变量
%p // 按照指针形式输出数据printf("%d",grade) //输出grade的值printf("%d",&grade) //输出grade的内存地址int grade // grade会被赋值随机值int *grade //会被赋值随机地址sizeof() //占空间的字节数
指针的定义:
数据类型 * 指针名
int * ptr_num,*ptr_Maxfloat * p_averagechar * p_ch1,*P_ch2
注:在定义时:写的*是一个说明,标志其后的变量是指针变量,如int * ptr_Max ,ptr_Max是指针名称
与普通变量一样,指针先定义,再赋值,再使用,对于指针的赋值,一般理解为指向。
定义指针时的数据类型应当与指针指向的数据类型一致。
指针的初始化:
指针变量可以初始化一个地址,0或NULL,(指针变量使用过程中必须要有确定的值)
数据类型 * 指针名 = 变量地址如:int * point_num =#// & :地址运算符,取变量的地址// * :指针运算,去指针指向的内存地址中存储的值// *与& 互为逆运算
指针的赋值
将变量所在的地址赋值给指针(直接赋值)
将指针指向的地址赋值给指针(简介赋值)
指针的运算:
算数运算就是对指针变量加上或减去一个整数,使指针变量指向相邻的存储单元。
如果指针运算时指针前带有*,相当于指针指向的内存地址中存储的数据参与了运算,即数值相运算。
如果直接用指针参数运算,指针的加,减,代表内存中向高,低地址方向,移动一个指针类型所占据的内存空间。
数据表示两个指针指向内存之间相隔多少个元素。
指针相减或比较大小时,必须是同一数据类型。
(字符串以“\o”结尾)
说明:
char *pp =str ,相当于指针pp指向了字符串,str的首地址,并非指针pp相当于整个字符串。
运行PP++可以实现指针PP在字符串中指向下一个字符
由于单目运算符的结合律均从右向左,因此*pp++的市级意义为*(PP++),相当于PP++,* 没有起作用。
同理,++*p相当于++(*p),即指针p指向变量的值自增1,而非指针p向下移动。
指针的比较达标其指向变量的地址的大小比较
地址越靠前越小
取地址预算符&
#include "stdio.h"int main(){ char c ='A',*p=&c; // 类型说明符“*”,取地址运算符“&” printf("int c=%d\t char c =%c\t &c=0x%x\n",c,c,&c); printf("int*p =%d\t char*p=%c\t p=%#X\n",*p,*p,p); return 0;}
输出:
int c=65 char c =A &c=0x61ff1bint*p =65 char*p=A p=0X61FF1B
间接访问运算符“*”
#include "stdio.h"int main(){ int i=37,*p=&i; printf("i=%d,*p=%d\n",i,*p); *p=5; *p*=*p+15; //注意间接访问符与乘数运算符 printf("i=%d,*p=%d\n",i,*p); return 0;}
输出:
i=37,*p=37i=100,*p=100
间接运算符*是一元运算符,也就是说:间接运算符只是一个操作数,
double x,y,*ptr; //两个double变量和一个double指针ptr = &x; //使得ptr指向x的 地址*ptr = 7.8; // *ptr 等效于变量x本身// 注意如果没有ptr=&x为ptr分配有效地址,那么所包含*ptr的语句都是// 没有意义的,有可能会赵成程序崩溃。*ptr *=2.5; //将x乘以2.5y = *ptr + 0.5 //将加法x+0.5 的结果赋值给y
指针与数组
一维数组中的每一个元素都具有相同的数据类型并分配了相同大小的存储空间,数组元素的地址等于该元素相对数组首地址的偏移量。指针变量既然可以指向变量,当然也可以指向数组元素(把某一元素的地址存放到一个指针变量当中),数组袁术的地址就是数组元素的指针,可以用一个指针变量指向一个数组元素。
int a[3] ={10,20,30};int *p;p=&a[0]; //把a[0]元素的地址赋值给指针变量,
索引一维数组方法:
下标法(即使数组名和下标值),如a[1] 就是用下标法表示数组的第二个元素。
地址法(指针法,位移法):即通过给出地址访问数据元素,例如:通过a+2地址可以找到素组a第三个元素a[2],
*(a+2)就是a[2].因此,*(a+i)与a[i]等价,都是指数组a的第i+1个元素的值
说明:用下标法访问数组元素时,是把a[i] 转化成*(a+i)处理的。即先计算出数组元素的地址(a+i),然后再找到它指向的存储空间,读出或写入它的值。而数组元素用地址法(指针法,位移法)访问时,则不必每次计算数组元素的地址,特别是使用P++这样的操作是比较快的(切记注意下标越界)
指针与二维数组:
在C语言中,一维数组名代表了该数组的首地址,也就是该数组的第一个元素的起始地址,它是一个指向数组第一个元素的指针,在一维数组并定义后,编译系统就会为该数组分配存储空间,其首地址也就确认了,所以数组名实际上是一个指针常量。
一般形式
[存储类型]数据类型 (*指针变量名)[数组长度]int a[4][3] ={{-1,-2,-3},{-4,-5,-6},{-7,-8,-9},{-10,-11,-12}};int (a*p)[3];// 指针变量q指向一个具有3个int型元素的一维数组q=a; //把二维数组的首地址给q,p指向二维数组的第一行
二维数组a中的元素以下8种方法
//下标-下标法a[i][j]q[i][j]//下标-位移法*(a[i]+j)*q([i]+j)//位移-下标法(*(a+i))[j](*(q+i))[j]//位移-位移法*(*(a+i))+j)*(*(q+i))+j)
指向数组元素的指针变量与指向数组的指针变量的区别
#include "stdio.h"int main(){ int a[4][3]={{1,2,3},{4,5,6},{7,8,9},{10,11,12}}; int i,j,count=1,sum=1; int*p=a[0]; //定义 一个int*类型的指针变量p,p是一级指针 int (*q)[3] =a; //指向数组的指针变量q,a是int(*)[3]类型的二级指针 for(;p { printf("%d",*p); } printf("\n------------\n"); for(i=0;i<4;i++){ for(j=0;j<3;j++,count++) { sum=sum+(*(a+i))[j]; if (count%3!=0) printf("a[%d][%d]=%-2d,",i,j,(*(a+i))[j]); //当二级属为3时,换行 else printf("a[%d][%d]=%d\n",i,j,(*(a+i))[j]); } } for(i=0;j<3;j++){ for (j=0;j<3;j++) { printf("%d+",(*(q+i))[j]); } return 0; }}
输出:
123456789101112------------a[0][0]=1 ,a[0][1]=2 ,a[0][2]=3a[1][0]=4 ,a[1][1]=5 ,a[1][2]=6a[2][0]=7 ,a[2][1]=8 ,a[2][2]=9a[3][0]=10,a[3][1]=11,a[3][2]=12
指针与字符串
指向字符串的指针变量
定义char * 指针变量名1[,....,*指针变量名n]//符号“[]”展示它括起来的内容是可选项初始化char * 指针变量名1="字符串1" [,......,*指针变量名n="字符串n"];例如:char * dl = "sunday"上面的语句等价于char * dl;dl="sunday"
字符数组的定义
char 数组名1[下标1] ="字符串常量1"[,....,数组n[下标n]="字符串常量n"];例如:char ch[30] = "guangdong China"char ch[] = "guangdong china"char *ch ="guangdong china"
从键盘中输入一个月份号,则程序输出对应月份的英文名
#include "stdio.h"#include "string.h"char *English_month[]={"illegal month","jannuary","februray","March","April","May","June","July","August","September","October","November","December"};char*month_name(char **q,int n){ if (n<1||n>12) return *q; else return *(q+n);}int main(){ int i; printf("please enter a number of month:"); while (scanf("%d",&i)!=EOF) { printf("It is %s.\n",month_name(English_month,i)); }}
指针与函数:
关系
指针作为函数的参数
函数返回值是指针
指向函数的指针
返回指针值的函数
类型标识符 * 函数指针名 (函数参数表) // 函数参数表 表示函数的形参个书和类型 char * sort(int a[] ,int n); //此语句定义了一个返回指针值的函数sort(), // 该函数返回一个字符类型数据的指针,包含两个形参 // 包含n个元素的字符型数组a,第二个是整型变量n
空指针有别于其他指向对象或函数的有效指针,因此,当返回值为指针的函数出现执行失败的情况时,它通常会使用空指针作为返回值。标准函数fopen()正是这样的一个例子,如果指定模式下打开某文件失败使,该函数会返回一个空指针
NULL概念:
数据库中的NULL表示空,连“\0”都没有的空
在内存中,NULL代表地址为全0的一个地址,不存储任何东西。
将定义而未初始化的指针指向NULL,可以有效的防止指针指向未知内存空间而造成的不可预料的错误。
string.h
strlen
size_t strlen(const char *s); //const 不变字符串,返回s的字符串长度(不包括结尾的0)
案例:
#include "stdio.h"#include "string.h"int main(int argc,char const *argv[]){ char line[] ="Hello"; printf("strlen=%lu\n",strlen(line)); printf("sizeof=%li\n",sizeof(line)); // 计算了结尾额/0 return 0;}// 输出:strlen=5sizeof=6
strcmp:比较字符串
int strcmp(const char*s1,const char*s2)比较两个字符串,返回0:s1==s21: s1 > s2-1: s1 < s2
strcpy
char *strcpy(char *restrict dst,const char*restrict src);把src的字符串拷贝到dstrestrict表明src和他不重叠返回dst为了能链起代码来
strcat
char *strcat(char *restrict s1,const char *restrict s2);把s2拷贝到s1的后面,接成一个长的字符串返回s1s1必须具有足够的空间
字符串中找字符
char *strchr(const char*s,int c); // 在前面字符串中找c,忽略大小写char *strrchr(const char *s,int c);返回NULL表示没有找
指针的应用
//交换两个变量的值void swap(int *pa,int *pb){ int t =*pa; *pa =*pd; *pd=t; }
函数返回运算状态,结果通过指针返回
常见的套路是让函数返回特殊的不属于有效范围内的值来表示出错
-1或0(在文件操作会看到大量的例子)
但是当任何数值都是有效的结果时,就的分开返回
数组标量是特殊的指针
putchar
int putchar(int c);向标准输出写一个字符返回写了几个字符,EOF(-1) 表示写失败
getchar
int getchar(void);从标准输入读入一个字符返回类型是int是为了返回EOF(-1)
实例;
#include "stdio.h"int main(int argc,char const *argv[]){ int ch; while ((ch=getchar()) != EOF ){ putchar(ch); } printf("EOF\n"); return 0;}