If you give someone a program , you will frustrate them for a day ; if you teach them how to program , you will frustrate them for a lifetime , but for you , maybe I just this person .我是你们的圣彬老师 , 希望接下来的一段时间能够和大家一起探讨、学习 !
C语言是一门面向过程的语言 , 相比现在比较流行的面向对象 , 它更多的让我们理解 , 体会其中的奥义 , 在学习C语言过程中 , 大家要多思考 , 多理解 , 多敲 ,往往大家会有几个症状 : - 上课能听懂 , 但是就是敲不出来,简称难产! 那是因为你听懂我的中国话 , 并没有听懂我的编程思想
- 写作业代码难产 , 怎么破 :
- 尝试把思路用伪代码的方式写出来
- 写不出来 : 没有思路
- 能写出来 : 有思路,但是基本语法基础不扎实
- 对着伪代码写代码 :
- 如果能写出来说明只是思路不清晰
- 如果还是不行说明基础语法也不懂 所以要对症下药 , 但是唯一的药就是 先模仿 , 先学习 , 增强代码感觉 , 再敲出来 , 输入输出相结合.
最后 , 我想对哪些已经开始自学的同学说 , 可能你们会困惑 , 不懂 , 不理解 , 不会应用 , 甚至不知所云 . 可实际上 , 无论学什么 , 都是努力才可以学到真东西, 只有真正掌握技术的人 , 才有可能去享用它 , 如果你中途放弃了 , 之前所有的努力和付出都会变得没有价值 . 学习游泳难吗 ? 掌握英语口语难吗 ? 可能难 , 但在掌握了的人眼中 , 这根本不算什么 , "就这么回事呀" 只要你相信自己一定可以学得会 , 学得好 , 最终的结果一定是 , 你对着别人说 : "学编程---就这么回事 !
附件是Markdown的笔记 , 大家可以直接下来看, 有目录
指针入门直接获取/修改变量的值 : - int num = 10;
- num = 20;
- 请问num的值是否有改变 ? 有
复制代码使用指针可以 间接的获取/修改变量的值 : - int num = 10;
- num = 20;
- 请问num的值是否有改变 ? 有
复制代码认清指针 : - int num = 10;
- int *p = &num
- *p = 20;
- 为什么 int *p = 地址 ?
- 而 *p = 值 ?
- 在定义的时候 :
- int *p; 的 * 是类型说明符 说明定义的这个变量是一个指针变量
- 在使用的时候 :
- *p 的 * 是操作符 p所指向的地址对应的内容
- 再解释一次 :
- int *p; //定义一个指针变量
- p = &num ; // p指向num变量的地址 / p这个指针变量存放的是num的地址
- *p ; // p指向的地址 (num的地址) *一下 , 拿到地址对应的内容
复制代码
二级指针What : 指针的指针就是二级指针 / 存放指针的地址的指针变量就是二级指针 - int num = 10;
- int *p = #
- int **p1 = &p; // 这个就是二级指针
- p1 ----> 指针变量p的地址
- *p1 ------> 指针变量p的地址所对应的内容啊 ? ---> &num
- **p1 ------> *p / *&num ----> 10
复制代码
指针与数组数组指针 / 指针数组
- xxx指针 : 指向xxx的指针
- 数组指针 : 指向数组的指针
- 函数指针 : 指向函数的指针
- 结构体指针 : 指向结构体的指针
- 字符串指针 : 指向字符串的指针
- 常量指针 : 指向常量的指针
- * 指针数组 :
- 是一个指针还是数组 ? 数组 ! 什么数组 ? 数组元素是地址(指针)的数组
- 学习方法 :
- 要学习数组指针 , 就得了解数组名的本质
- 我们说过使用指针就是间接的获取或者修改变量的值
- 那么数组指针也是同样道理
- int main(){
- int arr[5] = {1,2,3,4,5};
- int *p = arr;
- //3中方法遍历数组元素
- //1 . 下标法
- for(int i = 0 ; i < 5 ; i++){
- printf("%d",arr[i]);
- }
- //2. 数组名发
- for(int i = 0 ; i < 5 ; i++){
- printf("%d",*(arr+i);
- }
- //3. 指针法
- for(int i = 0 ; i < 5 ; i++){
- printf("%d",*(p+i); // *(p+i) == *p++
- }
- return 0;
- }
复制代码
总结 : 如果你能用以上3总方法遍历数组元素 , 那么这个总结由你们来写
整型数组 对比 指针数组
- //int类型的数组 , 数组名为arr , 元素个数为3
- int arr[3] = {1,2,3};
- //定义3个整型变量
- int num1 = 10 , num2 = 20 , num3 = 30;
- //指针数组
- int *pArr[3] = {&num1,&num2,&num3};
- for(int i = 0;i < 3 ;i++){
- printf("%d\n",*pArr[i]); //除了这个方法还有吗?
- /*
- pArr 数组名 : 指向的第一个元素的地址 : 那么问题来了 是不是就是&num1的 ? 肯定不是
- 正确的是 :
- pArr 数组名 : 指向的第一个元素的地址 : &pArr[0]
- *pArr ----> * &pArr[0] ---> pArr[0] ---> &num1
- **pArr ----> * &num1 ---> num1
- */
- printf("%d\n",**(pArr+i));
- // *pArr[i] == **(pArr+i)
- }
复制代码
二维数组指针what : 指向二维数组的指针 - int arr[2][3] = {1,2,3,4,5,6};
- int (*pa)[3] = arr;
- for(int i = 0 ; i < 2 ; i++){
- for(int j = 0 ; j < 3 ; j++){
- //直接遍历 : 下标法遍历 / 数组名法遍历
- printf("%d",arr[i][j]);
- printf("%d",*(*(arr+i)+j));
- //间接遍历 : 二维数组指针遍历
- printf("%d",*(*(pa+i)+j));
- }
- }
复制代码
指针数组进阶
- //二维数组
- int arr[2][3] = {1,2,3,4,5,6};
- //指针数组
- int *pArr[2] = {arr[0],arr[1]};
- // 使用二维数组直接遍历
- for(int i = 0 ; i < 2 ; i++){
- for(int j = 0; j < 3 ; j++){
- printf("%d\t",arr[i][j]);
- }
- printf("\n");
- }
- // 使用指针数组间接遍历
- for(int i = 0;i < 2;i++){
- for(int j = 0 ; j< 3 ; j++){
- printf("%d\n",*(pArr[i]+j);
- // pArr[i] ---> arr[i]
- // pArr[i]+j ---> arr[i]+j
- // *(pArr[i]+j) ----> *(arr[i]+j) ---> arr[i][j]
- // pArr ---> &pArr[0]
- // *(pArr+i) ---> pArr[i]
- }
- // *(pArr[i]+j) == *(*(pArr+i)+j)
- }
复制代码
指针与函数的关系- 使用一个函数交换两个变量的值
- void changeNum(int *pNum1,int *pNum2){
- int temp = *pNum1;
- *pNum1 = *pnum2;
- *pNum2 = temp;
- }
- int main(){
- int num1 = 10 , num2 = 20;
- changeNum(&num1,&num2);
- // 此刻 , 你再打印num1 , num2 的值 , 看看是否有被改变 ? 答案是肯定的
- return 0;
- }
复制代码
指针函数- 指针函数 : 这是一个函数 , 返回值是指针(地址) 的函数
- #include <stdio.h>
- int *getArr(){
- int arr[5] = {1,2,3,4,5};
- return arr;
- }
- int main(int argc, const char * argv[]) {
- int *p = getArr();
- for (int i = 0; i < 5; i++) {
- printf("%d\n",*(p+i));
- }
- return 0;
- }
- 大家觉得此处能正常输出吗 ? 答案是否定的
- int arr[5] ; 这个是一个局部变量 , 作用域结束后 , 会被释放 , 所以我们保存此地址 , 是没有意义的 , 地址对应的内容已经被释放了
复制代码
我这里有2个解决方案 : 方法一 :
- 1)
- int arr[5] = {1,2,3,4,5};这个地方 加 static修饰
- static int arr[5] = {1,2,3,4,5}; 这样 , 此数组就是静态区的, 也就是全局区 , 生命周期会延长至程序结束.
复制代码 方法二 :
- 2) 使用malloc方式 , 创建堆区的动态内存空间
- int *p = (int *)malloc(sizeof(int)*4);
- *(p) = 1;
- *(p+1) = 2;
- *(p+2) = 3;
- *(p+3) = 4;
- *(p+4) = 5;
- 使用此方法别忘了用完以后使用free()函数手动销毁堆区申请的空间欧
复制代码
函数指针- 函数指针 What : 这到底是怎么鬼 ? 指向函数的指针 , 是一个指针
这个跟以后我们学习OC中的block有关联 , 我们要先尽量理解一下 先带大家回忆一下函数 : - 无参无返回值函数
- 有参无返回值函数
- 无参有返回值函数
- 有参有返回值函数
无参无返回值函数 :需求 : 打印一行星星
- #include <stdio.h>
- void printf_Star(){
- printf("******\n");
- }
- int main(int argc, const char * argv[]) {
- //定义函数指针
- //先定义 , 后赋值
- void (*p)();
- //赋值 此处一定是函数名
- p = printf_Star;
- //使用函数指针间接调用函数
- p();
- return 0;
- }
- 输出结果 : ******
复制代码
有参无返回值函数 :需求 : 打印3行星星
- void printf_Star(int n){
- for (int i = 0; i < n; i++) {
- printf("******\n");
- }
- }
- int main(int argc, const char * argv[]) {
- //定义函数指针
- //定义的同时初始化 此处一定是函数名
- void (*p)(int) = printf_Star;
- //使用函数指针间接调用函数
- p(3);
- return 0;
- }
- 输出结果 :
- ******
- ******
- ******
复制代码
无参有返回值函数 :需求 : 返回一个1000以内的随机数
- #include <stdio.h>
- #include <stdlib.h>
- int getRandNum(){
- int randNum = arc4random_uniform(1000);
- return randNum;
- }
- int main(int argc, const char * argv[]) {
- //定义函数指针
- //定义的同时初始化 此处一定是函数名
- int (*p)() = getRandNum;
- //使用函数指针间接调用函数
- printf("随机数 : %d\n",p());
- return 0;
- }
复制代码
有参数有返回值函数 :需求 : 传2个值, 返回它们的商
- float division(int num1,int num2){
- // int / int 得到的还是int 所以需要强制类型装换
- return num1 / (float)num2;
- }
- int main(int argc, const char * argv[]) {
- //定义函数指针
- //定义的同时初始化 此处一定是函数名
- float (*p)(int num1,int num2) = division;
- //使用函数指针间接调用函数
- printf("商为 : %.2f\n",p(67,3));
- return 0;
- }
复制代码
指针与字符串在讲这个指针与字符串之前 , 大家要先了解一下内存的5大区域 :- 栈区
- 堆区
- 全局区(静态区) 又称之为静态区 , 其中已经初始化的全局(静态)变量在数据段(数据区) , 未初始化的全局(静态)变量在BSS段
- 常量区
- 代码区
当然内存的区域远不止这么简单 , 比我们认知的复杂的多 , 所以C语言内存的5大区域也可能有不同说法 , 但是都大同小异 , 但是要确保准确性 验证
- #include <stdio.h>
- #include <stdlib.h>
- // 全局变量 在全局区
- int a = 1;
- int main(int argc, const char * argv[]) {
- //局部变量在栈区
- int num = 10;
- //malloc函数申请动态内存空间,堆区
- int *p = malloc(sizeof(int)*4);
- //static修饰的局部变量为静态变量 , 在静态区(全局区)
- static int b = 10;
- // "apple"在常量区
- char *str = "apple";
- printf("栈区: %p\n堆区: %p\n静态区:%p\n常量区:%p\n全局区:%p\n代码区:暂不作验证,OC阶段讲解\n",&num,p,&b,str,&a);
- return 0;
- }
- 栈区: 0x7fff5fbff7cc
- 堆区: 0x1001015b0
- 静态区:0x100001024
- 常量区:0x100000f44
- 全局区:0x100001020
- 代码区:暂不作验证,OC阶段讲解
- 栈区一般都是0x7fff开头的 , 堆区 : 0x100.... 后6位有值 , 之前2个0
- 大家注意看静态区和全局区 尾号 : 1024 ---> 1020 差4个字节 , 其实静态区和全局区是一个区
- 全局区一般都是 0x10000xxxx 后四位有值 , 之前是4个0
- 常量区 : 一般都是0x100000xxx 后三位有值 , 之前都是5个0
复制代码
学习指针与字符串就3个问题 : - 1. 用字符数组的方式
- char str[6] = "apple";
- 2. 用指针的方式
- char *str = "apple";
复制代码
- 前者保存在栈区 , 数组元素为6个 , 字符串长度为5
- 后者保存在常量区 , 指针变量str指向了常量区的"apple"的地址,字符串长度为5
复制代码
- 值得注意的问题
- 前者字符串内容可改 , 比如说 str[3] = 'p' , 是可以的 , 后者 *(str+3) = 'p' , 是错误的 , 因为常量区的内容是可读不可写的 , 不可改
- char str[5] = "apple";
- str[4] = 'p';
- printf("%s\n",str); // 这是没问题的
- ----------------------------------------
- char *str = "apple";
- *(str+4) = 'p';
- printf("%s\n",str); // 这个是错误的
复制代码
- char str[5];
- scanf("%s",str);
- 此处正确没问题
- ----------------------------------
- char *str;
- scanf("%s",str);
- 此处错误 , 野指针错误 , str没有任何指向
- ------------------------------------
- 解决办法 :
- char *str = (char *)malloc(sizeif(char)*10);
- scanf("%s",str);
- 此处正确没问题
复制代码
const与指针搭配的用法
- const int num = 1;
- num++; //错误
- num = 20; //错误
- Cannot assign to variable 'num' with const-qualified type 'const int'
复制代码
const 在 * 前面 : 指针的指向可以变 , 地址对应的值不可变
- int num = 10;
- const int *p = #
- int const *p = #
- 指针的指向可以变 , 地址对应的值不可变
- p = &num2; //这个可以
- 但是 *p = 30; // 这个错误 Read-only variable is not assignable
复制代码
const 在 * 后面 : 指针的指向不可以变 , 地址对应的值可变
- int num = 10;
- int * const p = #
- 指针的指向不可以变 , 地址对应的值可变
- p = &num2 ; //这个错误 Read-only variable is not assignable
- *p = 10; // 这个可以
- 理解: 1.
- const int *p; 常量指针 指向常量的指针 所以值不能变,指向可变
- int const *p;
- int * const p; 指针常量 这是个常量 指向不可变,值可变
复制代码
最后 , 如果还是没法懂 , 就强行记忆 : - const 在 * 的左侧 指向可变,值不能变
- const 在 * 的右侧 指向不可变 值可变
- const 在 * 的两侧 指向和值都不可以变
结构体与指针What : 指向结构体变量地址的指针 回顾结构体 : 区分结构体类型 , 结构体变量 , 结构体别名 结构体类型
- //结构体类型 : 这个仅仅只是定义的类型
- struct Car{
- int lunzi; //轮子
- char *pp; //品牌
- };
复制代码
结构体变量
- //结构体类型 : 这个仅仅只是定义的类型
- struct Car{
- int lunzi; //轮子
- char *pp; //品牌
- }car;
- 变量名为 : car
复制代码
结构体类型起别名
- typedef struct Car{
- int lunzi; //轮子
- char *pp; //品牌
- }iCar;
- 对结构体类型 :struct Car 起了别名: iCar
复制代码
到此我们就要用指针给结构体结合使用了
只有结构体指针才有的特殊用法 : p->结构体的成员变量
- struct Car{
- int lunzi;
- char *pp;
- }car;
- struct Car *p = &car;
- p->lunzi = 10;
- p->pp = "BMW";
- printf("lunzi = %d,pp = %s\n",p->lunzi,p->pp);
- // p->lunzi == (*p).linzi
复制代码
|