BACK:编程之基 --- C语言基础大全 II  


流程一览

  • Eighth Week 数组
    • 1. 数组
    • 2. 数组运算
      • 2.1 素数
      • 2.2 二维数组
  • Ninth Week 指针
    • 1. 指针
    • 2. 指针应用
    • 3. 常见错误
    • 4. 数组和指针
    • 5. 指针和const
    • 6. 指针运算
    • 7. 动态分配内存

Eighth Week 数组


1. 数组

定义数组:

  • <类型> 变量名称[元素数量];

    • int grades[100];
      
    • double weight[20];
      
  • 元素数量必须是整数

  • C99之前:元素数量必须是编译时刻确定的字面量

数组:

  • 是一种容器(放东西的东西),特点是:

    • 其中所有的元素具有相同的数据类型;
    • 一旦创建,不能改变大小
    • *(数组中的元素在内存中是连续依次排列的)

有效的下标范围:

  • 编译器和运行环境都不会检查数组下标是否越界,无论是对数组单元做读还是写
  • 一旦程序运行,越界的数组访问可能造成问题,导致程序崩溃
    • segmentation fault
  • 但是也可能运气好,没造成严重的后果
  • 所以这是程序员的责任来保证程序只使用有效的下标值:[0,数组的大小-1]

  • int a[0];  // 可以存在,但是无用
    

统计个数示例:

const int number = 10;  // 数组的大小,c99
int x;
int count[number];  // 定义数组
int i;for (i=0; i<number; i++) {  // 初始化数组count[i] = 0;
}
scanf("%d", &x);
while (x!=-1) {if (x>=0 && x<=9) {count[x]++;  // 数组参与运算}scanf("%d", &x);
}
for (i=0; i<number; i++) {  // 遍历数组输出printf("%d:%d\n", i, count[i]);
}

2. 数组运算

集成初始化时的定位:

int a[10] = {  // c99 ONLY[0] = 2, [2] = 3, 6,
};
  • 用[n]在初始化数据中给出定位
  • 没有定位的数据接在前面的位置后面
  • 其他位置的值补零
  • 也可以不给出数组大小,让编译器算
  • 特别适合初始数据稀疏的数组

数组的大小:

  • sizeof给出整个数组所占据的内容的大小,单位是字节

    sizeof(a)/sizeof(a[0])
    
  • sizeof(a[0])给出数组中单个元素的大小,于是相除就得到了数组的单元个数

  • 这样的代码,一旦修改数组中初始的数据,不需要修改遍历的代码

数组的赋值:

int a[] = {2, 4, 6, 7, 1, 3};
int b[] = a;
  • 数组变量本身不能被赋值
  • 要把一个数组的所有元素交给另一个数组,必须采用遍历
for (i=0; i<length; i++) {b[i] = a[i];
}

  • 数组作为函数参数时,往往必须再用另一个参数来传入数组的大小
  • 数组作为函数的参数时:
    • 不能在[]中给出数组的大小
    • 不能再利用sizeof来计算数组的元素个数!
/*
找出key在数组a中的位置
@param key 要寻找的数字
@param a 要寻找的数组
@param length 数组a的长度
@return 如果找到,返回其在a中的位置;如果找不到则返回-1
*/
int search(int key, int a[], int length);int main(void) {int a[] = {2, 4, 6, 7, 1, 3, 5, 9};int x;int loc;printf("请输入一个数字:");scanf("%d", &x);loc = search(x, a, sizeof(a)/sizeof(a[0]));if (loc != -1) {printf("%d在第%d个位置上\n", x, loc);} else {printf("%d不存在\n", x);}return 0;
}int search(int key, int a[], int length) {int ret = -1;int i;for (i=0; i<length; i++) {if (a[i] == key) {ret = i;break;}}return ret;
}

2.1 素数

判断是否能被已知的且<x的素数整除:

int isPrime(int x, int knownPrimes[], int numberOfKnownPrimes) {int ret = 1;int i;for (i=0; i<numberOfKnownPrimes; i++) {if (x%knownPrimes[i] == 0) {ret = 0;break;}}return ret;
}int main(void) {const int number = 100;int prime[number] = {2};int count = 1;int i = 3;while (count < number) {if (isPrime(i, prime, count)) {prime[count++] = 1;}i++;}for (i=0; i<number; i++) {printf("%d", prime[i]);if ((i+1)%5) printf("\t");else printf("\n");}return 0;
}

构造素数表:

  • 预构造n以内的素数表

    1. 令x为2
    2. 将2x、3x、4x直至ax<n的数标记为非素数
    3. 令x为下一个没有被标记为非素数的数,重复2;直到所有的数都已经尝试完毕

  • 构造n以内(不含)的素数表

    1. 开辟prime[n],初始化其所有元素为1,prime[x]为1表示x是素数
    2. 令x=2
    3. 如果x是素数,则对于 (i=2; x*i<n; i++) 令 prime[i*x]=0
    4. 令x++,如果x<n,重复3,否则结束
#include <stdio.h>int main() {const int maxNumber = 25;int isPrime[maxNumber];int i;int x;for (i=0; i<maxNumberl i++) {isPrime[i] = 1;}for (x=2; x<maxNumber; i++) {if (isPrime[x]) {for (i=2; i*x<maxNumber; i++) {isPrime[i*x] = 0;}}}for (i=2; i<maxNumber; i++) {if (isPrime[i]) {printf("%d\t", i);}}printf("\n");return 0;
}

