C++学习笔记之八 复合类型---指针、数组和指针运算
2016-05-03 15:30
736 查看
指针和数组基本等价的原因在于指针算术和C++内部处理数组的方式。
还是通过一个程序例子看一下:
#include <iostream>
int main()
{
using namespace std;
double wages[3] = { 10000.0, 20000.0, 30000.0 };
short stacks[3] = {3, 2, 1};
//下面是两种获得数组地址的方式
double* pw = wages; //数组名就等于数组的地址
short* ps = &stacks[0]; //或者数组第一个元素的地址就是数组的地址
cout<<"pw = " << pw <<" , *pw = "<<*pw <<endl;
pw = pw+1;
cout << "add 1 to the pw pointer :\n";
cout << "pw = "<<pw << " , *pw = " <<*pw <<" \n\n";
cout << "ps = "<<ps << " , *ps = " <<*ps << endl;
ps = ps+1;
cout << "add 1 to the ps pointer :\n";
cout << "ps = "<<ps << " , *ps = " <<*ps <<" \n\n";
cout<<"access
4000
two elements with array notation\n";
cout<<"stacks[0] = " << stacks[0]
<<" , stacks[1] = " <<stacks[1] << endl;
cout<<"access two elements with pointer notation\n";
cout<<"*stacks = " << *stacks
<<" , *(stacks+1) = " <<*(stacks+1) << endl;
cout<<sizeof(wages) << " =size of wages array\n";
cout<<sizeof(pw) << " =size of pw pointer\n"
return 0;
}
下面是该程序的输出:
pw = 0x28ccf0, *pw = 10000
add 1 to the pw pointer:
pw = 0x28ccf8, *pw = 20000
ps = 0x28ccea, *ps = 3
add 1 to the ps pointer:
ps = 0x28ccrc, *ps = 2
access two elements with array notation
stacks[0] = 3, stacks[1] = 2
access two elements with pointer notation
*stacks =3, *(stacks +1) =2
24 = size of wages array
4 = size of pw pointer
两个数组的存储情况如下图所示:
注意:将指针变量加1后,其增加的值等于指向的类型占用的字节数。
从该程序的输出可知,*(stacks+1)和stacks[1]是等价的。同样,*(stacks + 2)和stacks[2]也是等价的。通常使用数组表示法时,C++都执行下面的转换:arrayname[i] becomes *(arrayname + i)
如果使用的指针,而不是数组名,则C++也将执行同样的转换:
pointername[i] becomes *(pointername + i)
因此,在很多情况下,可以相同的方式使用指针名和数组名。对于它们,可以使用数组方括号表示法,也可以使用解除引用运算符(*)。在多数表达式中,它们都表示地址。区别之一是,可以修改指针的值,而数组名是常量。
另一个区别是,对数组应用sizeof运算符得到的是数组的长度,而对指针应用sizeof运算符得到的是指针的长度,即使指针指向的是同一个数组。
还有一个地方需要提一下:数组名被解释为第一个元素的地址,而对数组名应用地址运算符时,得到的是整个数组的地址:
short tell[10];
cout << tell <<endl; //显示的是&tell[0]
cout<<&tell <<endl; //显示的是整个数组的地址
从数字上说,这两个地址相同;但从概念上说,&tell[0]是一个2字节内存块的地址,而&tell是一个20字节内存块的地址。
char flower[10] = "rose";
cout << flower << "s are red\n";
这个语句我们以前就经常会用到,我们知道这个句子的执行结果将会输出roses are red 。但是这其中的原理我们并不清楚。其实,flower是数组名,我们知道数组名表示的是数组第一个元素的地址,那么cout是不是就输出的第一个元素地址里的数据呢?显然不是,否则我们也不会得到rose的完整输出了。
其实是这样的,cout对象认为char的地址是字符串的地址,因此它打印该地址处的字符,然后继续打印后面的字符,直到遇到空字符(\0)为止。总之,如果给cout提供一个字符的地址,则它将从该字符开始打印,直到遇到空字符为止。
所以,我们需要注意,在cout和多数C++表达式中,char数组名、char指针以及用引号括起来的字符串常量都被解释为字符串第一个字符的地址。那么在用cout输出时,自然的输出的都是完整的字符串直到遇见空字符。
下面用一个程序来演示一下如何使用不同形式的字符串:
#include <iostream>
#include <cstring>
int main()
{
using namespace std;
char animal[20] = "bear";
const char* bird = "wren";
char* ps;
cout << animal << " and ";
cout << bird << " \n";
cout << " Enter a kind of animal : ";
cin >> animal;
ps = animal;
cout << ps << "!\n";
cout << " Before using strcpy() : \n";
cout << animal << " at " << (int *)animal << endl;
cout << ps <<" at " << (int *) ps << endl;
ps = new char [strlen(animal) + 1];
strcpy(ps, animal);
cout << " After using strcpy() : \n";
cout << animal << " at " << (int *)animal << endl;
cout << ps << " at "<<(int* )ps << endl;
delete [] ps;
return 0;
}
执行结果:
bear and wren
Enter a kind of animal : fox
fox!
Before using strcpy() :
fox at 0x0065fd30
fox at 0x0065fd30
After using strcpy() :
fox at 0x0065fd30
fox at 0x004301c8
程序需要说明的几个地方:
(1)const char* bird = "wren"; 记住,常量字符串“wren”实际表示的是字符串的地址,这条语句将“wren”的地址赋给了bird指针。这意味着可以像使用字符串“wren”那样使用指针bird。
(2)对于输入,可以使用animal进行输入(要保证输入长度不超过数组长度),然而,使用bird进行输入就不合适了(bird是const指针)。
(3)注意代码:
cout << animal << " at " << (int *)animal << endl;
cout << ps <<" at " << (int *) ps << endl;
很多朋友会有疑问了,一般来说,如果给cout提供一个指针,它将打印地址。但是如果指针的类型为char*,则cout将显示指向的字符串。如果要显示的是字符串的地址,则必须将这种指针强制转换为另一种指针类型,如int*.因此,ps显示为字符串“fox”,而(int*)ps显示为该字符串的地址。
将new用于结构由两步组成:创建结构和访问其成员。要创建结构,需要同时使用结构类型和new。例如,
inflatable* ps = new inflatable;
这将把足以存储inflatable结构的一块可用内存的地址赋给ps。
下一步是访问成员。大家可能注意到了,用new创建的结构没有结构名,只是知道它的地址。C++专门为这种情况提供了一个运算符:箭头成员运算符(->)。例如,如果ps指向一个inflatable结构,则ps->price是被指向的结构的price成员。
两种访问成员的方式如下图说明:
除了这两种访问成员变量的方式,还有一种访问结构成员的方法是,如果ps是指向结构的指针,则*ps就是被指向的值---结构本身。由于*ps是一个结构,因此(*ps).price是该结构的price成员。注意C++的运算符优先规则要求使用括号。
用一个例子练习一下访问成员:
#include <iostream>
struct inflatable
{
char name[20];
float volume;
double price;
};
int main()
{
using namespace std;
inflatable* ps = new inflatable;
cout << "Enter name of inflatable item: "
cin.get(ps->name, 20);
cout << "Enter volume in cubic feet: ";
cin >> (*ps).volume;
cout << "Enter peice: $";
cin >> ps->price;
cout << "Name: " << (*ps).name << endl;
cout << " Volume: " << ps->volume << " cubic feet\n";
cout << "Price : $" << ps->price <<endl;
delete ps;
return 0;
}
下面是该程序的运行情况:
Enter name of inflatable item: Fabulous Frodo
Enter volume in cubic feet: 1.4
Enter price: $27.99
Name: Fabulous Frodo
Volume: 1.4 cubic feet
Price: $27.99
另一个使用new和delete的示例:
#include <iostream>
#include <cstring>
using namespace std;
char* getname(void);
int main()
{
char* name;
name = getname();
cout<<name << " at " << (int*) name << "\n";
delete [] name; //这个地方注意,这里释放的内存是在getname函数中创建的
name = getname();
cout<<name << " at " << (int*) name << "\n";
delete [] name;
return 0;
}
char* getname()
{
char temp[80];
cout <<"Enter last name: ";
cin >> temp;
char* pn = new char [strlen(temp +1)];
strcpy(pn , temp);
return pn;
}
下面是该程序运行的情况:
Enter last name: Fredeldumpkin
Fredeldumpkin at 0x004326b8
Enter last name: Pook
Pook at 0x00430lc8
(1)自动存储
在函数内部定义的常规变量使用自动存储空间,被称为自动变量,这意味着它们在所属的函数被调用时自动产生,在该函数结束时消亡。
实际上,自动变量是一个局部变量,其作用域为包含它的代码块。代码块是被包含在花括号中的一段代码。到目前为止,我们使用的代码块都是整个函数。然而,下一章将会看到,函数内也可以有代码块。如果在其中的某个代码块定义了一个变量,则该变量仅在程序执行改代码块中的代码时存在。
自动变量通常存储在栈中。这意味着执行代码时,其中的变量将依次加入到栈中,而在离开代码块时,将按相反的顺序释放这些变量,这被称为后进先出。因此,在程序执行过程中,栈将不断地增大和缩小。
(2)静态存储
静态存储是整个程序执行期间都存在的存储方式。使变量称为静态的方式有两种:一种是在函数外面定义它(也就是全局变量),另一种是在声明变量时使用关键字static:
static double fee = 56.50;
(3)动态存储
new和delete运算符提供了一种比自动变量和静态变量更灵活的方法。它们管理了一个内存池,这在C++中被称为自由存储空间或堆。该内存池跟用于静态变量和自动变量的内存是分开的。new和delete让您能够在一个函数中分配内存,而在另一个函数中释放它。因此,数据的生命周期不完全受程序或函数的生存时间控制。与使用常规变量相比,使用new和delete让程序员对程序如何使用内存有更大的控制权。然而,内存管理也更复杂。
1、指针运算
将整数变量加1后,其值将增加1;但将指针变量加1后,增加的量等于它指向的类型的字节数。比如,将指向double的指针加1后,如果系统对double使用8个字节存储,则数值将增加8;将指向short的指针加1后,如果系统对short使用2个字节存储,则指针值将增加2。还是通过一个程序例子看一下:
#include <iostream>
int main()
{
using namespace std;
double wages[3] = { 10000.0, 20000.0, 30000.0 };
short stacks[3] = {3, 2, 1};
//下面是两种获得数组地址的方式
double* pw = wages; //数组名就等于数组的地址
short* ps = &stacks[0]; //或者数组第一个元素的地址就是数组的地址
cout<<"pw = " << pw <<" , *pw = "<<*pw <<endl;
pw = pw+1;
cout << "add 1 to the pw pointer :\n";
cout << "pw = "<<pw << " , *pw = " <<*pw <<" \n\n";
cout << "ps = "<<ps << " , *ps = " <<*ps << endl;
ps = ps+1;
cout << "add 1 to the ps pointer :\n";
cout << "ps = "<<ps << " , *ps = " <<*ps <<" \n\n";
cout<<"access
4000
two elements with array notation\n";
cout<<"stacks[0] = " << stacks[0]
<<" , stacks[1] = " <<stacks[1] << endl;
cout<<"access two elements with pointer notation\n";
cout<<"*stacks = " << *stacks
<<" , *(stacks+1) = " <<*(stacks+1) << endl;
cout<<sizeof(wages) << " =size of wages array\n";
cout<<sizeof(pw) << " =size of pw pointer\n"
return 0;
}
下面是该程序的输出:
pw = 0x28ccf0, *pw = 10000
add 1 to the pw pointer:
pw = 0x28ccf8, *pw = 20000
ps = 0x28ccea, *ps = 3
add 1 to the ps pointer:
ps = 0x28ccrc, *ps = 2
access two elements with array notation
stacks[0] = 3, stacks[1] = 2
access two elements with pointer notation
*stacks =3, *(stacks +1) =2
24 = size of wages array
4 = size of pw pointer
两个数组的存储情况如下图所示:
注意:将指针变量加1后,其增加的值等于指向的类型占用的字节数。
从该程序的输出可知,*(stacks+1)和stacks[1]是等价的。同样,*(stacks + 2)和stacks[2]也是等价的。通常使用数组表示法时,C++都执行下面的转换:arrayname[i] becomes *(arrayname + i)
如果使用的指针,而不是数组名,则C++也将执行同样的转换:
pointername[i] becomes *(pointername + i)
因此,在很多情况下,可以相同的方式使用指针名和数组名。对于它们,可以使用数组方括号表示法,也可以使用解除引用运算符(*)。在多数表达式中,它们都表示地址。区别之一是,可以修改指针的值,而数组名是常量。
另一个区别是,对数组应用sizeof运算符得到的是数组的长度,而对指针应用sizeof运算符得到的是指针的长度,即使指针指向的是同一个数组。
还有一个地方需要提一下:数组名被解释为第一个元素的地址,而对数组名应用地址运算符时,得到的是整个数组的地址:
short tell[10];
cout << tell <<endl; //显示的是&tell[0]
cout<<&tell <<endl; //显示的是整个数组的地址
从数字上说,这两个地址相同;但从概念上说,&tell[0]是一个2字节内存块的地址,而&tell是一个20字节内存块的地址。
2、指针和字符串
数组和指针的关系可以扩展到C-风格字符串。比如下面这个语句:char flower[10] = "rose";
cout << flower << "s are red\n";
这个语句我们以前就经常会用到,我们知道这个句子的执行结果将会输出roses are red 。但是这其中的原理我们并不清楚。其实,flower是数组名,我们知道数组名表示的是数组第一个元素的地址,那么cout是不是就输出的第一个元素地址里的数据呢?显然不是,否则我们也不会得到rose的完整输出了。
其实是这样的,cout对象认为char的地址是字符串的地址,因此它打印该地址处的字符,然后继续打印后面的字符,直到遇到空字符(\0)为止。总之,如果给cout提供一个字符的地址,则它将从该字符开始打印,直到遇到空字符为止。
所以,我们需要注意,在cout和多数C++表达式中,char数组名、char指针以及用引号括起来的字符串常量都被解释为字符串第一个字符的地址。那么在用cout输出时,自然的输出的都是完整的字符串直到遇见空字符。
下面用一个程序来演示一下如何使用不同形式的字符串:
#include <iostream>
#include <cstring>
int main()
{
using namespace std;
char animal[20] = "bear";
const char* bird = "wren";
char* ps;
cout << animal << " and ";
cout << bird << " \n";
cout << " Enter a kind of animal : ";
cin >> animal;
ps = animal;
cout << ps << "!\n";
cout << " Before using strcpy() : \n";
cout << animal << " at " << (int *)animal << endl;
cout << ps <<" at " << (int *) ps << endl;
ps = new char [strlen(animal) + 1];
strcpy(ps, animal);
cout << " After using strcpy() : \n";
cout << animal << " at " << (int *)animal << endl;
cout << ps << " at "<<(int* )ps << endl;
delete [] ps;
return 0;
}
执行结果:
bear and wren
Enter a kind of animal : fox
fox!
Before using strcpy() :
fox at 0x0065fd30
fox at 0x0065fd30
After using strcpy() :
fox at 0x0065fd30
fox at 0x004301c8
程序需要说明的几个地方:
(1)const char* bird = "wren"; 记住,常量字符串“wren”实际表示的是字符串的地址,这条语句将“wren”的地址赋给了bird指针。这意味着可以像使用字符串“wren”那样使用指针bird。
(2)对于输入,可以使用animal进行输入(要保证输入长度不超过数组长度),然而,使用bird进行输入就不合适了(bird是const指针)。
(3)注意代码:
cout << animal << " at " << (int *)animal << endl;
cout << ps <<" at " << (int *) ps << endl;
很多朋友会有疑问了,一般来说,如果给cout提供一个指针,它将打印地址。但是如果指针的类型为char*,则cout将显示指向的字符串。如果要显示的是字符串的地址,则必须将这种指针强制转换为另一种指针类型,如int*.因此,ps显示为字符串“fox”,而(int*)ps显示为该字符串的地址。
3、使用new创建动态结构
在运行时创建数组优于在编译时创建数组,对于结构也是如此。需要在程序运行时为结构分配所需的空间,这也可以使用new运算符来完成。通过使用new,可以创建动态结构。由于类与结构非常相似,因此本节介绍的有关结构的技术也适用于类。将new用于结构由两步组成:创建结构和访问其成员。要创建结构,需要同时使用结构类型和new。例如,
inflatable* ps = new inflatable;
这将把足以存储inflatable结构的一块可用内存的地址赋给ps。
下一步是访问成员。大家可能注意到了,用new创建的结构没有结构名,只是知道它的地址。C++专门为这种情况提供了一个运算符:箭头成员运算符(->)。例如,如果ps指向一个inflatable结构,则ps->price是被指向的结构的price成员。
两种访问成员的方式如下图说明:
除了这两种访问成员变量的方式,还有一种访问结构成员的方法是,如果ps是指向结构的指针,则*ps就是被指向的值---结构本身。由于*ps是一个结构,因此(*ps).price是该结构的price成员。注意C++的运算符优先规则要求使用括号。
用一个例子练习一下访问成员:
#include <iostream>
struct inflatable
{
char name[20];
float volume;
double price;
};
int main()
{
using namespace std;
inflatable* ps = new inflatable;
cout << "Enter name of inflatable item: "
cin.get(ps->name, 20);
cout << "Enter volume in cubic feet: ";
cin >> (*ps).volume;
cout << "Enter peice: $";
cin >> ps->price;
cout << "Name: " << (*ps).name << endl;
cout << " Volume: " << ps->volume << " cubic feet\n";
cout << "Price : $" << ps->price <<endl;
delete ps;
return 0;
}
下面是该程序的运行情况:
Enter name of inflatable item: Fabulous Frodo
Enter volume in cubic feet: 1.4
Enter price: $27.99
Name: Fabulous Frodo
Volume: 1.4 cubic feet
Price: $27.99
另一个使用new和delete的示例:
#include <iostream>
#include <cstring>
using namespace std;
char* getname(void);
int main()
{
char* name;
name = getname();
cout<<name << " at " << (int*) name << "\n";
delete [] name; //这个地方注意,这里释放的内存是在getname函数中创建的
name = getname();
cout<<name << " at " << (int*) name << "\n";
delete [] name;
return 0;
}
char* getname()
{
char temp[80];
cout <<"Enter last name: ";
cin >> temp;
char* pn = new char [strlen(temp +1)];
strcpy(pn , temp);
return pn;
}
下面是该程序运行的情况:
Enter last name: Fredeldumpkin
Fredeldumpkin at 0x004326b8
Enter last name: Pook
Pook at 0x00430lc8
4、自动存储、静态存储和动态存储
根据分配内存的方法,C++有3种管理数据内存的方式:自动存储、静态存储和动态存储(有时也叫做自由存储空间或堆)。(1)自动存储
在函数内部定义的常规变量使用自动存储空间,被称为自动变量,这意味着它们在所属的函数被调用时自动产生,在该函数结束时消亡。
实际上,自动变量是一个局部变量,其作用域为包含它的代码块。代码块是被包含在花括号中的一段代码。到目前为止,我们使用的代码块都是整个函数。然而,下一章将会看到,函数内也可以有代码块。如果在其中的某个代码块定义了一个变量,则该变量仅在程序执行改代码块中的代码时存在。
自动变量通常存储在栈中。这意味着执行代码时,其中的变量将依次加入到栈中,而在离开代码块时,将按相反的顺序释放这些变量,这被称为后进先出。因此,在程序执行过程中,栈将不断地增大和缩小。
(2)静态存储
静态存储是整个程序执行期间都存在的存储方式。使变量称为静态的方式有两种:一种是在函数外面定义它(也就是全局变量),另一种是在声明变量时使用关键字static:
static double fee = 56.50;
(3)动态存储
new和delete运算符提供了一种比自动变量和静态变量更灵活的方法。它们管理了一个内存池,这在C++中被称为自由存储空间或堆。该内存池跟用于静态变量和自动变量的内存是分开的。new和delete让您能够在一个函数中分配内存,而在另一个函数中释放它。因此,数据的生命周期不完全受程序或函数的生存时间控制。与使用常规变量相比,使用new和delete让程序员对程序如何使用内存有更大的控制权。然而,内存管理也更复杂。
相关文章推荐
- 【C/C++】自增运算符++详解
- C语言小结
- C++书籍清单
- C++中的static用法小结
- C++ Primer 学习笔记
- C语言中的IO函数小计
- C/C++混编之 extern "C"
- c++中斜杠和反斜杠的作用 相对路径和绝对路径
- C++;每周一些题(1)
- leecode 242---Valid Anagram
- C++小结
- C++中指向子类的父类指针访问父、子类成员时的权限和函数隐藏规则
- C++杂七杂八小知识
- nyoj10 skiing
- 复化simpson求积算法 c++实现
- C++11 多线程编程
- 关于C++中的虚拟继承的一些总结
- C++ 值传递 指针传递 引用传递
- Palindrome Partitioning
- c++ bind函数绑定——c++复习(六)