C语言笔记(翁恺版本)(六)

简介: C语言笔记(翁恺版本)(六)

第九周:指针


9.1指针


9.1-1取地址运算


运算符&


  1. scanf(("%d",&i);里的&
  2. 获得变量的地址,它的操作数必须是变量   int i;printf("%x",&i);
  3. 地址的大小是否与int相同取决于编译器   int i;printf("%p",&i);


&不能取的地址


  1. &不能对没有地址的东西取地址,需要有明确的变量
  2. &(a+b)可以
  3. &(a++)不行
  4. &(++a)不行


试试这些&


  1. 变量的地址      被分配在相邻紧挨着的地方
  2. 相邻的变量的地址
  3. &的结果的sizeof
  4. 数组的地址
  5. 数组单元的地址
  6. 相邻的数组单元的地址


9.1-2 指针


scanf


  1. 如果能够将取得的变量的地址传递给一个函数,能否通过这个地址在那个函数内访问这个变量? 可以
  2. scanf("%d",&i);
  3. scanf()的原型应该是怎么样的?我们需要一个参数能保存别的变量的地址,如何表达能够保存地址的变量?


指针


就是保存地址的变量


  1. int i
  2. int* p = &i;
  3. int* p,q;
  4. int  *P,q;
  5. 这个*不管是靠近int还是靠近p,最后实际指向的都是p


指针变量


变量的值是内存的地址


  1. 普通变量的值是实际的值
  2. 指针变量的值是具有实际值的变量的地址


作为参数的指针


  1. void f(int *p);
  2. 在被调用的时候得到了某个变量的地址:int i = 0;f(&i);
  3. 在函数里面可以通过这个指针访问外面的这个i


访问那个地址上的变量*


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


*左值之所以叫左值


  1. 是因为出现在赋值号左边的不是变量,而是值,是表达式计算的结果;
  2. a[0] = 2; *p = 3;
  3. 是特殊的值,所以叫做左值


指针的运算符&*


1.互相反作用


  1. &yptr  ->(&yptr) -> *(yptr的地址) ->得到那个地址上的变量 ->yptr
  2. &yptr  ->&(yptr) ->&(y) ->得到y的地址,也就是yptr ->yptr


传入地址


为什么 int i;scanf("%d",i);编译没有报错?


但是运行会报错,只是刚好凑巧你传进去的整数大小跟地址大小一样,编译会拿你传进去的整数当做地址去运行,将整数传到了不该传的地方去了,所以运行就一定会出错


指针应用场景一


交换两个变量的值

void swap(int *pa, int *pd)
{
    int t = *pa;
    *pa = *pb;
    *pb = t;
}


指针应用场景二


  1. 函数返回多个值,某些值就只能通过指针返回
  2. 传入的参数实际上是需要保存带回的结果的变量


指针应用场景二b


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


指针最常见的错误


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


9.1-3指针与数组


传入的数组成了什么?

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;
}


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


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


数组参数


以下四种函数原型是等价的


  1. int sum(int*ar,int n);
  2. int sum(int*,int);
  3. int sum(int*ar[],int n);
  4. int sum(int[],int);


数组变量是特殊的指针


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


  1. int a[10];int*P = a; //无需用&取地址
  2. 但是数组的单元表达的是变量,需要用&取地址
  3. a == &a[0]


[]运算符可以对数组做,也可以对指针做:


  1. p[0]<==>a[0]


*运算符可以对指针做,也可以对数组做:


  1. *a = 25;


数组变量是const的指针,所以不能被赋值


  1. int a[] <==> int *const a = ....


9.1-4 指针与const


指针可以是const,值也可以是const


指针是const


表示一旦得到某个变量的地址,不能再指向其他变量


  1. int *const q = &i;   //q是const   q指向i这个事实不能被改变
  2. *q = 26; //OK
  3. q++;error


所指是const


表示不能通过这个指针去修改那个变量(并不能使得那个变量成为const)


  1. const int*p = &i;
  2. p = 26; //error!(p)是const
  3. i = 26; //ok
  4. p = &j;  //ok


判断哪个被const了的标志是const在*的前面还是后面


  1. int i;
  2. const int*  p1 = &i;
  3. int const*  p2 = &i;
  4. int  *const p3 = &i
  5. 2跟3其实是一样的,指针所指向的东西不可被修改
  6. 4则是表示指针不可被修改


