c语言-几点经验

 今天拿着高质量c,c++编程指南 翻了翻,讲的还挺不错的,呵呵,很多该注意的地方,以前都没注意,c语言只能说熟悉,不能说精通,呵呵,把值得注意的地方总结下:

1)return语句不可返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。例如:

    char * Func(void)

    {

        char str[] = “hello world”; // str的内存位于栈上

        …

        return str;     // 将导致错误

    }

 

2)使用断言

         void  *memcpy(void *pvTo, const void *pvFrom, size_t size)

{

        assert((pvTo != NULL) && (pvFrom != NULL));     // 使用断言

        byte *pbTo = (byte *) pvTo;     // 防止改变pvTo的地址

        byte *pbFrom = (byte *) pvFrom; // 防止改变pvFrom的地址

        while(size -- > 0 )

            *pbTo ++ = *pbFrom ++ ;

        return pvTo;

}

 

3)引用和指针的比较

引用是C++中的概念,初学者容易把引用和指针混淆一起。一下程序中,n是m的一个引用(reference),m是被引用物(referent)。

    int m;

    int &n = m;

n相当于m的别名(绰号),对n的任何操作就是对m的操作。

 

4)传值

以下是“值传递”的示例程序。由于Func1函数体内的x是外部变量n的一份拷贝,改变x的值不会影响n, 所以n的值仍然是0。

    void Func1(int x)

{

    x = x + 10;

}

int n = 0;

    Func1(n);

    cout << “n = ” << n << endl;  // n = 0

 

5)传地址

以下是“指针传递”的示例程序。由于Func2函数体内的x是指向外部变量n的指针,改变该指针的内容将导致n的值改变,所以n的值成为10。

    void Func2(int *x)

{

    (* x) = (* x) + 10;

}

int n = 0;

    Func2(&n);

    cout << “n = ” << n << endl;      // n = 10

 

6)引用传递

    以下是“引用传递”的示例程序。由于Func3函数体内的x是外部变量n的引用,x和n是同一个东西,改变x等于改变n,所以n的值成为10。

    void Func3(int &x)

{

    x = x + 10;

}

int n = 0;

    Func3(n);

    cout << “n = ” << n << endl;      // n = 10

 

    对比上述三个示例程序,会发现“引用传递”的性质象“指针传递”,而书写方式象“值传递”。

 

这时引用就是一个别名;

 

7)指针和数组

char a[] = “hello”;

a[0] = ‘X’;

cout << a << endl;

char *p = “world”;     // 注意p指向常量字符串

p[0] = ‘X’;             // 编译器不能发现该错误

cout << p << endl;

 

8)指针参数如何传递内存

如果函数的参数是一个指针,不要指望用该指针去申请动态内存,在下面的例子中,Test函数的语句GetMemory(str, 200)并没有使str获得期望的内存,str依旧是NULL,为什么?

void GetMemory(char *p, int num)

{

    p = (char *)malloc(sizeof(char) * num);

}

void Test(void)

{

    char *str = NULL;

    GetMemory(str, 100);    // str 仍然为 NULL 

    strcpy(str, "hello");   // 运行错误

}

 

原因分析:

char *str =NULL;  的含义是定义一个char型的指针str ,这个指针指向的变量值为NULL,那么GetMemory(str,100)调用时,形参会copy实参一份东东,比如str的地址是1000,而此时形参的地址是1004,而str指向的变量的地址为800,那么1004的地址为str的copy,他们都指向800;这时在函数GetMemory里面 形参p的地址为1004,然后在里面分配了一块内存,假设分配的内存的地址为2004,这时p就指向2004了。即此时实参还是指向800,而p指向了2004,函数返回时,当然str指向的还是800,此时800里面的值为NULL,所以strcpy(str,"hello")就会出错了。

每执行一次GetMemory就会泄露一块内存,因为没有用free释放内存。

 

这里应该注意的地方就是指针作为参数只能改变实参的值,而上面的例子是讲形参的地址改变了,而实参的值没有变,所以是错误的。

 

9)指向指针的指针

针对8)的问题,可以采用指向指针的指针解决;

 

void GetMemory2(char **p, int num)

{

    *p = (char *)malloc(sizeof(char) * num);

}

void Test2(void)

{

    char *str = NULL;

    GetMemory2(&str, 100);  // 注意参数是 &str,而不是str

    strcpy(str, "hello");  

    cout<< str << endl;

    free(str); 

}

 

此时:解释如下:

    char *str = NULL; 和8)一样

