• 1.review二维数组
  • 2.指针常量变量指向空指针
  • 3.指针的运算
  • 4.指针运算续加数组遇上指针
  • 5.二维数组与指针
  • 6.函数之库函数rand_srand

1.review二维数组

 

二维数组的本质,也是一维数组,只不过,一维数组中的每个元素,又是一个一维数组而己。

声明/定义

int[4]  array[3] => int array[3][4];

二维数组的逻辑形式
基础七_指针变量
二维数组的存储形式

基础七_数组名_02

2.指针常量变量指向空指针

 

十六进制

基础七_#include_03

指针的指向

基础七_指针变量_04

#include <stdio.h>


int main(int argc, char *argv[])
{

    int a;
    int *p = &a;
    printf("%p\n",p);

    *p = 100;
    printf("%d\n",a);

    int b;
    p = &b;
    *p = 231;
    printf("%d\n",b);


    return 0;
}

基础七_二维数组_05

野指针

一个指针变量,如果,指向一段无效的空间,则该指针称为野指针,是由invalidpointer翻译过来,直译是无效指针。

常见情型有两种,一种是未初化的指针,一种是指向一种己经被释放的空间。

对野指针的读或写崩溃尚可忍受,对野指针的写入成功,造成的后果是不可估量的。

对野指针的读写操作,是危险而且是没有意义的。

世上十之八九最难调的bug皆跟它有关系。


NULL指针(零值无类型指针)

如何避免野指针呢。

NULL是一个宏,俗称空指针,他等价于指针(void*) 0(void*)0是一个很特别的指针,因为他是一个计算机黑洞,既读不出东西,也不写进东西去。

所以被赋值NULL的指针变量,进行读写操作,是不会有内存数据损坏的。

c标准中是这样定义的:

define NULL  ((void *)0)

故常用NULL来给临时不需要初初始化的指针变量来进行初始化。

或对己经被释放指向内存空间的指针赋值。

可以理解为c专门拿出了NULL(零值无类型指针),用于作标志位使用。


void本质

void即无类型,可以赋给任意类型的指针,本质即代表内存的最小单位,在32位机上地位等同于char。

#include <stdio.h>


int main(int argc, char *argv[])
{


    printf("sizeof(char) = %d\n",sizeof (char));
    printf("sizeof(void) = %d\n",sizeof (void));


    return 0;
}

基础七_数组名_06

声明两个变量分别表示什么

int *p,q;
#include <stdio.h>


int main(int argc, char *argv[])
{

    char *p,q;
    printf("sizeof(*p) = %d\n",sizeof (p));
    printf("sizeof(q) = %d\n",sizeof (q));


    return 0;
}

基础七_二维数组_07

区别指针

int *ptr_var;
ptr_var,&ptr_var,*ptr_var的三者区别
ptr_var:指针变量
&ptr_var:指针变量的地址
*ptr_var:取指针变量地址的内容

3.指针的运算

 

指针运算的本质是指针中存储的地址的运算。
指针可参与的运算。

赋值运算

区别初始化和赋值。

不兼容类型赋值会发生类型丢失。

为了避免隐式转化带来可能出现的错误,最好用强制转化显示的区别。


指针的算术运算,不是简单的数值运算,而是一种数值加类型运算。将指针加上或者减去某个整数值(以n*sizeof(T)为单位进行操作的)。

运算符 + - ++ --
示例 p+3 p-3 p++/++p p--/--p

#include <stdio.h>


//指针类型(步长)+地址(物理数据)

//数值+1,就是加1。
//指针+1,加的是步长,指针类型的大小

int main(int argc, char *argv[])
{

    int *p = (int*)0x0001;
    int  data= 0x0001;
    printf("p    =%#x  p+1   =%#x\n",p,p+1);
    printf("p    =%#x  p+1   =%#x\n",data,data+1);
    printf( "(double*)p = %#x(double*)p+1 = %#x\n" ,(double*)p,(double*)p+1);
    printf("(int)p = %#x(int)p+1 = %#x\n" ,(int)p,(int)p+1);


    printf( "%x\n" ,++p);
    printf( "%x\n" ,++data);

    int arr[10];
    int *pHead = &arr[0];
    int *pTail = &arr[9];
    printf("%d \n" ,pHead);
    printf("%d \n" ,pTail);
    printf("%d \n" ,pHead - pTail);

    int arr1[10];
    int *pHead1 = (int)&arr1[0];
    int *pTail1 = (int)&arr1[9];
    printf("%d \n" ,pHead1 - pTail1);

    return 0;
}