转换


  1. 总是可以把一个非const的值转化成const的


void f(const int* x);
int a = 15;
f(&a);//ok
const int b = a;
f(&b);//ok
b = a + 1;//error!


当要传递的参数的类型比地址大的时候,这是常用的手段:既能用比较少的字节数传递值给参数,又能避免函数对外面的变量的修改


const数组


  1. const int a[] = {1,2,3,4,5,6,};
  2. 数组变量已经是const的指针了,这里的const表明数组的每一个单元都是const int
  3. 所以必须通过初始化进行赋值


保护数组值


因为把数组传入函数时传递的是地址,所以那个函数内部可以修改数组的值


为保护数组不被函数破坏,可以设置参数为const


  1. int sum(const int a[],int length);


9.2指针运算


9.2-1 指针是可计算的


1+1=2?


  1. 给一个指针加1表示要让指针指向下一个变量


int a[10];
int *P = a;
*(p+1)——>a[1];


  1. 如果指针不是指向一片连续分配的空间,如数组,则这种运算没有意义


指针计算


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


  1. 给指针加、减一个整数(+,+=,-,-=)
  2. 递增递减(++/--)
  3. 两个指针相减


*p++


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


指针比较


  1. <,<=,>,>=,!=都可以对指针做
  2. 比较它们在内存中的地址
  3. 数组中的单元的地址肯定是线性递增的


0地址


  1. 当然你的内存中有0地址,但是0地址通常是个不能随便碰的地址
  2. 所以你的指针不应该具有0值
  3. 因此可以用0地址来表示特殊的事情:

  1. 返回的指针是无效的
  2. 指针没有被真正的初始化(先初始化为0)
  1. NULL是一个预定定义的符号,表示0地址

  1. 有的编译器不愿意你用0地址来表示0地址


指针的类型


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


指针的类型转换


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

  1. 计算时与char*相同(但不相通)
  1. 指针也可以转换类型

  1. intp = &i;voidq = (void*)p;
  1. 这并没有改变p所指向的变量的类型,而是让后人用不同的眼光通过p看他所指的变量

  1. 我不在当你时int啦,我认为你就是个void!


用指针来做什么?


  1. 需要传入较大的数据时用作参数
  2. 传入数组后对数组做操作
  3. 函数返回不止一个结果

  1. 需要用函数来修改不止一个变量
  1. 动态申请的内存...


9.2-2动态内存分配


输入数据


  1. 如果输入数据时,先告诉个数,然后再输入,要记录每个数据
  2. int a = (int)malloc(n*sizeof(int));


malloc


#include
void*malloc(size_t size);
  1. 向malloc申请的空间是以字节为单位的
  2. 返回的结果是void*,需要类型转换为自己需要的类型(比如int)

  1. (int)malloc(nsizeof(int))


没空间了?


  1. 如果申请失败则返回0,或者叫做NULL
  2. 可以自己测测看自己电脑系统能给多少空间


free()


  1. 把申请得来的空间还给"系统"
  2. 申请过的空间,最终都应该要还的

  1. 出来混的,迟早都是要还的
  1. 只能还申请来的空间的首地址
  2. free(0)?


free()常见问题


  1. 申请了没free->长时间运行内存逐渐下降

  1. 新手:忘了
  2. 不够老的老手:找不动合适的free的时机
  1. free过了再free
  2. 地址变过了,直接去free


9.2-3 函数间传递指针


好的模式


  1. 如果程序中要用到动态分配的内存,并且会在函数之间传递,不要让函数申请内存后返回给调用者
  2. 因为十有八九调用者会忘了free,或找不到合适的时机来free
  3. 好的模式是让调用者自己申请,传地址进函数,函数再返回这个地址出来


在同一个地方malloc和free


除非函数的作用就是分配空间,否则不要再函数中malloc然后传出去用


函数返回指针


  1. 返回指针没问题,关键是谁的地址?

  1. 本地变量(包括参数)?函数离开后这些变量就不存在了,指针所指的是不能用的内存
  2. 传入的指针?没问题
  3. 动态申请的内存?没问题
  4. 全局变量->以后会解释