2.2 二维数组

二维数组的初始化:

int a[][5] = {{0, 1, 2, 3, 4},{2, 3, 4, 5, 6},
};
  • 列数是必须给出的,行数可以由编译器来数
  • 每行一个{},逗号分隔
  • 最后的逗号可以存在,有古老的传统
  • 如果省略,表示补零
  • 也可以用定位(*C99 ONLY)


Ninth Week 指针


1. 指针

只能变量取地址

  • 就是保存地址的变量
int i;
int* p = &i;
int* p, q;
int *p, q;  // 与上一行所表达一样,p指针,q是int类型变量

访问那个地址上的变量*

  • *是一个单目运算符,用来访问指针的值所表示的地址上的变量
  • 可以做右值也可以做左值
    • int k = *p;
      
    • *p = k+1;
      

*左值之所以叫左值

  • 是因为出现在赋值号左边的不是变量,而是值,是表达式计算的结果;

    • a[0] = 2;
      
    • *p = 3;  // 赋值时,p代表的是地址,\*p代表的是地址所指的值。
      
  • 是特殊的值,所以叫做左值

2. 指针应用

指针应用场景一:

  • 交换两个变量的值
void swap(int *pa, int *pb) {int t = *pa;*pa = *pb;*pb = t;
}

指针应用场景二a:

  • 函数返回多个值,某些值就只能通过指针返回

    • 传入的参数实际上是需要保存带回的结果的变量

指针应用场景二b:

  • 函数返回运算的状态,结果通过指针返回
  • 常用的套路是让函数返回特殊的不属于有效范围内的值来表示出错:
    • -1或0(在文件操作会看到大量的例子)
  • 但是当任何数值都是有效的可能结果时,就得分开返回了
    • 后续的语言(C++,Java)采用了异常机制来解决这个问题

3. 常见错误

定义了指针变量,还没有指向任何变量,就开始使用指针

4. 数组和指针

传入函数的数组成了什么?

  • 函数参数表中的数组实际上是指针

    • sizeof(a) == sizeof(int*)
      
    • 但是可以用数组的运算符[]进行运算

int isPrime(int x, int knownPrimes[], int numberOfKnownPrimes) {int ret = 1;int i;for (i=0; i<numberOfKnownPrimes; i++) {if (x%knownPrimes[i] == 0) {ret = 0;break;}}return ret;
}

数组变量是特殊的指针:

  • 数组变量本身表达地址,所以

    • int a[10]; int *p=a;  // 无需用&取地址
      
    • 但是数组的单元表达的是变量,需要用&取地址

    • a == &a[0]
      
  • []运算符可以对数组做,也可以对指针做:

    • p[0] <==> a[0]
  • *运算符可以对指针做,也可以对数组做:

    • *a = 25;
      
  • 数组变量是const的指针,所以不能被赋值

    • int a[] <==> int *cosnt a=…

5. 指针和const

指针是const:

  • 表示一旦得到了某个变量的地址,不能再指向其他变量(可以改所指地址变量的值,不可以改所指地址)

    • int* const q = &i;  // q是const
      
    • *q = 26;  // OK
      
    • q++;  // ERROR
      

所指是const:

  • 表示不能通过这个指针去修改那个变量(并不能使得那个变量成为const。变量本身是可以修改的,但是不可以通过那个指针去修改)

    • const int *p = &i;
      
    • *p = 26;  // ERROR!(\*p)是const
      
    • i = 26;  // OK
      
    • p = &j;  // OK
      

转换:

  • 总是可以把一个非const的值转换成const的
