指针和自由存储的空间
#include <iostream>
int main()
{
using namespace std;
int donuts = 6;
double cups = 4.5;
cout << "donuts value = " << donuts;
cout << " and donuts address = " << &donuts << endl;
//使用取地址符&可以找到常规变量的地址,地址一般以十六进制存储,
cout << "cups value = " << cups;
cout << " and cups address " << &cups << endl;
//在现在操作的系统中先存储了cups ,后存储了donuts,所以donuts的存储位置要比cups低,相差8个字节,应为double占8个字节。
//在有些系统中,可能先存储了donuts,后存储cups,这样cups的存储位置就比donuts的存储位置低,相差了4个字节,因为int占4个字节。
//有的系统也可能不会将这两个变量存储在相邻的内存单元中。
return 0;
}
donuts value = 6 and donuts address = 0x61fe1c
cups value = 4.5 and cups address 0x61fe10
如何声明指针以及初始化
#include <iostream>
int main()
{
using namespace std;
int updates = 6;
int* p_updates;//空格是可选的,一般c++的程序员采用int* 变量的形式(即在*和变量之间有一个空格),c的就是int *变量
p_updates = &updates;
//int* p1,p2;//此处声明的是一个指针变量(p1)和一个int型变量(p2),所以需要注意的是对每个指针变量名,都需要加一个*。
//由于不同类型的所使用的的字节是不同的,所以在声明指针变量时必须指出指定指针指向的数据的类型
//*p_updates就是int类型,p_updates是指向int的指针
//double* tax_ptr;
//char* str;
//虽然tax_ptr和str指向两种长度不同的数据类型,但这两个变量本身的长度通常是相同的。也就是说此处的char的地址和double地址的长度相等。
//初始化指针:需要注意的是初始化的是指针变量,就指针变量 = &所指向的值,而不是初始化指针变量所指向的值,即*指针变量= 值;
//上面的p_updates就是一种初始化的方式:即第7行的:p_updates = &updates,(不能用*p_updates = updates代替),这样会引发一些危险。
//一定要在对指针应用解除引用运算符(*)之前,将指针初始为一个确定的地址。
//也可以在声明语句中就初始化其值,可将第六、七行代码改为 int* p_updates = &updates;
//指针与数字
//虽然计算机把地址当做整数来处理,但是指针和整数是截然不同的类型,不能简单地将整数赋值给指针
int* pt;
//pt = 0xB8000000;//error
pt = (int*) 0xB8000000;//要将数字作为地址来用,须使用强制类型转化将数字转化成为合适的地址类型,
cout << "Value: updates = " << updates;
cout << ", *p_uodates = " << p_updates << endl;
cout << "Address: &updates = " << &updates;
cout << ", p_updates = " << p_updates << endl;
*p_updates = *p_updates + 1;//由于指针变量指向updates,所以*p_updates完全等价于updates
cout << "Now updates = " << updates << endl;
return 0;
}
Value: updates = 6, *p_uodates = 0x61fe0c
Address: &updates = 0x61fe0c, p_updates = 0x61fe0c
Now updates = 7
使用new来分配内存
#include <iostream>
int main()
{
using namespace std;
int nights = 1001;
int* pt = new int;//基本格式就是数据类型* 指针变量 = new 数据类型,也就是需要在两个地方指定数据类型,用来指定需要什么样的内存和用来声明合适的指针
//第一个int用来声明pn是指向int的指针,第二个int是通过new来告诉程序需要合适存储int类型的内存,new运算符根据类型来确定需要多少字节的内存。然后找到这样的内存并返回。
*pt = 1001;
cout << "night value = ";
cout << nights << " :location " << &nights << endl;
cout << "int ";
cout << "value = " << *pt << " : location " << pt << endl;
double* pd = new double;
*pd = 10000001.1;
//new分配的内存块与常规变量声明不同,变量nights和pd的值都存储在被称为栈的内存区域内,
//而new从被称为 堆 或者自由存储区的内存区域分配空间
cout << "double ";
cout << "value = " << *pd << ":location = " << pd << endl;
cout << "location of pointer pd:" << &pd << endl;
cout << "size of pt = " << sizeof(pt);
cout << ": size of *pt = " << sizeof(*pt) <<endl;
cout << "size of pf= " << sizeof pd;
cout << ": size of *pd=" << sizeof(*pd) <<endl;
delete pd;//使用delete可以释放由new分配的内存,这将释放pd指向的内存,但不会删除指针pd本身,可以使指针重新指向另一个新分配的内存块,
//new和delete一定要配合使用,否则会导致内存泄漏,也就是被分配的内存无法使用了。
delete pt;
//delete pt;//不要释放已经释放的内存
int* ps = new int;
int* pq = ps;
delete pq;
//这里设置了两个指向同一个内存块的指针,这将增加错误地删除同一个内存块两次的可能性,所以对于返回指针的函数,设置另一个指针是必要的。
return 0;
}
night value = 1001 :location 0x61fe04
int value = 1001 : location 0x1f1c30
double value = 1e+07:location = 0x1f1c50
location of pointer pd:0x61fdf8
size of pt = 8: size of *pt = 4
size of pf= 8: size of *pd=8
使用new来创建动态数组
#include <iostream>
int main()
{
using namespace std;
double* p3 = new double[3];//创建动态数组的方式:在后面加上方括号,里面标清楚元素个数
//此时new运算符返回第一个元素的地址,该地址被赋值给p3;这里p3实际上是指向一个double的指针
//所以*p3就是第一个元素的值
p3[0] = 0.2;//可以把指针变量p3看成是动态数组的名称,p3[0]就是第一个元素,*p3和p3[0]是等价的
p3[1] = 0.5;
p3[2] = 0.8;
cout << "p3[1] is " << p3[1] << ".\n";
p3 = p3 + 1;//不能修改数组名的值,但是指针是变量,可以修改其值,这里p3+1后,p3[0]就指向第数组二个元素的值,
//相邻的int一般差两个或者四个字符,但是这里加一后就是将指向下一个元素的地址,这就表面指针算术有特别之处。
cout << "Now p3[0] is " <<p3[0] << " and ";
cout << "p3[1] is " << p3[1] << ".\n";
p3 = p3 - 1;//上面+1,这里需要-1,使p3还指向第一个元素,方便delete [];
delete [] p3;//当程序使用完成后,应该释放掉,方括号告诉内存应该释放掉整个数组
//方括号应该对应着来,使用new时有(没有),那么delete也应该有(没有);
return 0;
}
p3[1] is 0.5.
Now p3[0] is 0.5 and p3[1] is 0.8.
指针、数组和指针运算
#include <iostream>
int main()
{
using namespace std;
double wages[3] = {1000.0, 2000.0, 3000.0};
//c++在多数情况下将数组名解释为数组第一个元素的地址,也就是wages指向数组的第一个元素,可以使用&wages[0] = wages
short stacks[3] = {3, 2, 1};
double* pw = wages;//将pw声明为wages数组第一个元素的地址,
short * ps = &stacks[0];
cout << "pw = " << pw << " , *pw = " << *pw <<endl;
pw = pw + 1;//pw+1,由于pw指向double类型,因此就会让他pw的值(就是对应的十六进制地址的数字加8)
//也就是指针变量加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 pointer:\n";
cout << " ps = " << ps << ", *ps = " << *ps << "\n\n";
cout << "access two elements with array notation.\n";
cout << "stacks[0] = " <<stacks[0]
<<", stack[1] = " <<stacks[1] << endl;
cout << "access two elements with pointer notation\n";
cout << "*stacks = " << *stacks
<< ", *(stacks + 1) = " <<*(stacks + 1) << endl;//数组名stacks开始和ps都是指向数组第一个元素的值
//故*stacks等价于*ps,*(stacks + i)等价于*(ps + i);stacks[i]等价于ps[i];不一样就是可以修改指针的值,但数组名是常量
//另一点不一样的就是sizeof(指针变量,这里是ps和pw)得到的指标的长度,sizeof(数组名,这里是stracks和wages)是数组的长度,如下
cout << sizeof(wages) << " = size of wages array\n";
cout << sizeof(pw) << " = size of pw pointer\n";
short tell[10];
cout << tell << endl;
cout << &tell << endl;
//从数字上说两个数字相等,数组名解释为其第一个元素的地址,但是对数组名应用取地址符&后得到的整个数组的地址
//&tell[0]是一个2字节内存块的地址,
//但是&tell是一个20字节内存块的地址,tell+1,将地址值加2,&tell+1将地址值加20,
//也就是tell是一个short指针(也就是short*),&tell是一个指向包含10个元素的short数组(shoort(*)[10]);
short (*pas) [10] = &tell;//注意不可省略括号,否则优先级会导致pas变成一个short指针数组,它包含10个元素,
//此时pas的类型是short(*)[10],由于pas被设置为&tell,因此*pas与tell等价,所以(*pas)[0]
return 0;
}
pw = 0x61fdf0 , *pw = 1000
add 1 to the pw pointer:
pw = 0x61fdf8 , *pw = 2000
ps = 0x61fdea , *ps = 3
add 1 to the pointer:
ps = 0x61fdec, *ps = 2
access two elements with array notation.
stacks[0] = 3, stack[1] = 2
access two elements with pointer notation
*stacks = 3, *(stacks + 1) = 2
24 = size of wages array
8 = size of pw pointer
0x61fdd0
0x61fdd0
指针和字符串
#include <iostream>
#include <cstring>
int main()
{
using namespace std;
char animal[20] = "bear";
const char* bird = "wren";
char* ps;
cout << animal << " and ";
//数组名是第一个元素的地址,因此cout语句中的animal是包含字符b的char元素的地址,
//cout认为char的地址就是字符串的地址,因此cout就得打印该地址处的字符,直到遇到空字符。
//总之,给cout一个字符的地址,它就将从该字符开始打印,直到遇到空字符为止。
//用引号括起来的字符串像数组名一样,也是第一个元素的地址,在传递给cout时也是发送该字符串的地址,不是将整个字符串都发送给cout
//综上,对于字符串、用引号括起来的字符常量以及指针所描述的字符串,处理方式一样,都将传递他们的地址
cout << bird << "\n";
//cout << ps << "n";\\此处指针变量ps未被初始化,创建未初始化的指针无法控制它如何被使用,用cout对其进行输出时可能是一个空行
//也可能是一堆乱码,或者程序崩溃。
cout << "Enter a kind of animal: " ;
cin >> animal;//使用数组animal进行输入一版是安全的,只要输入比较短,能够被存储在数组中
//但是使用bird来进行输入不合适,因为bird指针被声明为const,因此编译器禁止改变bird指向的位置中的内容
//cin >> ps;//也不能使用未被初始化的指针进行输入,因为ps未被初始化,因此不知道信息应该被存储在什么地方,
//甚至可能改写内存中的信息。所以,在将字符串读入程序中,应使用已分配的内存地址,该地址可以是数组名,也可以是使用new初始化过的指针。
ps = animal;//将animal赋值给ps,并不会复制字符串,而是复制地址,
//这样两个指针都将指向相同的内存单元和字符串,获取的不是字符串的副本,获取字符串的副本见34行
cout << ps << "!\n";
cout << "Before using strcpy() :\n";
cout << animal << " at " << (int* ) ps << endl;
//一般给cout一个指针变量,会输出其地址,但是如果指针是char*类型,则cout将显示指向的字符串
//如果想要显示的字符串的地址,必须将这种指针强制转化成另一种指针类型,如int * ,
ps = new char[strlen(animal) + 1];//创建a字符串副本,new找到一个合适正确的内存块,并返回内存块的地址
//+1是因为要包含进去空字符(strlen函数:计算的是字符串str的长度,从字符的首地址开始遍历,
//以 '\0' 为结束标志,然后将计算的长度返回,计算的长度并不包含'\0')
strcpy(ps, animal);//strcpy()接收两个参数,第一个参数是目标地址,第二个是要复制的字符串的地址,可以将animal数组中的字符串复制到新分配的的空间中,最终得到副本(地址不一样)
cout << "After using strcpy():\n";
cout << animal << " at " << (int* ) animal << endl;
cout << ps << " at "<<(int* ) ps << endl;
//关于strcpy()函数和strncpy()
char food[20] = "carrots";
strcpy(food, "flan");//初始化数组是,应该使用=运算符,或者使用strcpy()或者strncpy(),这里是strcpy,它只有两个参数
strncpy(food, "a picnic basket filled with mang goodies", 19);//第三个参数是要复制的最大字符数,如果无第三个参数
//类似于语句的代码,,food数组比字符串小,函数会将字符串中剩余的部分复制到数组后面的内存字节中,这就可能会导致程序正在使用其他内存。
//如果有第三个参数的话,出现上面的情况时,该函数到达字符串结尾之前,目标内存已经用完,则它将不会添加空字符。那么就应该这样使用该函数:
//strncpy(food, "a picnic basket filled with mang goodies", 19);
//food[19] = '\0';//这样就最多复制19个字符,然后将最后一个字符设置成空字符
//当然,如果要复制的字符串少于19个字符,那么strncpy()将在复制完该字符串之后加上空字符,以标记字符串的结尾。
//注意!!!:应该使用strcpy()和strncpy,而不是赋值运算符来将字符串赋给数组;
delete [] ps;
return 0;
}
Enter a kind of animal: fix
fix!
Before using strcpy() :
fix at 0x61fdf0
After using strcpy():
fix at 0x61fdf0
fix at 0xfd1c30
这个程序中再申明一点是:语句ps = animal;将animal赋值给ps并不会复制字符串,而只是复制地址,这样两个指针指向的同一个内存地址和字符串,ps就不是animal的副本(因为指向的同一个地址,一旦指向的这个地址被释放,那么字符串也就不存在了,所以不是副本)。
ps = new char[strlen(animal+1)]; strcpy(ps, animal);这两个语句最终创建了字符串的副本,首先使用new动态的分配了能够刚好能容纳animal字符串的内存块(该内存块的地址是新的,跟animal的地址不一样),然后使用函数strcpy()将字符串animal字符数组中的字符串复制到新分配的空间中,字符数组animal的副本就创建完成了。
关于strcpy()函数:
它有两个参数:第一个参数是目标地址,第二个参数就是要复制的字符串的地址。当目标地址不足以容纳字符串时,函数会将剩余部分复制到数组后面的内存字节中,这就可能会导致覆盖程序的其他内存。针对这个问题,有两种解决办法:
第一种:使用函数strncpy()函数,注意是strncpy(),它有第三个参数——要复制的最大字符数。使用这个函数也有需要注意的点,比如设置的要复制的最大字符数是19的话,如果被赋值的字符串少于19的话,则strncpy()函数将在复制完该字符串后自动加上空字符,以标记该字符串的结尾。如果是大于19的话,它就不会添加空字符。就需要像下面这样使用该函数(手动将字符串的最后一位设置成空字符,因为c风格的字符的定义就是最后一个字符是空字符):
strncpy(food, "a picnic basket filled with mang goodies", 19);
food[19] = '\0';
使用new创建动态结构
//使用new创建动态结构
#include <iostream>
struct inflatable
{
char name[20];
float volumn;
double price;
};
int main()
{
using namespace std;
//使用new创建动态结分两步,第一步;创建结构时,要同时使用new和结构类型
//这里就是创建了一个未命名的inflatable类型,并将其地址赋值给ps指针变量
inflatable * ps = new inflatable;
//把足以存储inflatable结构的一块可用内存的地址赋给ps
cout << "Enter name of inflatiable item: ";
//由于使用new创建动态结构时,没有名称,只知道它的地址,所以不能将成员运算符句点应用于结构名
//应该使用箭头成员运算符(->),如下
cin.get(ps->name, 20);
//ps指向一个inflatable结构,则ps->name是被指向的结构的成员
//如果结构标识符是结构名,则使用句点运算符,如果标识符是指向指针结构的指针,则使用箭头运算符
cout << "Enter volumn in cubic feetd: ";
//另一种访问结构成员的方法时,ps是指向就结构的指针,则*ps就是被指向的值——结构本身。所以*ps就是一个结构,例如可使用(*ps).volumn
cin >> (*ps).volumn;
cout << "Enter price : $";
cin >> ps->price;
cout << "Name: " << (*ps).name <<endl;
cout << "volumn: "<< ps->volumn << " cubic feet\n";
cout << "Price: " << ps->price << endl;
delete ps;
return 0;
}
Enter name of inflatiable item: fox
Enter volumn in cubic feetd: 124
Enter price : $45
Name: fox
volumn: 124 cubic feet
Price: 45
一个使用delete和new的实例
#include <iostream>
#include <cstring>
using namespace std;
char* getname(void);
int main()
{
char* name;
name = getname();
cout << name << " at " << (int*) name << "\n";
delete [] name;//这里释放掉name指向的内存块,
name = getname();//再次调用getname函数,c++不保证新释放掉的内存(12行)就是下一次使用new时选择的内存。但此处输出的结果一致
cout << name << " at " << (int*) name << "\n";
delete [] name;
return 0;
}
char* getname()
{
char temp[80];
cout << "Enter last name: ";
cin >> temp;//使用cin将输入的单词放到temp数组中,
char* pn = new char[strlen(temp) + 1];//使用new分配新内存,以便存储该cin输入的单词
//+1是因为空字符,
strcpy(pn, temp);//使用标准库函数strcpy()将temp中的字符串复制到新的内存块中,
//该函数并不检查内存块是否可以容纳字符串,因为上面的new请求合适的字节数完成了这个工作
return pn;//函数返回pn。这个是字符串副本的地址;
//本程序将new和delete放在不同的函数中通常并不理想,因为很容易忘记delete。
}
Enter last name: g
g at 0xf21c30
Enter last name: y
y at 0xf21c30
类型组合
#include <iostream>
struct antarica_years_end
{
int year;
};
int main()
{
antarica_years_end s01, s02, s03;
s01.year = 1999;
antarica_years_end * pa = &s02;//创建指向antarica_years_end这种结构的指针
pa-> year = 1999;//使用箭头运算符表示结构成员
antarica_years_end trio[3];//可以创建结构数组,数组中的每一个元素都是antarica_years_end结构
trio[0].year = 2003;
std::cout << trio ->year << std::endl;
const antarica_years_end * arp[3] = {&s01, &s02, &s03};//可以创建指针数组,数组中的每一个元素都是指向antarica_years_end结构的指针
std::cout << arp[1]->year << std::endl;
const antarica_years_end ** ppa = arp;//其中arp是一个数组的名称,因此它是第一个元素的地址,
//但其第一个元素是指针,所以ppa是一个指针,它指向const antarica_years_end,
auto ppd = arp;//使用auto编译器知道arp的类型,进而推断出ppd的类型,可以替代ppa的创建方式
std::cout << (*ppa) ->year <<std::endl;
std::cout << (*(ppd + 1))->year << std::endl;
return 0;
}
2003
1999
1999
1999