函数返回数组


  1. 如果一个函数的返回类型是数组,那么它实际返回的也是数组的地址
  2. 如果这个数组是这个函数的本地变量,那么回到调用函数那里,这个数组就不存在了
  3. 所以只能返回(和返回指针是一样的)

  1. 传入的参数:实际就是在调用者那里
  2. 全局变量或者动态分配的内存


第十周:字符串


10.1-1字符数组


  1. char word[] = {'H','e','l','l','o','!'};


这(指1)不是C语言的字符串,因为不能用字符串的方式做计算


  1. char word[] = {'H','e','l','l','o','!','\0'};

word[0]

H

word[1]

e

word[2]

l

word[3]

l

word[4]

o

word[5]

word[6]

\0


字符串


  1. 以0(整数0)结尾的一串字符

  1. 0或者'\0'是一样的,但是和'0'不同
  1. 0标志字符串的结束,但它不是字符串的一部分

  1. 计算字符串长度的时候不包含这个0
  1. 字符串以数组的形式存在,以数组或者指针的形式访问

  1. 更多的是以指针的形式


string.h里有很多处理字符串的函数


  1. C语言的字符串是以字符数组的形态存在的

  1. 不能用运算符对字符串做运算
  2. 通过数组的方式可以遍历字符串
  1. 唯一特殊的地方是字符串字面量可以用来初始化字符数组
  2. 以及标准库提供了一系列字符串函数


10.1-2字符串变量


  1. char *str = "Hello";
  2. char word[] = "hello";
  3. char line[10] = "Hello";结尾编辑器会自动补0,多占据一个位置
  4. "Hello会被编译器变成一个字符数组放在某处,这个数组的长度是6,结尾还有表示结束的0"
  5. 两个相邻的字符串常量会被自动连接起来


字符串常量


char* s = "Hello,world";


  1. s是一个指针,初始化为指向一个字符串常量

  1. 由于这个常量所在的地方,所以实际上s是const char *s,但是由于历史的原因,编译器接受不带const的写法
  2. 但是试图对s所指的字符串做写入会导致严重的后果
  3. 如果有两处相同的地方,指针会同时指向同一处地方,所以指针必须是只读的
  1. 如果需要修改字符串,应该用数组:

  1. char s[] = "Hello,world!";
  2. 这个数组跟指针的区别就是,指针指向某一处地方,而数组则表示就在我这里
  3. 会将放在不可写的"Hello,world!"数组内容拷贝到你的s那里去


当我们需要一个字符串的时候,指针还是数组?


  1. char*str = "Hello";
  2. char word[] = "Hello";
  3. 数组:这个字符串在这里

  1. 作为本地变量空间自动被回收
  1. 指针:这个字符串不知道在哪里

  1. 处理参数
  2. 动态分配空间
  3. 用在只需要只读的,不打算去往里面写入东西的。表达函数的参数。
  1. 如果要构造一个字符串->数组
  2. 如果要处理一个字符串->指针


char*是字符串?


  1. 字符串可以表达为char*的形式
  2. char*不一定是字符串

  1. 本意是指向字符的指针,可能指向的是字符的数组(就像int*一样)
  2. 只有当char*所指向的字符数组有结尾的0,才能说它所指的是字符串


10.1-3字符串输入输出


  1. char*t = "title";
  2. char*s;
  3. s = t;
  4. 并没有产生新的字符串,只是让指针s指向了t所指的字符串,对s的任何操作就是对t做的
  5. char string[8];
  6. scanf("%s",string);
  7. printf("%s\n",string);
  8. scanf读入一个单词(到空格、tab或回车为止),但scanf这样是不安全的,因为不知道要读入内容的长度


安全的输入


  1. char string[8];
  2. scanf("%7s",string);
  3. 在%s中间可以加入数字来让编译器知道需要限制在多少字符范围内(或者说最多允许读入的字符数量),比如%7s,限制在7个字符范围(超出部分就不会读入了)


常见错误


  1. char*string;
  2. scanf("%s",string);
  3. 以为char("%s".string);
  4. 以为char*是字符串类型,定义了一个字符串类型的变量string就可以直接使用了

  1. 由于没有对string初始化为0,所以不一定每一次运行都出错(实际上这是错误的,指针用错了,没有指向一个确定的地方)


