C语言万字基础笔记总结(二)

简介: 简介:C语言学习笔记,记录所学,便于复习。 由于篇幅过大,考虑到观感,准备分多篇记录。学习视频:《带你学C带你飞》IDE:Dev-C++ 5.11为了练习编程习惯,暂时放弃Clion

一、数组

1、数组的定义

  • 格式:类型 数组名[元素个数]

如:int i[5]

2、访问数组元素

  • 格式:数组名[下标]
    如:
    i[0]; // 访问i数组中第1个元素
    i[3]; // 访问i数组中第4个元素
  • :数组的下标是从0开始的,下标0对应数组第一个元素

3、数组的初始化

  • 将数组中所有元素初始化为0
int a[5] = {0};
//事实上,这个只是把第一个元素赋值为0,其余各位是自动初始化为0
int a[5] = {1};
//这里就是,第一个元素为1,其余元素为0
  • 如果是给元组赋予不同的值,用逗号隔开即可
int a[5] = {1, 2, 3, 4, 5};
  • 也可以只给一部分元素赋值,未被赋值的元素,自动初始化为0
int a[5] = {1, 2, 3};
//前三位成功赋值,后两位,自动初始化为0
  • 也可以指定元素进行赋值,未被赋值的元素自动初始化为0
int a[5] = {[2] = 2, [4] = [4]};
//数组a的第3位被赋值为2,第5位被赋值为4,因为数组的下标是从0开始的!!!
  • 也可以只给出元素的值,不指定数组的长度

(编译器会根据值的个数自动判断数组的长度)

int a[] = {1, 2, 3, 4, 5}


二、 二维数组

1、二维数组的定义

  • 格式:类型 数组名[常量表达式] [常量表达式]

2、二维数组的访问

  • 格式:数组名[下标][下标]

a[0][0]; // 访问a数组中第一行第一列的元素

b[2][3]; // 访问b数组中第三行第四列的元素

3、二维数组的初始化

  • 由于二维数组在内存里面中是线性存放的,因此可以将所有的数据写在一个花括号里面
int a[2][3] = {1, 2, 3, 4, 5, 6};

那到底是怎么存放的呢,用程序打印一下,观察结果。

#include <stdio.h>
int main()
{
    int a[2][3] = {1, 2, 3, 4, 5, 6};
    int i, j;
    for (i = 0;i < 2;i++)
    {
        for (j = 0;j < 3;j++)
        {
            printf("a[%d][%d] = %d\n", i, j, a[i][j]);
        }
    }
    return 0;
}

结果如下:

a[0][0] = 1
a[0][1] = 2
a[0][2] = 3
a[1][0] = 4
a[1][1] = 5
a[1][2] = 6

通过结果,是不是很容易看出来了~

  • 更直观的表示元素的分布,可以用大括号将每一行的元素括起来
int a[2][3] = {{1, 2, 3}, { 4, 5, 6}};

或者写成这种样式:

int a[][3] = {
         {1, 2, 3}, 
         {4, 5, 6}
};

以上这两种形式的行数都可以省略不写

  • 将整个二维数组初始化为0,则只需要在大括号里写一个0
int a[2][3] = {0};
  • 对某些指定的元素进行初始化,其它元素自动初始化为0
int a[2][3] = {[0][0] = 1, [1][2] = 2};

三、指针

1、定义指针变量

- 格式:类型名 *指针变量名

char *pa; //定义一个指向字符型的指针变量

int *pb; // 定义一个指向整型的指针变量

取地址运算符和取值运算符

  • 如果需要获取某个变量的地址,可以使用取地址运算符(&)
    如:char *pa = &a;
  • 如果需要访问指针变量指向的数据,可以使用取值运算符(*)
    如:printf("%c, %d\n", *pa, *pb);
  • 注意:

值得一提的是 * 这个符号,在定义指针需要它,而在同时又是取值运算符。需要慢慢理解

避免使用未初始化的指针