GetMemory2(&str, 100);传递的是&str,即str的地址,在上面分析了假设str的地址为1000,那么传递的就是1000;此时,str指向的变量为NULL,即没分配地址,而形参为char **p;  当实参传递给形参时,将p=&str,即将1000传给了p,即指针变量p的值为1000,这时在函数里面使用*p来分配内存,相当于1000地址所指向的变量的内容为malloc分配的地址,这样就使得str指向的变量也为malloc的地址,从而输出正确的值。

 

10)用函数返回值来传递动态内存

char *GetMemory3(int num)

{

    char *p = (char *)malloc(sizeof(char) * num);

    return p;

}

void Test3(void)

{

    char *str = NULL;

    str = GetMemory3(100); 

    strcpy(str, "hello");

    cout<< str << endl;

    free(str); 

}

看到这里,有些同学可能会问,返回指针的值,那么p是局部变量,用完就不存在了,不会有什么问题吗?

 

这里大家要注意,p指向的内存是在堆上分配的,而不是在栈分配的,malloc分配的内存一直有效,而p作为局部变量,函数外就不存在了,但是此时它已经将内存传递给str了。str = GetMemory3(100);  所以是正确的。这里注意和例子1)区分开来;

 

11)return语句返回指向“栈内存”的指针 是错误的

char *GetString(void)

{

    char p[] = "hello world";

    return p;   // 编译器将提出警告

}

void Test4(void)

{

char *str = NULL;

str = GetString();  // str 的内容是垃圾

cout<< str << endl;

}

因为p是局部变量,作用域在函数内,char p[] = "hello world"; 分配的内存在栈上,它只有在该函数内有效,所以返回时,内存就无效了。因此上例是错误的。

 

12)return语句返回常量字符串 不合常理

char *GetString2(void)

{

    char *p = "hello world";

    return p;

}

void Test5(void)

{

    char *str = NULL;

    str = GetString2();

    cout<< str << endl;

}

虽然能正确输出结果,

GetString2内的“hello world”是常量字符串,位于静态存储区,它在程序生命期内恒定不变。无论什么时候调用GetString2,它返回的始终是同一个“只读”的内存块。

 

13)指针的算术运算

指针可以加上或减去一个整数。指针的这种运算的意义和通常的数值的加减运算的意义是不一样的。例如:  
  
  1、char a[20];  
  2、int* ptr=a;  
  ...  
 ...  
  3、ptr++;  
  在上例中,指针ptr的类型是int*,它指向的类型是int,它被初始化为指向整形变量a。接下来的第3句中,指针ptr被加了1,编译器是这样处理的:它把指针ptr的值加上了sizeof(int),在32位程序中,是被加上了4。由于地址是用字节做单位的,故ptr所指向的地址由原来的变量a的地址向高地址方向增加了4个字节。  
由于char类型的长度是一个字节,所以,原来ptr是指向数组a的第0号单元开始的四个字节,此时指向了数组a中从第4号单元开始的四个字节。  

 

 1、char a[20];  
  2、int* ptr=a;  
  ...  
  ...  
  3、ptr+=5;
  在这个例子中,ptr被加上了5,编译器是这样处理的:将指针ptr的值加上5乘sizeof(int),在32位程序中就是加上了5乘4=20。由于地址的单位是字节,故现在的ptr所指向的地址比起加5后的ptr所指向的地址来说,向高地址方向移动了20个字节。在这个例子中,没加5前的ptr指向数组a的第0号单元开始的四个字节,加5后,ptr已经指向了数组a的合法范围之外了。虽然这种情况在应用上会出问题,但在语法上却是可以的。这也体现出了指针的灵活性。

14)指针的强制类型转换

1、floatf=12.3;  
  2、float*fptr=&f;  
  3、int*p;  
   在上面的例子中,假如我们想让指针p指向实数f,应该怎么搞?是用下面的语句吗?  

  p=&f;  

  不对。因为指针p的类型是int*,它指向的类型是int。表达式&f的结果是一个指针,指针的类型是float*,它指向的类型是float。两者不一致,直接赋值的方法是不行的。至少在我的MSVC++6.0上,对指针的赋值语句要求赋值号两边的类型一致,所指向的类型也一致,其它的编译器上我没试过,大家可以试试。为了实现我们的目的,需要进行 "强制类型转换 ":  
p=(int*)&f;
如果有一个指针p,我们需要把它的类型和所指向的类型改为TYEP*TYPE,   那么语法格式是:  
  (TYPE*)p;  
  这样强制类型转换的结果是一个新指针,该新指针的类型是TYPE*,它指向的类型是TYPE,它指向的地址就是原指针指向的地址。而原来的指针p的一切属性都没有被修改。  