void f(const int *x);  // 表示接受的值,我不会去更改它,你放心!int a = 15;
f(&a);  // ok
const int b = a;  // 无论传入f的值是不是const都可以
f(&b);  // okb = a + 1;  // Error
  • 当要传递的参数的类型比地址大的时候,这是常用的手段:既能用比较少的字节数传递值给参数,又能避免函数对外面的变量的修改

6. 指针运算

1+1=2?

  • 给一个指针加1表示要让指针指向下一个变量
int a[10];
int *p = a;
// *(p+1)——>a[1]
  • 如果指针不是指向一片连续分配的空间,如数组就是连续分配的空间,则这种运算没有意义

指针计算:

  • 这些算术运算可以对指针做:

    • 给指针加、减一个整数(+,+=,-,-=)
    • 递增递减(++/–)

指针相减是指相隔了多少个数

*p++

  • 取出p所指的那个数据来,完事之后顺便把p移到下一个位置去
  • *的优先级虽然高,但是没有++高
  • 常用于数组类的连续空间操作
  • 在某些CPU上,这可以直接被翻译成一条汇编指令

指针比较:

  • <,<=,==,>,>=,!=都可以对指针做(地址大小的比较)
  • 比较它们在内存中的地址
  • 数组中的单元的地址肯定是线性递增的

0地址

  • 当然你的内存中有0地址,但是0地址通常是个不能随便碰的地址
  • 操作系统会给每个进程一片虚拟的地址空间,都含有虚拟的从0地址开始的连续空间
  • 所以你的指针不应该具有0值
  • 因此可以用0地址来表示特殊的事情:
    • 返回的指针是无效的
    • 指针没有被真正初始化(可以先初始化为0,但是并没有意义的值。可以当你使用这个变量的时候系统会崩溃,表明这个值为0或没有初始化)
  • NULL是一个预定定义的符号,表示0地址
    • 有的编译器不愿意你用0来表示0地址

指针的类型:

  • 无论指向什么类型,所有的指针的大小都是一样的,因为都是地址
  • 但是指向不同类型的指针是不能直接相互赋值的
  • 这是为了避免用错指针

指针的类型转换:

  • void*表示不知道指向什么东西的指针

    • 计算时与char*相同(但不相通)
  • 指针也可以转换类型
int *p = &i;
void *q = (void*)p;
  • 这并没有改变p所指的变量的类型,而是让后人用不同的眼光通过p看它所指的变量

    • 我不再当你是int啦,我认为你就是个void!

  1. 指针的类型就是指针
    指针大小和内存的编址方式有关,只是恰好与无符号整形大小相同
    他的大小是4字节(32位)就是类似0012ff78(16进制 32位)
    注:如果你的电脑是64位电脑那么他的大小就是8字节!
  2. 指针是用来保存内存地址的
    内存有按32位编制和按64位编制之分
  3. 为什么要给指针定义类型呢?
    只有为指针定义类型
    才能知道指针所指向的变量的大小
    例如: int*p;和 double*q;
    那么读取*p时 就要从地址p开始读取4字节
    读取*q时就要从地址q开始读取8字节

用指针来做什么:

  • 需要传入较大的数据时用作参数
  • 传入数组后对数组做操作
  • 函数返回不止一个结果
    • 需要用函数来修改不止一个变量
  • 动态申请的内存

7. 动态分配内存

malloc:

#include <stdlib.h>
void* malloc(size_t size);
  • 向malloc申请的空间的大小是以字节为单位的

  • 返回的结果是void*,需要类型转换为自己需要的类型

    • (int*)malloc(n*sizeof(int))
      

没空间了?

  • 如果申请失败则返回0,或者叫做NULL
  • 你的系统能给你多大的空间?
#include <stdio.h>
#include <stdlib.h>int main(void) {void *p;int cnt = 0;while (p=malloc(100*1024*1024)) {cnt++;}printf("分配了%d00MB的空间\n", cnt);return 0;
}

free():

  • 把申请得来的空间还给“系统”
  • 申请过的空间,最终都应该要还
    • 出来混,迟早都是要还的
  • 只能还申请来的空间的首地址

常见问题:

  • 申请了每free ——> 长时间运行内存逐渐下降

    • 新手:忘了
    • 老手:找不到合适的free的时机
  • free过了再free
  • 地址变过了,直接去free

BACK:编程之基 --- C语言基础大全 IV  