int *p //例如这样就是未初始化的指针
-----------------------------
int *p = &a //例如这样就是初始化过的指针
  • 指针的小例子
# include <stdio.h>
int main(){
  char a = 'H';
  int b = 521; 
  char *pa = &a;
  int *pb = &b;  // 这里的*是用来定义指针变量 
  printf("a=%c\n", *pa);   //  这里打印的是地址指向的那个变量值 
  printf("b=%d\n", *pb);  //  这里的*是用来取值 
  printf("sizeof pa = %d\n", sizeof(pa)); 
  printf("sizeof pb = %d\n", sizeof(pb)); 
  printf("a的地址:%p\n", pa);  //  这里打印的是指针存放的地址 
  printf("b的地址:%p\n", pb);
  return 0;
}

结果:

a=H
b=521
sizeof pa = 8
sizeof pb = 8
a的地址:000000000062FE0F
b的地址:000000000062FE08

数组名其实是数组的第一个元素的地址

# include <stdio.h>
int main(){
  char str[3];  
  printf("请输入最帅的博主:");
  scanf("%s", str);
  printf("最帅博主的地址是:%p\n", str);  //  查看数组名的地址
  printf("最帅博主的地址是:%p\n", &str[0]); //  取数组第一个元素的地址
  return 0;
}

运行结果:

请输入最帅的博主:远方的星
最帅博主的地址是:000000000062FE10
最帅博主的地址是:000000000062FE10

那数组其它位置的地址与首地址是什么关系呢?

# include <stdio.h>
int main(){
  char a[] = "Hello";
  int b[] = {1, 2, 3, 4, 5};
  float c [] = {1.1, 2.2, 3.3, 4.4};
  double d[] = {1.1, 2.2, 3.3, 4.4};
  printf("a[0] -> %p, a[1] -> %p, a[2] -> %p\n", &a[0], &a[1], &a[2]);
  printf("b[0] -> %p, b[1] -> %p, b[2] -> %p\n", &b[0], &b[1], &b[2]);
  printf("c[0] -> %p, c[1] -> %p, c[2] -> %p\n", &c[0], &c[1], &c[2]);
  printf("d[0] -> %p, d[1] -> %p, d[2] -> %p\n", &d[0], &d[1], &d[2]);
  return 0;
}

运行结果:

a[0] -> 000000000062FE10, a[1] -> 000000000062FE11, a[2] -> 000000000062FE12
b[0] -> 000000000062FDF0, b[1] -> 000000000062FDF4, b[2] -> 000000000062FDF8
c[0] -> 000000000062FDE0, c[1] -> 000000000062FDE4, c[2] -> 000000000062FDE8
d[0] -> 000000000062FDC0, d[1] -> 000000000062FDC8, d[2] -> 000000000062FDD0

这里是十六进制,数组a,char类型,依次增加1个字节;数组b,int类型,依次增加4个字节;数组c,float类型,依次增加4个字节;数组d,double类型,依次增加8个字节。与下表相对,不难发现其规律,依次增加的大小为数据类型占的字节数。


  • 64位编译器

image.png

2、指针的运算

当指针指向数组元素的时候,我们可以对指针变量进行加减运算,这样做的意义相当于指向距离指针所在位置向前或向后第n个元素。

  • 例:
# include <stdio.h>
int main(){
  char a[] = "Hello";
  char *p = a;
  printf("*p = %c, *(p+1) = %c, *(p+3) = %c\n", *p, *(p+1), *(p+3));
  return 0;
}

结果:

*p = H, *(p+1) = e, *(p+3) = l

由此可见,加减代表的是方向。p+1指的是,指向数组的下一个元素,那么p-1,便是指向数组的上一个元素。

  • 这里使用指针间接访问数组元素的方法叫做指针法。

3、指针和数组的区别

  • 数组名只是一个地址,而指针是一个左值。

lvalue(左值):C语言中指用于识别或定位一个存储位置的标识符,左值必须是可改变的。