4.指针运算续加数组遇上指针

 

#include <stdio.h>


//指针类型(步长)+地址(物理数据)

//数值+1,就是加1。
//指针+1,加的是步长,指针类型的大小

int main(int argc, char *argv[])
{


    int *p=0x0001;
    int *q=0x0005;

    if(p+1 == q)
    {
        printf("p+1  == q\n");
    }

    return 0;
}

基础七_指针变量_08

注意:只有当指针指向一串连续的存储单元时,指针的移动才有意义。才可以将一个指针变量与一个整数n做加减运算

练习:判断是否是回文字符串

#include <stdio.h>


//指针类型(步长)+地址(物理数据)

//数值+1,就是加1。
//指针+1,加的是步长,指针类型的大小

int main(int argc, char *argv[])
{



    char name[5] = {'m','a','d','a','m'};
    char *ph = &name[0];
    char *pt = &name[4];

    int flag = 1;
    while(ph<pt)
    {
          if(*ph == *pt)
          {
              ph++;
              pt--;
          }
          else {
             break;
             flag = 0;
          }

    }

    if(flag ==1)
    {
        printf("回文");
    }else {
        printf("非回文");
    }
    return 0;
}

基础七_二维数组_09

指针的运算只能发生在同类型或整型之间,否则会报错或是警告。
指针的运算,除了数值以外,还有类型在里面。

数组遇上指针

一维数组的访问方式

传统方式(下标/偏移法)

数组名是数组的唯一标识符,数组名代表数组首元素的地址

我们可以用下标的方式对数组进行访问。
除此之外,还可以用,本质方法进行访问。

数组名是常量指针
数组名是常量,才可以唯一的确定数组元素的起始地址。

一维数组名跟一级指针的关系
数组除了可以用下标法和本质法访问以外,还可以用指针法访问。能用数组名解决的问题的,都可以用指针来解决,而能用指针来解决的问题,并一定能用数组名来解决。

#include <stdio.h>

//数组访问  偏移法  本质法

int main(int argc, char *argv[])
{

    int arr[10] = {1,2,3,4,5,6,7,8,9,0};

    for (int i=0;i<10;i++)
    {
        printf("%d\n",*(arr+i));
    }

    return 0;
}

基础七_数组名_10

#include <stdio.h>

//数组访问  下标法

int main(int argc, char *argv[])
{

    int arr[10] = {1,2,3,4,5,6,7,8,9,0};

    for (int i=0;i<10;i++)
    {
        printf("%d\n",arr[i]);
    }

    return 0;
}

基础七_指针变量_11

#include <stdio.h>

//数组访问  下标法
//一维的数组名,可以赋给一级指针。
//数组名能干的,指针就能干,数组不能干的,指针也能干。

int main(int argc, char *argv[])
{

    int arr[10] = {1,2,3,4,5,6,7,8,9,0};
    int *pa = arr;

    for (int i=0;i<10;i++) {
        printf("%d\n",*pa);
        pa++;
    }


    return 0;
}

5.二维数组与指针

 

#include <stdio.h>

//数组访问  下标法
//一维的数组名,可以赋给一级指针。
//数组名能干的,指针就能干,数组不能干的,指针也能干。

int main(int argc, char *argv[])
{

    int arr[10] = {5,5,5,5,5,5,5,5,5,5};
    int *pa = arr;

    for (int i=0;i<10;i++) {
        printf("%d\n",(*pa)++);
        //printf("%d\n",arr[0]++);

    }

    for (int i=0;i<10;i++) {
        printf("%d\n",*(pa+i));
    }


    return 0;
}

基础七_数组_12

