今天拿着高质量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 的右边是谁,即修饰的是谁。
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型指针
|