4、指针数组

  • 例如:
int  *p[5]
  • 指针数组是一个数组,每个数组元素存放一个指针变量。

在指向字符指针的时候非常好用。

# include <stdio.h>
int main(){
  char *p[] = {
    "选择了就不必害怕",
    "努力了就一定成功",
    "加油,陌生人!" 
  };
  int i;
  for (i = 0;i < 3;i++){
    printf("%s\n", p[i]);  //  这里是不能写成*p[i]的,这样的意思是指向数组的第一个元素地址
  }
  return 0;
}

5、数组指针

  • 例如:
int (*p)[5]
  • 数组指针是一个指针,它指向的是一个数组

例:这里使用数组指针完成的

# include <stdio.h>
int main(){
  int temp[5] = {1, 2, 3, 4, 5};
  int (*p2)[5] = &temp;  //指向数组的指针 
  int i;
  for (i = 0;i < 5;i++)
  {
    printf("%d\n", *(*p2 + i));
  }
  return 0;
}

运行结果:

1
2
3
4
5
  • 比较:使用(普通)指针
    (数组名其实是数组的第一个元素的地址)
    结果上虽然相等,但本质上是有所区别的。。。
# include <stdio.h>
int main(){
  int temp[5] = {1, 2, 3, 4, 5};
  int *p = temp;  //指向数组的第一个元素的指针 
  int i;
  for (i = 0;i < 5;i++)
  {
    printf("%d\n", *(p + i));
  }
  return 0;
}


6、指针和二维数组

arry指向的是包含5个元素的数组的指针

把一个地址的值取出来,称之为 “解引用”

解引用到底是什么,查了一些资料,是否真的是 “取指针指向的地址的内容” 还不得而知,还需要日后慢慢的沉淀。

解引用:

*(array+i) == array[i]
*(*(array+i)+j) == array[i][j]
*(*(*(array+i)+j)+k) == array[i][j][k]

下标索引的形式都可以转换为使用指针间接索引的形式。

  • 例:
# include <stdio.h>
int main(){
  int array[4][5] = {0};
  int i,j,k = 0;
  for (i=0;i<4;i++)
  {
    for(j=0;j<5;j++)
    {
      array[i][j] = k++;
    }
  }
  printf("*(array+1):%p\n", *(array + 1));//array+1的和进行解引用 
  printf("array[1]:%p\n", array[1]);//array第二行首地址 
  printf("&array[1][0]:%p\n", &array[1][0]);//对 array[1][0]取地址 
  printf("**(array+1):%d\n", **(array+1));//对array+1的和的解引用进行解引用 
  printf("array[1][0]:%d\n", array[1][0]);//array[1][0]的值 
  return 0;
}

运行结果:

*(array+1):000000000062FDC4
array[1]:000000000062FDC4
&array[1][0]:000000000062FDC4
**(array+1):5
array[1][0]:5

7、void指针

  • void指针又称通用指针,就是可以指向任意类型的数据。任何类型的指针都可以赋值给void指针。
  • 例:验证一下:
# include <stdio.h>
int main(){
  int num = 521;
  int *p1 = &num;
  char *p2 = "天道酬勤";
  void *p3;
  p3 = p1;
  printf("p1:%p, p3:%p\n", p1, p3);
  p3 = p2;
  printf("p2:%p, p3:%p\n", p2, p3);
  return 0;
}

结果如下:

p1:000000000062FE04, p3:000000000062FE04
p2:0000000000404000, p3:0000000000404000

得到的结果是一样的,由此可见,是可以将任意数据类型的指针赋值给void的指针。

但如果要根据void指针,取出其内容,最好进行数据类型的强制转换。

  • 根据上述例子进行修改:
# include <stdio.h>
int main(){
  int num = 521;
  int *p1 = &num;
  char *p2 = "天道酬勤";
  void *p3;
  p3 = p1;
  printf("p3:%d\n", *(int *)p3);//(int *)p3是进行类型强制转换,再加一个*,进行解引用,才能取出值 
  p3 = p2;
  printf("p3:%s\n", (char *)p3);//由于字符串的特殊性,就不需要进行进引用了 
  return 0;
}

