小结
字符篇
字符
char c = 'a'; char c[2] = {'a','b'}; char *c='a'; char *c[] = {'a','b'};
字符串
char s = "a";//以'\0'结束 char s[3]="ab"; char s[3]={'a','b','\0'};//必须加这\0字符串结束标志 string s="abc";//C语言中没有string
归纳
指针篇
一级指针
#include <stdio.h> int main () { int var = 20; /* 实际变量的声明 */ int *ip; /* 指针变量的声明 */ ip = &var; /* 在指针变量中存储 var 的地址 */ /* 使用指针访问值 */ printf("Value of *ip variable: %d\n", *ip ); return 0; }
在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值.
int *ptr = NULL;
如需检查一个空指针,您可以使用 if 语句,如下所示:
if(ptr) { } /* 如果 p 非空,则完成 */ if(!ptr){ } /* 如果 p 为空,则完成 */
多级指针
#include <stdio.h> int main () { int var; int *ptr; int **pptr; ptr = &var; /* 获取 var 的地址 */ pptr = &ptr;/* 使用运算符 & 获取 ptr 的地址 */ return 0; }
归纳
指针的类型
从语法的角度看,只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。
int*ptr; //指针的类型是int* char*ptr; //指针的类型是char* int**ptr; //指针的类型是int** int(*ptr)[3]; //指针的类型是int(*)[3] int*(*ptr)[4]; //指针的类型是int*(*)[4]
指针所指向的类型
从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。例如:
int*ptr; //指针所指向的类型是int char*ptr; //指针所指向的的类型是char int**ptr; //指针所指向的的类型是int* int(*ptr)[3]; //指针所指向的的类型是int()[3] int*(*ptr)[4]; //指针所指向的的类型是int*()[4]
数组篇
一维数组
#include <stdio.h> int main () { int n[ 10 ]; int i; for ( i = 0; i < 10; i++ ) { n[ i ] = i + 100;/* 初始化数组元素,设置元素 i 为 i + 100 */ printf("Element[%d] = %d\n", i, n[i] );/* 输出数组中每个元素的值 */ } return 0; }
多维数组
#include <stdio.h> int main () { /* 一个带有 5 行 2 列的数组 */ int a[5][2] = { {0,0}, {1,2}, {2,4}, {3,6},{4,8}}; int i, j; /* 输出数组中每个元素的值 */ for ( i = 0; i < 5; i++ ) { for ( j = 0; j < 2; j++ ) { printf("a[%d][%d] = %d\n", i,j, a[i][j] ); } } return 0; }
结构体篇
struct tag { member-list member-list member-list ... } variable-list ;
tag 是结构体标签。
member-list 是标准的变量定义,比如 int i; 或者 float f,或者其他有效的变量定义。
variable-list 是结构变量,定义在结构的末尾,最后一个分号之前,可以指定一个或多个结构变量。
//方法一: 结构体没有标明其标签 struct { int a; char b; double c; } s1;
//方法二: 结构体的标签被命名为SIMPLE,没有声明变量 struct SIMPLE { int a; char b; double c; }; //用SIMPLE标签的结构体,另外声明了变量t1、t2、t3 struct SIMPLE t1, t2[20], *t3;
//方法三: 用typedef创建新类型 typedef struct { int a; char b; double c; } Simple2; //现在可以用Simple2作为类型声明新的结构体变量 Simple2 u1, u2[20], *u3;
在上面的声明中,方法一和方法二 声明被编译器当作两个完全不同的类型,即使他们的成员列表是一样的,如果令 t3=&s1,则是非法的。
结构体的成员可以包含其他结构体,也可以包含指向自己结构体类型的指针,而通常这种指针的应用是为了实现一些更高级的数据结构如链表和树等。
//此结构体的声明包含了其他的结构体 struct COMPLEX { char string[100]; struct SIMPLE a; };
//此结构体的声明包含了指向自己类型的指针 struct NODE { char string[100]; struct NODE *next_node; };
如果两个结构体互相包含,则需要对其中一个结构体进行不完整声明,如下所示:
struct B; //对结构体B进行不完整声明 //结构体A中包含指向结构体B的指针 struct A { struct B *partner; //other members; }; //结构体B中包含指向结构体A的指针,在A声明完后,B也随之进行声明 struct B { struct A *partner; //other members; };
函数篇
#include <stdio.h> int max(int num1, int num2);/* 函数声明 */ int main () { int a = 100;int b = 200; //局部变量定义 int ret; //局部变量声明 ret = max(a, b);/* 调用函数来获取最大值 */ return 0; } /* 函数返回两个数中较大的那个数 */ int max(int num1, int num2) { int result; if (num1 > num2) result = num1; else result = num2; return result; }
升级篇
指针与数组
数组指针 Type (*p)[n]
数组指针:是指针——指向数组的指针。
#include <stdio.h> int main() { //一维数组 int a[5] = { 1, 2, 3, 4, 5 }; int(*p)[5]; p = &a;//把数组a的地址赋给p,则p为数组a的地址,则*p表示数组a本身 printf("%p\n", a); //输出数组a的地址,一般用数组的首元素地址来标识一个数组 printf("%p\n", p); //根据上面,p为数组a的地址,输出数组a的地址 printf("%p\n", *p); //*p表示数组a本身,一般用数组的首元素地址来标识一个数组 printf("%p\n", &a[0]); //a[0]的地址 printf("%p\n", &a[1]); //a[1]的地址 printf("%p\n", p[0]); //数组首元素的地址 printf("%d\n", **p); //*p表示地址,则*(*p)表示值,当*p表示数组首元素地址时,**p表示首元素本身,即首元素的值1 printf("%d\n", *p[0]); //根据优先级,p[0] 表示首元素地址,则*p[0]表示首元素本身,即首元素的值1 printf("%d\n", *p[1]); //错误,不表示a[1]...表示什么我还不知道 //将二维数组赋给指针 int b[3][4]; int(*pp)[4]; //定义一个数组指针,指向含4个元素的一维数组 pp = b; //将该二维数组的首地址赋给pp,也就是b[0]或&b[0],二维数组中pp=b和pp=&b[0]是等价的 pp++; //pp=pp+1,该语句执行过后pp的指向从行b[0][]变为了行b[1][],pp=&b[1] int k; scanf_s("%d", &k); return 0; }
指针数组 Type *p[n]
指针数组:是数组——装着指针的数组。
#include <stdio.h> int main() { int a = 1; int b = 2; int *p[2]; p[0] = &a; p[1] = &b; printf("%p\n", p[0]); //a的地址 printf("%p\n", &a); //a的地址 printf("%p\n", p[1]); //b的地址 printf("%p\n", &b); //b的地址 printf("%d\n", *p[0]); //p[0]表示a的地址,则*p[0]表示a的值 printf("%d\n", *p[1]); //p[1]表示b的地址,则*p[1]表示b的值 //将二维数组赋给指针数组 int *pp[3]; //一个一维数组内存放着三个指针变量,分别是p[0]、p[1]、p[2],所以要分别赋值 int c[3][4]; for (int i = 0; i<3; i++) pp[i] = c[i]; int k; scanf_s("%d", &k); return 0; }
归纳
数组指针是一个指针变量,占有内存中一个指针的存储空间;
指针数组是多个指针变量,以数组的形式存储在内存中,占有多个指针的存储空间。
char a[3];
数组名a代表首元素的首地址,也就是相当于&a[0],而a[0]的类型为char,因此&a[0]类型为char *,因此,可以定义如下的指针变量:
char * p = a;//相当于char * p = &a[0];
a和&a[0]代表的都是数组首元素的首地址,&a虽然在数值上也等于数组首元素首地址的值,但是其类型并不是数组首元素首地址类型,也就是char *p = &a是错误的。
&a对数组名取地址,类型为整个数组,&a的类型是char (*)[3],所以正确的赋值方式如下:
char (*p)[3] = &a;
对a+1和&a+1的区别时,
a 代表数组首元素首地址, 类型为char *, 因此 a+1 相当于数组首地址值+sizeof(char)
&a 代表整个数组, 类型为char (*)[3], 因此 &a+1 相当于数组首地址值+sizeof(a)
(sizeof(a)代表整个数组大小,但是无论数组大小如何,sizeof(&a)永远等于一个指针变量占用空间的大小)
指针与函数
函数指针 Type (*func)()
例如:
void (*func)(); //声明函数指针 int Function(); //声明普通函数
把函数的地址赋值给函数指针,可以采用下面两种方式:
func=&Function; //方式一 func=Function; //方式二
取地址运算符&不是必需的,因为函数名表示它的地址,如果是函数调用,还必须包含一个圆括号括起来的参数表。如下两种方式来通过指针调用函数:
x=(*func)(); //方式一 x=func(); //方式二
方式二看上去和函数调用无异。但是有些程序员倾向于使用第一种格式,因为它明确指出是通过指针而非函数名来调用函数的。
void (*funcp)(); void FileFunc(),EditFunc(); main() { funcp=FileFunc; //传地址 (*funcp)(); //调用函数 funcp=EditFunc; (*funcp)(); } void FileFunc(){ printf("FileFunc\n"); } void EditFunc(){ printf("EditFunc\n"); }
指针函数 Type *func()
int* GetDate(); int* aaa(int,int);
函数返回的是地址值。
int* GetDate(int wk,int dy);//指针函数,返回int* 即地址 main(){ int wk,dy; do{ printf("Enter wk(1-3)\tdy(1-2)\n"); scanf("%d%d",&wk,&dy); } while(wk<1||wk>3||dy<1||dy>2); printf("%d\n",*GetDate(wk,dy)); } int* GetDate(int wk,int dy) { static int calendar[3][2]= {{1,2},{2,3},{5,6},}; return &calendar[wk-1][dy-1]; }
函数参数
按值传递
(1)形参和实参各占一个独立的存储空间。
(2)形参的存储空间是函数被调用时才分配的,调用开始,系统为形参开辟一个临时的存储区,然后将各实参传递给形参,这是形参就得到了实参的值。
#include<stdio.h> void swap1(int x, int y)//定义中的x,y变量是swap函数的两个形参 { int tmp; tmp = x; x = y; y = tmp; printf("x=%d,y=%d\n", x, y); } int main() { int a = 2; int b = 3; swap1(a, b);//a,b变量为swap函数的实际参数 printf("a=%d,b=%d", a, b); return 0; } //输出结果为:x=3,y=2; a=2,b=3
地址传递
地址传递与值传递的不同在于,它把实参的存储地址传送给形参,使得形参指针和实参指针指向同一块地址。
因此,被调用函数中对形参指针所指向的地址中内容的任何改变都会影响到实参。
void swap2(int *px, int *py) { int tmp; tmp = *px; *px = *py; *py = tmp; printf("px=%d,py=%d\n", *px, *py); } int main() { int a = 2; int b = 3; swap2(&a, &b);/*调用了swap函数,同样也有隐含动作px=&a;py=&b;*/ printf("a=%d,b=%d", a, b); return 0; } //输出结果为*px=3,py=2 a=3,b=2;
引用传递
引用传递是以引用为参数,则既可以使得对形参的任何操作都能改变相应数据,又使函数调用方便。引用传递是在形参调用前加入引用运算符“&”。
引用为实参的别名,和实参是同一个变量,则他们的值也相同,该引用改变则它的实参也改变。
代码示例:
#include<stdio.h> void swap3(int &x,int &y) { int tmp = x; x = y; y = tmp; printf("x=%d,y=%d\n", x, y); } int main() { int a = 2; int b = 3; swap3(a, b);//调用方式与传值一样 printf("a=%d,b=%d", a, b); system("pause"); return 0; } //输出结果:x=3,y=2; a=3,b=2;
归纳
在C语言中,并没有引用的概念,引用是C++的概念。
在C++的函数参数中,有两种形式,其中 TYPE & var的形式,称为传引用方式; TYPE var的形式,称为传值。
二者的区别为,当传引用时,实际传到函数中的形参,是实际参数的一个引用,而不是仅传递值到函数中。具体的表现有以下几条:
1 传引用时,形参和实参是同一个变量,即使用相同的内存空间,二者有相同的地址。而传值时二者地址不同;
2 传引用时,由于没有新建变量,所以对于类对象参数,不会产生构造和析构。而如果是传值调用,调用时会进行构造,退出函数时会进行析构;
3 由于传引用使用的是原本实参的地址,所以对引用参数值的修改,会在退出函数后体现在主调函数中,而传值调用对参数的修改不会影响到主调函数。