c++学习笔记——指针

 指针和自由存储的空间

#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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值