1.数组名是一个常量,不允许重新赋值。⒉指针变量是一个变量,可以重新赋值。
3.p+和ai均表示数组元素ai的地址,均指向a利
4.*(p+i)和*(ati)均表示p+i和a+i所指对象的内容a0。
5.*p++︰等价于"(p++)。其作用:先得到*p,再使p=p+1。
6.(*p)++:表示将p所指向的变置(元素)的值加1。即等价于a[i]+。
7.指向数组元素的指针也可以表示成数组的形式,即允许指针变量带下标,如*(p+i)可以表示成p。

二维数组的访问方式

下标法
数组元素的表示方法是:数组名称[行][列],对于m行n列的二维数组,a[0][0]是数组的第一个元素,a[m-1][n-1]是最后一个元素。

本质偏移法

aa[0]a[0][0]都经历了什么?

#include <stdio.h>

//数组访问  下标法
//一维的数组名,可以赋给一级指针。
//数组名能干的,指针就能干,数组不能干的,指针也能干。

int main(int argc, char *argv[])
{

    int arr[3][4] = {1,2,3,4,10,20,30,40,111,222,333,444};
    for(int i=2; i>=0; i--){
        for(int j=3; j>=0; j--){
            printf("a[%d][%d]  = %#x\n",i,j,&arr[i][j]);
        }
        printf("=========== \n");
    }

    printf( "arr = %#p arr+1 = %#x arr+2 = %#x \n", arr, arr+1,arr+2);
    return 0;
}

基础七_数组_13

#include <stdio.h>

//数组访问  下标法
//一维的数组名,可以赋给一级指针。
//数组名能干的,指针就能干,数组不能干的,指针也能干。

int main(int argc, char *argv[])
{

    int arr[3][4] = {1,2,3,4,10,20,30,40,111,222,333,444};
    for(int i=2; i>=0; i--){
        for(int j=3; j>=0; j--){
            printf("a[%d][%d]  = %#x\n",i,j,&arr[i][j]);
        }
        printf("=========== \n");
    }

    printf( "arr = %#p arr+1 = %#x arr+2 = %#x \n", arr, arr+1,arr+2);
    printf( "arr[0] = %#p arr[0]+1 = %#x arr[0]+2 = %#x \n", arr[0], arr[0]+1,arr[0]+2);

    return 0;
}

基础七_数组_14

二维数组名,跟二级指针,没有关系
二维数组名的本质是,数组指针

6.函数之库函数rand_srand

 


c语言包含两部分,c语法和c标准库。


使用函数,可以有如下好处:
可以提高程序开发的效率。
提高了代码的重用性。
使程序变得更简短而清晰。
有利于程序维护。


库是对函数的再一次升级


库存在的意义

将常用的函数分类组织到一起,即可制作成函数库。

库存在的意义,就是避免重复造轮子。

像我们使用的printf 函数,没有必要每个使用此功能的函数,都要重写来实现。重复现成的、可用的、已经证明很好用的东西就是造轮子。


如何使用库函数

由C语言系统提供;用户无须定义,也不必在程序中作类型说明;只需在程序前包含有该函数定义的头文件(/usr/include/stdio.h),而不无关系库在哪里(/usr/lib/libc.so 标准库);到底提供了哪些函数可以通过查表的方式获得。
函数的三要素,函数名,函数参数,函数返回值,却是我们要研究的。


#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(int argc, char *argv[])
{

    unsigned int seconds = time(NULL);
    printf("%d\n",seconds);

    return 0;
}

基础七_数组_15

随机函数rand():
使用随机函数产生,某一范围内了随机数。比如生成[1,100]以内的随机数。

srand和 rand()配合使用产生伪随机数序列。

rand函数在产生随机数前,需要系统提供的生成伪随机数序列的种子,rand根据这个种子的值产生一系列随机数。

如果系统提供的种子没有变化,每次调用rand函数生成的伪随机数序列都是一样的。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(int argc, char *argv[])
{
    srand(time(NULL));    //给随机数发生器一个种子,rand去发生器去取数
    int randNum = rand();
    printf("%d\n",randNum);
    randNum = rand();
    printf("%d\n",randNum);
    randNum = rand();
    printf("%d\n",randNum);
    randNum = rand();
    printf("%d\n",randNum);
    randNum = rand();
    printf("%d\n",randNum);
    return 0;
}

基础七_数组名_16

练习:产生10个不同的随机数int randArr[10]