C语言的“悔悟”
首先讨论指针和数组http://blog.jobbole.com/44863/
The 5-minute Guide to C Pointers
指针、引用和取值
什么是指针?什么是内存地址?什么叫做指针的取值?指针是一个存储计算机内存地址的变量。在这份教程里“引用”表示计算机内存地址。从指针指向的内存读取数据称作指针的取值。指针可以指向某些具体类型的变量地址,例如int、long和double。指针也可以是void类型、NULL指针和未初始化指针。本文会对上述所有指针类型进行探讨。
根据出现的位置不同,操作符 * 既可以用来声明一个指针变量,也可以用作指针的取值。当用在声明一个变量时,*表示这里声明了一个指针。其它情况用到*表示指针的取值。
&是地址操作符,用来引用一个内存地址。通过在变量名字前使用&操作符,我们可以得到该变量的内存地址。
1
2
3
4
5
6
7
8
9
|
// 声明一个int指针 int *ptr; // 声明一个int值 int val = 1; // 为指针分配一个int值的引用 ptr = &val; // 对指针进行取值,打印存储在指针地址中的内容 int deref = *ptr; printf ( "%d\n" , deref); |
第2行,我们通过*操作符声明了一个int指针。接着我们声明了一个int变量并赋值为1。然后我们用int变量的地址初始化我们的int指针。接下来对int指针取值,用变量的内存地址初始化int指针。最终,我们打印输出变量值,内容为1。
第6行的&val是一个引用。在val变量声明并初始化内存之后,通过在变量名之前使用地址操作符&我们可以直接引用变量的内存地址。
第8行,我们再一次使用*操作符来对该指针取值,可直接获得指针指向的内存地址中的数据。由于指针声明的类型是int,所以取到的值是指针指向的内存地址存储的int值。
这里可以把指针、引用和值的关系类比为信封、邮箱地址和房子。一个指针就好像是一个信封,我们可以在上面填写邮寄地址。一个引用(地址)就像是一个邮件地址,它是实际的地址。取值就像是地址对应的房子。我们可以把信封上的地址擦掉,写上另外一个我们想要的地址,但这个行为对房子没有任何影响。
void指针、NULL指针和未初始化指针 一个指针可以被声明为void类型,比如void *x。一个指针可以被赋值为NULL。一个指针变量声明之后但没有被赋值,叫做未初始化指针。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
int *uninit; // int指针未初始化 int *nullptr = NULL; // 初始化为NULL void *vptr; // void指针未初始化 int val = 1; int *iptr; int *castptr; // void类型可以存储任意类型的指针或引用 iptr = &val; vptr = iptr; printf ( "iptr=%p, vptr=%p\n" , iptr, vptr); // 通过显示转换,我们可以把一个void指针转成 // int指针并进行取值 castptr = ( int *)vptr; printf ( "*castptr=%d\n" , *castptr); // 打印null和未初始化指针 printf ( "uninit=%p, nullptr=%p\n" , uninit, nullptr); // 不知道你会得到怎样的返回值,会是随机的垃圾地址吗? // printf("*nullptr=%d\n", nullptr); // 这里会产生一个段错误 // printf("*nullptr=%d\n", nullptr); |
执行上面的代码,你会得到类似下面对应不同内存地址的输出。
1
2
3
|
iptr=0x7fff94b89c6c, vptr=0x7fff94b89c6c *castptr=1 uninit=0x7fff94b89d50, nullptr=(nil) |
第1行我们声明了一个未初始化int指针。所有的指针在赋值为NULL、一个引用(地址)或者另一个指针之前都是未被初始化的。第2行我们声明了一个NULL指针。第3行声明了一个void指针。第4行到第6行声明了一个int值和几个int指针。
第9行到11行,我们为int指针赋值为一个引用并把int指针赋值为void指针。void指针可以保存各种其它指针类型。大多数时候它们被用来存储数据结构。可以注意到,第11行我们打印了int和void指针的地址。它们现在指向了同样的内存地址。所有的指针都存储了内存地址。它们的类型只在取值时起作用。
第15到16行,我们把void指针转换为int指针castptr。请注意这里需要显示转换。虽然C语言并不要求显示地转换,但这样会增加代码的可读性。接着我们对castptr指针取值,值为1。
第19行非常有意思,在这里打印未初始化指针和NULL指针。值得注意的是,未初始化指针是有内存地址的,而且是一个垃圾地址。不知道这个内存地址指向的值是什么。这就是为什么不要对未初始化指针取值的原因。最好的情况是你取到的是垃圾地址接下来你需要对程序进行调试,最坏的情况则会导致程序崩溃。
NULL指针被初始化为o。NULL是一个特殊的地址,用NULL赋值的指针指向的地址为0而不是随机的地址。只有当你准备使用这个地址时有效。不要对NULL地址取值,否则会产生段错误。
指针和数组
C语言的数组表示一段连续的内存空间,用来存储多个特定类型的对象。与之相反,指针用来存储单个内存地址。数组和指针不是同一种结构因此不可以互相转换。而数组变量指向了数组的第一个元素的内存地址。
一个数组变量是一个常量。即使指针变量指向同样的地址或者一个不同的数组,也不能把指针赋值给数组变量。也不可以将一个数组变量赋值给另一个数组。然而,可以把一个数组变量赋值给指针,这一点似乎让人感到费解。把数组变量赋值给指针时,实际上是把指向数组第一个元素的地址赋给指针。
1
2
3
4
5
6
7
8
|
int myarray[4] = {1,2,3,0}; int *ptr = myarray; printf ( "*ptr=%d\n" , *ptr); // 数组变量是常量,不能做下面的赋值 // myarray = ptr // myarray = myarray2 // myarray = &myarray2[0] |
第1行初始化了一个int数组,第2行用数组变量初始化了一个int指针。由于数组变量实际上是第一个元素的地址,因此我们可以把这个地址赋值给指针。这个赋值与int *ptr = &myarray[0]效果相同,显示地把数组的第一个元素地址赋值到了ptr引用。这里需要注意的是,这里指针需要和数组的元素类型保持一致,除非指针类型为void。
指针与结构体
就像数组一样,指向结构体的指针存储了结构体第一个元素的内存地址。与数组指针一样,结构体的指针必须声明和结构体类型保持一致,或者声明为void类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
struct person { int age; char *name; }; struct person first; struct person *ptr; first.age = 21; char *fullname = "full name" ; first.name = fullname; ptr = &first; printf ( "age=%d, name=%s\n" , first.age, ptr->name); |
第1至6行声明了一个person结构体,一个变量指向了一个person结构体和指向person结构体的指针。第8行为age成员赋了一个int值。第9至10行我们声明了一个char指针并赋值给一个char数组并赋值给结构体name成员。第11行我们把一个person结构体引用赋值给结构体变量。
第13行我们打印了结构体实例的age和name。这里需要注意两个不同的符号,’.’ 和 ‘->’ 。结构体实例可以通过使用 ‘.’ 符号访问age变量。对于结构体实例的指针,我们可以通过 ‘->’ 符号访问name变量。也可以同样通过(*ptr).name来访问name变量。
原文链接: Dennis Kubes 翻译: 伯乐在线 - 唐尤华
译文链接: http://blog.jobbole.com/25409/
C does not directly support pass by reference because it always uses pass by value, but a programmer can implement pass by reference by passing a pointer to the variable that the programmer wants passed by reference
在C语言中并没有按引用传递的用法(C++中利用&),但是可以利用指针间接实现同引用传递相同的目的
The program below
#include <stdio.h> void foo(int *x); int main(void) { int i = 5; printf("In main(): %d\n", i); foo(&i); printf("In main(): %d\n", i); return 0; } void foo(int *x) { printf("In foo(): %d\n", *x); *x = 10; printf("In foo(): %d\n", *x); }
prints
In main(): 5 In foo(): 5 In foo(): 10 In main(): 10
Stack PUSH & POP Implementation using Arrays(出栈入栈)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 /*STACK PUSH() AND POP() IMPLEMENTATION USING ARRAYS*/ 2 #include <stdio.h> 3 #include<conio.h> 4 #define MAX 5 5 int top, status; 6 7 /*PUSH FUNCTION*/ 8 void push (int stack[], int item) 9 { if (top == (MAX-1)) 10 status = 0; 11 else 12 { status = 1; 13 ++top; 14 stack [top] = item; 15 } 16 } 17 18 /*POP FUNCTION*/ 19 int pop (int stack[]) 20 { 21 int ret; 22 if (top == -1) 23 { ret = 0; 24 status = 0; 25 } 26 else 27 { status = 1; 28 ret = stack [top]; 29 --top; 30 } 31 return ret; 32 } 33 34 /*FUNCTION TO DISPLAY STACK*/ 35 void display (int stack[]) 36 { int i; 37 printf ("\nThe Stack is: "); 38 if (top == -1) 39 printf ("empty"); 40 else 41 { for (i=top; i>=0; --i) 42 printf ("\n--------\n|%3d |\n--------",stack[i]); 43 } 44 printf ("\n"); 45 } 46 47 /*MAIN PROGRAM*/ 48 void main() 49 { 50 int stack [MAX], item; 51 int ch; 52 clrscr (); 53 top = -1; 54 55 do 56 { do 57 { printf ("\NMAIN MENU"); 58 printf ("\n1.PUSH (Insert) in the Stack"); 59 printf ("\n2.POP (Delete) from the Stack"); 60 printf ("\n3.Exit (End the Execution)"); 61 printf ("\nEnter Your Choice: "); 62 scanf ("%d", &ch); 63 if (ch<1 || ch>3) 64 printf ("\nInvalid Choice, Please try again"); 65 }while (ch<1 || ch>3); 66 switch (ch) 67 {case 1: 68 printf ("\nEnter the Element to be pushed : "); 69 scanf ("%d", &item); 70 printf (" %d", item); 71 push (stack, item); 72 if (status) 73 { printf ("\nAfter Pushing "); 74 display (stack); 75 if (top == (MAX-1)) 76 printf ("\nThe Stack is Full"); 77 } 78 else 79 printf ("\nStack overflow on Push"); 80 break; 81 case 2: 82 item = pop (stack); 83 if (status) 84 { printf ("\nThe Popped item is %d. After Popping: "); 85 display (stack); 86 } 87 else 88 printf ("\nStack underflow on Pop"); 89 break; 90 default: 91 printf ("\nEND OF EXECUTION"); 92 } 93 }while (ch != 3); 94 getch(); 95 }
引自《C专家编程》
@对于宏这样的预处理器,只应该适量使用,故无需深入讨论。C++在这方面引入了一些新的方法,使得预处理器几乎无用武之地;宏最好用来命名常量
1.9 阅读ANSI C标准,寻找乐趣和裨益
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
|
左定值,右定向
1.ANSI C标准中的两句话
* 每个实参都应该具有自己的类型,这样它的值就可以赋值给与它所对应的形参类型的对象
(该对象类型不能含有限定符)也就是说参数传递过程类似于赋值
* 要使赋值形式合法,必须要满足下列条件之一: 两个操作数都是指向有限定符或无限定符的相容类型的指针,
左边指针所指向的类型必须具有右边指针所指向类型的全部限定符
正如上句话所言:使得函数调用中实参char*能够与形参const char*匹配,以下代码来说明:
char *cp; const char *ccp; ccp = cp;
左操作数是一个指向有const限定符的char的指针
右操作符是一个指向没有限定符的char的指针
char类型与char类型是相容的,左操作数所指向的类型具有右操作符所指向类型的限定符(无),再加上自身的限定符(const)
注意:反过来就不能进行复制: cp = ccp; 将产生编译警告
又有一个标准:const float* 类型并不是一个有限定符的类型--它的类型是"指向一个具有const限定符的float类型的指针",也就是说
const限定符是修饰指针所指向的类型,而不是指针本身
类似地,const char **也是一个没有限定符的指针类型,它的类型是"指向有const限定符的char类型的指针的指针"
2.const是一个C语言的关键字,它限定一个变量不允许被改变。
3.const int num 和 int const num 是一样的,const只修饰其后的变量,而在类型前后没有区别
4.char const *p 可以理解为char const (*p), 即*p是const的,但是p却是可变的
5 例子:
int const *p1, p2; --> (*p1)和p2 不可变,但p1却是可变的
int const *const p1, p2; --> p2是const,是前一个const修饰的,*p1也被前一个const修饰,
而p1被后一个const修饰
int *const p1, p2; --> p1是const,(*const p1)是整体,所以const不修饰p2。
6.const在*的左边,则指针指向的变量的值不可变;在*的右边,则指针的指向不可变。简记为“左定值,右定向”。
7.在c中,对于const定义的指针,不赋初值编译不报错
8.强烈建议在初始化时说明指针的指向,防止出现野指针!
实参char *s与形参const char *p相容,而为什么char **argv与形参const char **p不相容呢
1.首先分析下为什么char *s与形参const char *p相容
s指针指向char类型,p指针也指向char类型,只不过无法通过(*p)改变它的值
const char *p1 = char *p2
二者指向类型相同,左边指针拥有右边指针全部限定符,满足赋值条件
2.为什么char **argv与形参const char **p不相容
附上:容易混乱的const
[cpp] view plaincopyprint? 01.int main(int argc, char* argv[]) 02.{ 03. 04. //定义基本类型的const变量,const 位置在哪儿都可以 05. const int x = 2,y = 3; //两个常量 06. 07. //定义一个非const变量 08. int z = 3; //一个普通变量 09. 10. //定义一个指向常的指针 11. const int* pc = &x; //指向常量的指针 12. 13. //定义一个常指针 14. int* const cp = &z; //常指针 15. 16. //定义一个非const指针 17. int* pp = &z; //int 型指针 18. 19. pc = &z; //可以,pc是一个指向常量的指针,不能通过该指针去修改指针所指向的内存空间的值,但是,该指针可以指向别的变量 20. 21. // *pc = 10; //不可以,*pc所指向的地址为const变量,其值不可更改 pc是一个指向常量的指针,不能通过该指针去修改指针所指向的内存空间的值 22. 23. pc = pp; //可以,pc为指针,其值可被改变 pc是一个指向常量的指针,pp是一个普通指针,用一个普通指针给一个指向常量的指针赋值是可以的 24. // pp = pc; //用一个指向常量的指针,赋值给一个普通指针,不可以。如果可以,那么就可以通过普通的指针去修改内存的值 25. 26. *cp = x; //可以,通过常指针去修改指针所指向变量的值是可以的 27. 28. // cp = &x; //不可以,cp为常指针,指针的值不能被修改,给常指针赋值是错误的 29. 30. // pp = &x; //不可以,pp是非const指针,原则上来讲给它赋值是可以的,在不同的编译器下有不同的结果 31. // pp = pc; //不可以,指向常量的指针不能赋值给一个普通的指针 32. pp = cp; //可以,常指针可以赋值给一个普通指针 33. 34. const int * const pp = &a; //双const 既保护指针又保护内存变量 35. 36. return 0; 37.}
C语言专门定义了两个预处理语句:#ifdef 和 #ifndef,用来测试某个名字是否已经定义
#ifndef HDR
#define EDR
/*hdr.h文件的内容放到这里*/
#endif