函数中指针做形参,强制类型转换例子。

voidfun(char*);  
inta=125,b;  
fun((char*)&a);  
...  
...  
voidfun(char*s)  
{  
charc;  
c=*(s+3);*(s+3)=*(s+0);*(s+0)=c;  
c=*(s+2);*(s+2)=*(s+1);*(s+1)=c;  
}  
}  
注意这是一个32位程序,故int类型占了四个字节,char类型占一个字节。函数fun的作用是把一个整数的四个字节的顺序来个颠倒。注意到了吗?在函数调用语句中,实参&a的结果是一个指针,它的类型是int*,它指向的类型是int。形参这个指针的类型是char*,它指向的类型是char。这样,在实参和形参的结合过程中,我们必须进行一次从int*类型到char*类型的转换。结合这个例子,我们可以这样来想象编译器进行转换的过程:编译器先构造一个临时指针char*temp,   然后执行temp=(char*)&a,最后再把temp的值传递给s。所以最后的结果是:s的类型是char*,它指向的类型是char,它指向的地址就是a的首地址。  

15)整数可以赋值给指针吗?

我们已经知道,指针的值就是指针指向的地址,在32位程序中,指针的值其实是一个32位整数。那可不可以把一个整数当作指针的值直接赋给指针呢?就象下面的语句:
unsignedinta;  
TYPE*ptr;//TYPE是int,char或结构类型等等类型。  
...  
...  
a=20345686;  
ptr=20345686;//我们的目的是要使指针ptr指向地址20345686(十进制  
)  
ptr=a;//我们的目的是要使指针ptr指向地址20345686(十进制)
编译一下吧。结果发现后面两条语句全是错的。那么我们的目的就不能达到了吗?不,还有办法:  
unsignedinta;  
TYPE*ptr;//TYPE是int,char或结构类型等等类型。  
...  
...  
a=某个数,这个数必须代表一个合法的地址;  
ptr=(TYPE*)a;//呵呵,这就可以了。
严格说来这里的(TYPE*)和指针类型转换中的(TYPE*)还不一样。这里的(TYPE*)的意思是把无符号整数a的值当作一个地址来看待。上面强调了a的值必须代表一个合法的地址,否则的话,在你使用ptr的时候,就会出现非法操作错误。  

  想想能不能反过来,把指针指向的地址即指针的值当作一个整数取出来。完   全可以。下面的例子演示了把一个指针的值当作一个整数取出来,然后再把这个整数当作一个地址赋给一个指针:  
  例十六:  
inta=123,b;  
int*ptr=&a;  
char*str;  
b=(int)ptr;//把指针ptr的值当作一个整数取出来。  
str=(char*)b;//把这个整数的值当作一个地址赋给指针str。
  现在我们已经知道了,可以把指针的值当作一个整数取出来,也可以把一个整数值当作地址赋给一个指针。  
  指针的安全问题  
看下面的例子:  
  例十七:
chars= 'a ';  
int*ptr;  
ptr=(int*)&s;  
*ptr=1298;  
  指针ptr是一个int*类型的指针,它指向的类型是int。它指向的地址就是s的首地址。在32位程序中,s占一个字节,int类型占四个字节。最后一条语句不但改变了s所占的一个字节,还把和s相临的高地址方向的三个字节也改变了。这三个字节是干什么的?只有编译程序知道,而写程序的人是不太可能知道的。也许这三个字节里存储了非常重要的数据,也许这三个字节里正好是程序的一条代码,而由于你对指针的马虎应用,这三个字节的值被改变了!这会造成崩溃性的错误。  
  让我们再来看一例:  
  例十八:  
  1、chara;  
  2、int*ptr=&a;  
  ...  
  ...  
  3、ptr++;  
  4、*ptr=115;  
  该例子完全可以通过编译,并能执行。但是看到没有?第3句对指针ptr进行自加1运算后,ptr指向了和整形变量a相邻的高地址方向的一块存储区。这块存储区里是什么?我们不知道。有可能它是一个非常重要的数据,甚至可能是一条代码。而第4句竟然往这片存储区里写入一个数据!这是严重的错误。所以在使用指针时,程序员心里必须非常清楚:我的指针究竟指向了哪里。在用指针访问数组的时候,也要注意不要超出数组的低端和高端界限,否则也会造成类似的错误。  
  在指针的强制类型转换:ptr1=(TYPE*)ptr2中,如果sizeof(ptr2的类型)大于sizeof(ptr1的类型),那么在使用指针ptr1来访问ptr2所指向的存储区时是安全的。如果sizeof(ptr2的类型)小于sizeof(ptr1的类型),那么在使用指针ptr1来访问ptr2所指向的存储区时是不安全的。至于为什么,读者结合例十七来想一想,应该会明白的。
 
