C++幼儿园[4] - 指针

来源:互联网 发布:java instanceof方法 编辑:程序博客网 时间:2024/04/29 13:01

1.补充

先来补充一些知识点

1. 1.更多数据类型

首先,变量类型都可用 signed / unsigned(有符号/无符号)来修饰(除bool)
我们平常直接声明的 int a,其实全称为signed int a,其中signed关键字会默认加上,表示可取正,可取负值
若用unsigned修饰,则表示无符号,只可取正值,对应的取值范围会*2

char a范围:[-128,127]
unsigned char a范围 :[0,255]

另外,int double 都可用 long修饰
或者说,long可用int 和 double 修饰
short可用int修饰
贵圈略乱,见下

long int i; // 占8字节,范围:[-2^63,2^63-1]long ii;    //与上一句同义long double d; //占10(或8)字节,范围:超大short int s; //占2字节,范围多大?short ss; //同上

1.2. 类型转换

在将高精度/大范围的类型转换为低精度/小范围的变量时,会发生精度降低,数据丢失的情况

    int a;    double b = 1.25;    a = b; //或者直接写a=1.25    cout << a;  // 结果为1

注意,计算机中精度降低并不是四舍五入,而是数据直接舍弃,所以这里只保留整数部分

如果他们的取值范围差很多,就会出现溢出的情况

    int a = 9999;    char c = a; 

上面的例子里,如果直接cout<<c,会输出一个字符
可以用断点调试的方法来看c的值。
调试查看char型数据的值
加上断点,在执行了对c赋值的语句之后,将鼠标移到c上,悬停,显示c的值为15。

与之前相反,如果将int转为long,不会发生精度降低的情况。

以上的类型转换都是隐式进行的,与强制类型转换相对

//没有强制类型转换    double a = 5 / 2; //5和2都是整数,默认结果为int型,结果为2    double p= 5.0 / 2; //运算式中有一个double,结果就默认为double// 强制转换:数据类型 (变量)     double b = double(5) / 2; //b=2.5。将5转换为了double,结果默认为double    double c = 5 / double(2); //c=2.5//强制转换:(数据类型) 变量    double d = (int)2.5; //d=2    double e = (int)5 / 2.0; //e=2.5。int只结合了5,int/double 结果为double    double f = (int)(5 / 2.0); // f=2

以上int和double也代表了精度的相对高低,即float/int ,double/short 都有相同的效果

1.3. 逻辑运算符

好像我还没讲过啊,不过你们应该都会

|| 或(or)
&& 与(and)
! 非(not)

他们都对布尔型的量起作用
比如
false||true结果为true
false&&true结果为false
!false为true

bool a= !9;if(a || false && true){    cout << a;}

1.4. sizeof函数

不需include iostream即可使用

它return参数所占地址的大小,单位字节
参数可以是变量、数据类型

#include <iostream>using namespace std;void main(){    int a = 123456;    cout << sizeof(a) << endl;    cout << sizeof(int) << endl;    cout << sizeof(double) << endl;}

1.5. char型数组

它有一些特殊的地方,所以还是单独拿出来说一下

用char表示一个字母
char数组就表示一组字母了