编程之基 --- C语言基础大全 III相关推荐

  1. 编程之基 --- C语言基础大全 IV

      BACK:编程之基 --- C语言基础大全 III   流程一览 Tenth Week 字符串 1. 字符串 2. 字符串函数 Eleventh Week 结构类型 1. 枚举 2. 结构 3. ...

  2. c语言基础编程题文库,C语言基础编程题资料.doc

    C语言基础编程题资料.doc |5-1 求幂之和 15 分本题要求编写程序,计算 sum21 22 23 cdots 2nsum2122232n.可以调用 pow 函数求幂.输入格式输入在一行中给出正 ...

  3. arcgispython编程案例_ArcGIS Python编程案例(1)-Python语言基础

    Python支持大部分在其他语言中出现的编程结构.在本章内容中,我们将会涉及到许多Python支持的编程结构.我们将会首先介绍如何创建一个新的Python脚本以及如何修改已有脚本.之后我们将深入了解P ...

  4. C语言基础大全(基于千锋教育超详细教程)

    C语言基础 1.第一个c语言程序 #include <stdio.h> int main() {printf("hello world");return 0; } [外 ...

  5. 编程入门:C语言基础知识全网超全不用到处找了!

    你背或者不背,干货就在那里,不悲不喜 你学或者不学,编程就在那里,不来不去 听到这话的你是否略感扎心? 01基础知识 计算机系统的主要技术指标与系统配置. 计算机系统.硬件.软件及其相互关系. 微机硬 ...

  6. c语言 异或_编程入门:C语言基础知识全网超全不用到处找了!(文末附清单)

    你背或者不背,干货就在那里,不悲不喜 你学或者不学,编程就在那里,不来不去 听到这话的你是否略感扎心? 01基础知识 1. 计算机系统的主要技术指标与系统配置. 2. 计算机系统.硬件.软件及其相互关 ...

  7. 【Java编程进阶】Java语言基础入门篇

    从今天开始,就要写Java编程进阶从入门到精通系列的文章啦!希望大家学习完都可以找到心仪的工作,在自己热爱的岗位上闪闪发光! 推荐学习专栏:Java 编程进阶之路[从入门到精通] 文章目录 一. 第一 ...

  8. C语言基础知识入门和C语言入门基础知识大全

    一.C语言基础知识入门 C语言一出现,就以其丰富的功能.强大的表达能力.灵活性.方便性和广泛的应用,在世界范围内迅速普及和普及.C语言不仅高效而且可移植.它可以用来开发应用软件.驱动程序.操作系统等. ...

  9. 2023年C语言基础知识入门(大全)

    一.C语言基础知识入门 C语言一经出现就以其功能丰富.表达能力强.灵活方便.应用面广等特点迅速在全世界普及和推广.C语言不但执行效率高而且可移植性好,可以用来开发应用软件.驱动.操作系统等.C语言也是 ...

最新文章

  1. socket 服务器浏览器与服务器客户端实例
  2. 使用1个盘三个5G分区创建12G逻辑卷
  3. 阿里云Elasticsearch的X-Pack:机器学习、安全保障和可视化
  4. nikon n150在电脑中不显示里面的图片的解决方法
  5. 一条开启勇士王朝的短信
  6. Hibernate 异常:“@OneToOne or @ManyToOne on XXX references an unknown entity: XXX”
  7. 也谈UpdatePanel与UrlRewrite一起work时出现Form Action属性的问题
  8. Windows解决net Framerwork 3.5无法安装的问题
  9. linux中cd中文意思,linux中cd ~和!!是什么意思?
  10. spring静态资源配置
  11. three 星空穿梭,常见的星空星星移动
  12. 没有基础可以学画插画吗?怎么学?
  13. 孙溟㠭创作篆刻作品(稻)纪念袁隆平老先生
  14. react点击后高亮显示
  15. 服务器cpu型号后面的字母,Intel 至强 E3服务器CPU后缀解读
  16. 杰奇linux伪静态,nginx 杰奇 nginx 伪静态规则
  17. bert-pytorch版源码详细解读
  18. jsp的实质是什么?
  19. Guid.NewGuid().ToString()的几种格式
  20. 关于镜像文件和虚拟光驱

热门文章

  1. Android监听手机屏幕解锁和判断屏幕状态
  2. 0G(無線技術最早的拓荒時代)
  3. 使用 CodeMirror 打造在线代码编辑器
  4. stm32f1单片机上用FFT测量信号频率(高精度、过程详细)
  5. 安装、配置Domino Admin V11
  6. js实现浏览器中的前进、后退按钮
  7. 移动H2-3获取超管密码
  8. mxnet编译android,mxnet to ncnn
  9. python date range_pandas.date_range()用法
  10. 我国水土流失面积有所减少,水土保持监管能力和现代化水平也显著提升[图]