结果如下:

p3:521
p3:天道酬勤

8、NULL指针

NULL指针又称空指针,它有啥作用呢?

  • 有利于养成良好的编程习惯
    当不清楚要将指针初始化为哪个地址时,可以将它初始化为NULL;在对指针进行解引用时,先检查该指针是否为NULL。可以有效避免野指针

9、指向指针的指针

  • 好处:

①避免重复分配内存

②只需要进行一处修改

10、指向常量的指针

之前见过用宏定义来定义常量,其实还可以利用const关键字。

  • const修饰的数据类型是指常类型,常类型的变量或对象的值是不能被更新的,即只能读不能重写。

例:

# include <stdio.h>
int main(){
  const float pi = 3.14;
  printf("%f\n",pi); 
  pi = 3.1415;
  return 0;
}

编译之后,会直接报错!

[Error] assignment of read-only variable 'pi'
// [错误]只读变量'pi'的赋值

由此可以得到关于指向常量的指针的小结论:

指向常量的指针

  • 指针可以修改为指向不同的常量
  • 指针可以修改为指向不同的变量
  • 可以通过解引用来读取指针指向的数据
  • 不可以通过解引用修改指针指向的数据


11、常量指针

①、指向非常量的常量指针

  • 指针自身不可以被修改
  • 指针指向的值可以被修改

例如:

# include <stdio.h>
int main(){
  int num = 521;
  const int cnum = 1024;
  int * const p = &num;
  *p = 7758;//对指针指向的值进行修改
  printf("*p:%d\n", *p);
  return 0;
}

输出为:

*p:7758

将上述例子加以修改:

# include <stdio.h>
int main(){
  int num = 521;
  const int cnum = 1024;
  int * const p = &num;
  p = &cnum;//对指针本身进行修改
  printf("*p:%d\n", *p);
  return 0;
}

编译结果,系统会报错!

[Error] assignment of read-only variable 'p'


②、指向常量的常量指针

  • 指针自身不可以被修改
  • 指针指向的值也可以不被修改

(和上例类似)


四、未完待续


指针真的是C的灵魂所在,欲速则不达,花的时间也比较长,不断练习才能更好地理解。

这一阶段的笔记就先记录到这里,但学C的道路仍然还很漫长。。。

路漫漫其修远兮,吾将上下而求索。

大家一起加油吧!

相关文章
|
6月前
|
存储 人工智能 算法
【C语言】自学终极笔记
【C语言】自学终极笔记
106 0
|
3月前
|
测试技术 C语言 C++
【C语言刷题训练——6】鹏哥C语言刷题训练营笔记,含代码讲解改进
【C语言刷题训练——6】鹏哥C语言刷题训练营笔记,含代码讲解改进
|
3月前
|
存储 C语言
【C语言】鹏哥C语言刷题训练营——第5节内容笔记(含代码全面分析和改进,讲解)
【C语言】鹏哥C语言刷题训练营——第5节内容笔记(含代码全面分析和改进,讲解)
|
5月前
|
C语言
|
5月前
|
C语言
|
6月前
|
C语言
C语言(指针详解)重点笔记:指针易错点,都是精华
C语言(指针详解)重点笔记:指针易错点,都是精华
75 0
|
存储 编译器 C语言
C语言笔记第03章:数组(四)
C语言笔记第03章:数组
96 0
|
编译器 C语言 索引
C语言笔记第03章:数组(一)
C语言笔记第03章:数组
104 0
|
6月前
|
NoSQL Redis C语言
|
C语言
C语言初阶 牛客网刷题笔记(将持续更新..)
C语言初阶 牛客网刷题笔记(将持续更新..)
C语言初阶 牛客网刷题笔记(将持续更新..)