空字符串


  1. char buffer[100] = "";

  1. 这是一个空的字符串,buffer[0] == '\0'
  1. char buffer[] = "";

  1. 这个数组的长度只有1!所以后面放不下任何的字符串


10.1-4字符串数组以及程序参数


字符串数组


  1. char **a

  1. a是一个指针,指向另一个指针,那个指针指向一个字符(串)
  1. char a[][]

  1. 一个错误的二维数组,因为没有说明几列,所以会报错
  2. 可以修改成char a[],本质上就相当于a[0]--->char


程序参数


  1. int main(int argc,char const*argv[])
  2. argv[0]是命令本身

  1. 当使用Unix的符号链接时,反应符号链接的名字


#include <stdio.h>
int main(int argc, char const *argv[])
{
    int i;
    for( i = 0; i < argc; i++){
        printf("%d:%s\n",i,argv[i]);
    }
    return 0;
}

10.2-1单字符输入输出,用putchar和getchar


putchar


  1. int putchar(int c);
  2. 向标准输出写一个符号
  3. 返回写了几个字符,EOF(-1)表示写失败


getchar


  1. int getchar(void);
  2. 从标准输入读入一个字符(跟scanf的区别是scanf可以一次性读入多个字符)
  3. 返回类型是int是为了返回EOF(-1)

  1. window-->Ctrl-Z
  2. Unix-->Ctrl-D(返回EOF)
  3. Ctrl-C会将shell与实际上显示的的通道关闭掉了
#include <stdio.h>
int main(int argc,char const *argv[])
{
    int ch;
    while( (ch = getchar()) != EOF ){
        putchar(ch);
    }
    printf("EOF\n");
    return 0;
}

在我们输入的东西(在键盘上敲出来的东西被称为行编辑的工作)的时候,那些都会被暂时放在shell里(类似缓冲区域),当我们按下回车之后,才会发送到实际上显示的地方上


10.2-(2-6)字符串函数strlen


string.h


  1. strlen

  1. size_t strlen(const char *s);
  2. 返回s的字符串长度(不包括结尾的0)
  1. strcmp

  1. int strcmp(const char s1,const char s2);
  2. 比较两个字符串,返回:    0:s1==s2,1:s1>s2,-1:s1
  1. strcpy

  1. charstrcpy(charrestrict dst,const char *restrict src);
  2. 把src的字符串拷贝到dst


  1. restrict表明src跟dst不重合

  1. 返回dst


  1. 为了能链起代码

  1. 复制一个字符串


  1. chardst = (char)malloc(strlen(src)+1); //之所以加一是因为结尾会自带\0,所以需要多一个位置
  2. strcpy(dst,src);
  1. strcat

  1. charstrcat(char restrict s1,const char *restrict s2);
  2. 把s2拷贝到s1的后面,接成一个长的字符串
  3. 返回s1
  4. s1必须具有足够的空间
  1. strchr

  1. 字符串中找字符
  2. charstrchr(const char s,int c);表示从左边找过来
  3. charstrrchr(const chars,int c);表示从右边找过来
  4. 返回NULL则表示没有找到
  5. 如何寻找第二个?寻找第二个的方法:


    p = strchr(p+1,'l');


    printf("%s\n",p);


  1. strstr

  1. 在字符串中寻找单个字符的


  1. char strstr(const char s1,const char *s2);

  1. 在寻找的时候忽略大小写


  1. charstrcasestr(const char s1,const char *s2);