16)常量指针与指针常量
在C++的学习中,有人经常搞不清楚“常量指针”和“指针常量”这两个概念。其实简单一点讲,“常量指针”所指向的地址上的数据是常量,而“指针常量”所指向的地址是常量,地址上面的数据是可以变化的。


     下面看及格简单的例子,可以说明他们的区别:
            第一个
 1  void  main(){
 2       char   * str1 = { " Hello " };
 3       char   * str2 = { " Hello World " };
 4       char   *   const  ptr1  = str1 ;
 5       // 指针常量--指针本身是常量,指向的地址不可以变化,但是指向的地址所对应的内容可以变化
 6 
 7      ptr1  = str2;  // 错误 因为这是一个常量指针,改变指向的地址了
 8 
 9      printf( " %s \n " , * ptr1);
10      }
11 
12 
13  //  编译错误    error C3892: 'ptr1' : you cannot assign to a variable that is const    
14 


</>第二个

 1  void  main(){
 2       char   * str1 = { " Hello " };
 3       char   * str2 = { " Hello World " };
 4       char   *   const  ptr1  = str1 ;
 5       // 指针常量--指针本身是常量,指向的地址不可以变化,但是指向的地址所对应的内容可以变化
 6 
 7       * ptr1  = ' A ' ; //  正确 因为指向的地址的内容是可以变化的
 8 
 9      printf( " %c \n " , * ptr1);
10      }
11 
12  // 输出  A
13 

第三个

 1  void  main(){
 2       char   * str1 = { " Hello " };
 3       char   * str2 = { " Hello World " };
 4       const   char   * ptr1  =  str1;
 5       // 常量指针--指向字符串常量,所指向的字符串内容不能变,但是指向的地址可以变化
 6      
 7      ptr1 = str2; //  正确 因为指向的地址是可以变化的
 8 
 9      printf( " %s \n " ,ptr1);
10      }
11 
12  // 输出 Hello World

第四个

 1  void  main(){
 2       char   * str1 = { " Hello " };
 3       char   * str2 = { " Hello World " };
 4       const   char   * ptr1  =  str2;
 5       // 常量指针--指向字符串常量,所指向的字符串内容不能变,但是指向的地址可以变化
 6      
 7      ptr1 = ' A ' ; //  错误 因为指向的地址是内容是不可以变化的
 8 
 9      printf( " %c \n " ,ptr1);
10      }
11 
12 
13  // 编译错误    error C2440: '=' : cannot convert from 'char' to 'const char *'
14 

相信从上面四个简单的例子可以看出他们不一样的地方把,在这里要请大家注意一下的地方是:

指针常量的申明:const 放在* 和指针名之间 Type * const pointer ;

常量指针的申明:const放在类型说明符之前 const Type *pointer ;

 
也可以通过const 的右边是谁,即修饰的是谁。
const右边是指针,那么首先这是一个指针,所以是常量指针。如char const *pointer;或者const char *pointer;这个指针指向一个常量,所以它指向的地址上的值是不能变的,当然它的指向是可以变的,即这个指针的值是可以变的,但所指向的内容是不能变的。
const右边是变量,那么这是一个常量,就是指针常量。如char *const pointer;常量不可变,这是一个指针性质的常量,指针实际上就是一个地址值,由于它是常量,所以这个地址值不能变,即指针常量所指的位置不能变,但所指的位置上的值是可以变的。 
 
 
17)指针的数据类型
int  i;    //定义整型变量i
int  *p; //p为指向整型数据的指针变量
int  a[n];  //定义含n个元素的整型数组a
int  *p[n];  //n个指向整型数据的指针变量组成的指针数组p
int  (*p)[n];  //p为指向含n个元素的一维整型数组的指针变量
int f();        //f为返回整型数的函数
int *p();     //p为返回指针的函数,该指针指向一个整型数据
int (*p)();  //p为指向函数的指针变量,该函数返回整型数
int **p;     //p为指针变量,它指向一个指向整型数据的指针变量
 
1)int  *p[3];     
指针数组
(2)int   (*p)[3];  //
指向一维数组的指针
(3)int  *p(int);
返回指针的函数
(4)int  (*p)(int); //
指向函数的指针,函数返回int型变量
(5)int  *(*p)(int);
指向函数的指针,函数返回int 型指针
 
(6)int (*p[3])(int);
函数指针数组,函数返回int型变量
(7)int  *(*p[3])(int);
函数指针数组,函数返回int型指针
 
 
 
 
 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值