void main(){    char k[3];    k[0] = 'k';    char a[3] = { 'k','d','g' }; //用单引号引起来的char来组成数组//  char b[3] = { "kdg" };  //不行?    char c[3] = "kd"; //又行了?    char d[] = "cppkdg";    cout << a[2] << endl;    for (int i = 0; i < 20; i++)    {        cout << d[i]; //输出了什么?    }}

再来试验一下

void main(){    char c[] = "cppkdg cpp\0kdg";    cout << c;}

得出结论,用""引起来的字符串,以\0作为结尾符号,且占1字节

1.6. 优雅地使用函数

函数的一个好处就是让程序分块了
比如做一道菜cook(),我们有几个步骤
wash()洗菜
oil() 放油
fry() 翻炒
fry()的过程中又要调节火候adjustFire()和放盐salt()
这个程序大概就长这样

void wash() { cout << "洗菜"; }void oil() { cout << "放油"; }void salt() { cout << "放了盐"; }void ajustFire() { cout << "调整火"; }void fry(){    ajustFire(); //注意,C++不能在函数中定义函数,只能调用    salt();}void cook(){    wash();    oil();    fry();}

每个函数都只做一件事,这件事就是它的名字。
下次别人要用你的程序炒菜,他光看名字就知道每个函数是干什么的,就能节约很多时间,创造出属于他的myCook()函数,美滋滋

函数的另一个好处是提高了代码的重用性
例如上面的salt(),只用写一遍,就可以在其他地方随意使用了(当然实际情况,需要加上一些参数)

2. 指针与地址

进入正题

2.1. 地址

变量在内存中存储的位置就是他们的地址。
我们所说的32位/64位系统,内存的地址就有32/64bits,一般用十六进制表示。
我们平常所写的win32程序,地址就是一个8位的,十六进制数字
像是0XFFFFFF

先介绍一下取地址运算符&
它有两种作用(当然,第3种,做位运算,bit operation用,暂不介绍)
写在等号左边时,表示引用
(阅读下面的教程)
http://www.runoob.com/cplusplus/cpp-references.html

写在等号右边时,表示取……的地址

    int a = 13548;    cout << &a; //输出看看,我这里的结果是00AFF9C4

所说的“等号”左边右边是一个形象的说法
等号左边:声明、定义一个变量/给一个变量赋值,称为左值
等号右边:某运算,称为右值

对右值做的操作,并不会影响原变量
比如上面将的类型强制转换

double a = 2.5;int i = (int)a;cout<< i<< " "<< a; 

原来的a并没有被强制转换成int,只是a的值赋给i的时候,是当做int的

2.2. 指针(pointer, ptr, p)

阅读下面的教程,对指针有个初步的了解
http://www.runoob.com/cplusplus/cpp-pointers.html

总结一下
今有int a=123;
int *p = &a;
或分开写,int *p; p = &a;
此时p是变量a的地址
*p是p的“值的值”,即p所代表的地址的值,即a,即123
对p的操作会影响a
&取地址对应,*就是取值号

引用一段书上的话

指针是“危险”的,如果未对它进行初始化,它就指向计算机里的一个随机地址,这个地址可能是一些重要的数据或程序代码,如果盲目去访问,可能会对系统造成很大的危害,因此指针变量在使用之前必须有确定的指向,应先赋值后再引用。

指针最常用的情况:
通过指针改变原变量的值
记得上次说过形参和实参的问题

void swap(int a,int b) //交换两个值{    int temp = b;    b = a;    a = temp;}void main(){    int a = 1;    int b = 2;    swap(a, b); //这样无法交换main函数中a,b的值}

以及…补充一点…调用的时候,swap(a,b),就相当于给swap函数传进去了两个可以被使用的值,理解为swap(int a=a, int b=b),等号左边是形参,名字可以被任意更改。
比如我定义

void swap(int num1, int num2) { ;}

调用时,可以想象成num1=a, num2=b,然后在函数内对num1和num2做各种改变

可以用指针来达到交换的效果。
http://www.runoob.com/cplusplus/cpp-passing-pointers-to-functions.html
阅读上面的教程,想想怎么实现变量交换

其实平常用的数组,变量的名字,就表示地址

#include <iostream>using namespace std;void main(){    int a[] = { 10,2,3 };    int *p = a; //a就是地址,所以不用&符号了    cout << a << endl;//  a = a + 1;   //错误,a不可变    cout << p << " " << p + 1 << endl;    p = p + 1; //可以,p表示的地址可变    cout << *p << " " << *(p + 1) << endl;}

通过以上程序,我们发现,可对地址用运算符号,“地址+1”得到相邻的地址,而数组就是一组地址相邻的数,所以数组第n个元素地址+1得到第n+1个元素的地址。
也发现了,数组名字表示的地址,是该数组第0个数的地址,即*p为10

所以,我们用的a[1]其实就是*(a+1)!也许[]就是为了简化指针呢

贴个教程加强理解,指针与数组
http://www.runoob.com/cplusplus/cpp-pointers-vs-arrays.html

以上是指针的基础知识,下面几个点比较容易弄混,搞不懂的多百度+实践

int *p 可以指向一个数组,即p表示了数组首元素的地址
那么,int (*p)[4]是什么?
指针数组。它是一组指针

int **p是什么?
二重指针。常用来指向一个指针数组,而每个一重指针指向一个数组(可以是多维)。可以有更多的指针符号,int ****p,就是一个多重指针,不过基本不用

很有必要的空指针
http://www.runoob.com/cplusplus/cpp-null-pointers.html

下面这篇加深理解(概念还是很重要的,但貌似下学期课程并不会讲?)
指针与引用的区分,参数传递
http://www.cnblogs.com/dolphin0520/archive/2011/04/03/2004869.html
上面这篇可能有些难以理解,不懂的多问

2.3. 动态数组

之前大家都应该试过,

int x;cin>> x;int a[x];

是错误的

那我们怎么来定义一个长度取决于用户需要的数组?

    int x;    cin >> x;    int *a = new int[x]; //第一步,申请一片连续的内存空间/** ...* 正常地使用该数组*/     delete[] a; //第二步,释放内存地址,否则会内存泄漏    a = NULL; //第三步,指针置空

上面三步都是不可少的,第三步最容易被忘掉

它们的原理:https://zhidao.baidu.com/question/1511589718638971140.html


以上就是这次要讲的内容,由于指针是C++入门的重难点,内容较多(贴了很多文章),难度较大,所以有什么看不懂/查不到的,多问。

作业(要交):

  1. 将之前的作业“数组排序”写成2个函数,用2种不同的方法排序(推荐冒泡排序和选择排序,其他也可),并在main中调用
    要求:将输入的数组排序,也就是调用了sort(a)后,a变成有序数组
    传入:一个int型数组(或指针、地址等),长度length
    返回:空
    提示:用指针、地址的相关知识和sizeof函数(求长度length)

  2. 写一个函数,交换两个int型变量,返回为空

  3. 写一个函数,计算字符串长度
    传入:一个字符串(char型数组),不要用string,形参不能是数组
    返回:长度,不计末尾的\0

  4. 写一个函数,将一个字符串(b)拼接到另一个字符串(a)末尾,并能让改变影响原数组
    传入:两个字符串(char型数组),不要用string,形参不能是数组
    返回:空
    提示:注意a的长度,以及结束符\0

  5. 写一个函数,反转字符串。即abcd变成dcba,要求同上,注意结束符

第一题的参考和解析:http://c.biancheng.net/cpp/biancheng/view/43.html

0 0
原创粉丝点击