#include <stdio.h>
#include <string.h>
size_t mylen(const char* s)
{
    int cnt = 0;
    int idx = 0;
    while(s[idx] != '\0' ){
        idx++;
        cnt++;
    }
    return cnt;
}
int main(int argc,char const *argv[])
{
    char line[] = "Hello";
    printf("strlen=%lu\n",mylen(line));
    printf("sizeof=%lu\n",sizeof(line));
    return 0;
}
#include <stdio.h>
#include <string.h>
int mycmp( const char* s1, const char* s2)
{
    //int idx = 0;
    //while( s1[idx] == s2[idx] && s1[idx]!='\0' ){
//  idx ++;
//}
    while( *s1 == *s2 && *s1 != '\0'){
        s1++;
        s2++;
    }
    return *s1 - *s2;
}
int main(int argc, char const *argv[])
{
    char s1[] = "abc";
    char s2[] = "abc";
    printf("%d\n",mycmp(s1,s2));
    printf("%d\n",'a','A');
    return 0;
}
#include <stdio.h>
#include <string.h>
char* mycpy(char* dst, const char* src)
{
    int idx = 0;
    while(src[idx] != "\0"){
        dst[idx] == src[idx];
        idx++;
    }
    dst[idx] = '\0';
 //   char* ret = dst;   
 //   方法1:while(*src != '\0'){
 //       *dst++ = *src++;
 //   }
    //方法2:while(*dst++ = *src++);   嗯,没了,就一行直接替换掉了方法1,还有比方法1代码还有更长的版本我没有记录
 // *dst = '\0'; 这是指针的写法
    return dst;
}
int main(int argc, char const *argv[])
{
    char s1[] = "abc";
    char s2[] = "abc";
    strcpy(s1,s2);
    return 0;
}


安全问题


  1. strcpy跟strcat都可能出现安全问题

  1. 如果目的地没有足够的空间?
  2. 建议是尽量不要去使用他
  1. 安全版本

  1. char strncpy(charrestict dst, const char* restrict src,size_t n);
  2. char strncat(charrestict s1, const char* restrict s2,size_t n);
  3. size_t n表示最多能够接受多少个n个字符,多了就直接掐掉
  4. int strncmp(const char s1,const char s2,size_t n);
  5. 这个则表示最多能够判断几个字符,超出则不判断


#include <stdio.h>
#include <string.h>
int main(int argc, char const *argv)
{
    char s[] = "hello";
    char *p = strchr(s,'l');
    printf("%s\n",p);  //结果为llo
    //寻找第二个的方法:
    //p = strchr(p+1,'l');
    //printf("%s\n",p);结果为lo
    return 0;
}
---------------------------------------------------
   将起选取的内容拷贝到其他地方的方法
    int main(int argc, char const *argv)
{
    char s[] = "hello";
    char *p = strchr(s,'l');
    char *t = (char*)malloc(strlen(p)+1);
    strcpy(t,p);
    printf("%s\n",t);
    free(t); // 申请来的空间记得释放掉哦
    //这是将llo的字符拷贝走了
    return 0;
}
-------------------------------------------------------
      将起选取的内容拷贝到其他地方的方法2版本
    int main(int argc, char const *argv)
{
    char s[] = "hello";
    char *p = strchr(s,'l');
    char c = *p;
    *p = '\0';
    char *t = (char*)malloc(strlen(s)+1);
    strcpy(t,s);
    printf("%s\n",t);
    free(t); // 申请来的空间记得释放掉哦
    //这是将he的字符拷贝走了
    return 0;
}
目录
相关文章
|
编译器 C语言
C语言习题----不同版本的差别
C语言习题----不同版本的差别
|
存储 人工智能 算法
【C语言】自学终极笔记
【C语言】自学终极笔记
251 0
|
前端开发 C语言
C语言04---第一个HelloWorld(vc版本)
C语言04---第一个HelloWorld(vc版本)
|
测试技术 C语言 C++
【C语言刷题训练——6】鹏哥C语言刷题训练营笔记,含代码讲解改进
【C语言刷题训练——6】鹏哥C语言刷题训练营笔记,含代码讲解改进
666 0
|
存储 C语言
【C语言】鹏哥C语言刷题训练营——第5节内容笔记(含代码全面分析和改进,讲解)
【C语言】鹏哥C语言刷题训练营——第5节内容笔记(含代码全面分析和改进,讲解)
780 0
|
搜索推荐 C语言 C++
【排序算法】C语言实现归并排序,包括递归和迭代两个版本
【排序算法】C语言实现归并排序,包括递归和迭代两个版本
739 1
|
C语言
C语言(指针详解)重点笔记:指针易错点,都是精华
C语言(指针详解)重点笔记:指针易错点,都是精华
342 0
|
C语言
【C语言/数据结构】排序(快速排序及多种优化|递归及非递归版本)
【C语言/数据结构】排序(快速排序及多种优化|递归及非递归版本)
272 0