0%

1.基本内容

什么是指针

  • 在计算机中所有数据都存储在内存单元中,而每个内存单元都有一个对应的地址, 只要通过这个地址就能找到对应单元中存储的数据.
  • 由于通过地址能找到所需的变量单元,所以我们说该地址指向了该变量单元。将地址形象化的称为“指针”
  • 内存单元的指针(地址)和内存单元的内容是两个不同的概念。

什么是指针变量

在C语言中,允许用一个变量来存放其它变量的地址, 这种专门用于存储其它变量地址的变量, 我们称之为指针变量。

1
2
3
char ch = 'a';
char *p; // 一个用于指向字符型变量的指针
p = &ch;

*表示这是一个指针变量

1
2
3
4
5
int *p;
p = 250; // 错误写法
/****************************/
int *p;
*p=&a; //错误写法
  • 多个指针变量可以指向同一个地址。

对指针变量的理解

指针变量本身的作用是储存一个用户定义的变量的地址,但是其本身作为一个额外的变量,也有自己的地址,且这个地址不等于其储存的变量的地址;

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main()
{
int a=1;
int *c;
c=&a;
printf("指针变量c自身的地址为:%d",&c);//6487568
printf("指针变量c存储单元内存储的内容(也就是a的地址)为:%d",c);//6487576
printf("变量a的地址为:%d",a);//6487576
}
// c代表c内存放的地址
// &c代表c自身的地址
// *c代表c内存放的地址中所存储的值
// (c+1)代表c内存放的地址+1*类型长度
// *c+1代表c内存放的地址中所存储的值本身+1
// *(c+1)代表c内存放的地址+1*类型长度后得到的地址中存储的值

取地址运算符(&)与解引用运算符(*)

  • 取地址运算符:&x,其作用为返回操作对象的地址

  • 解引用运算符:*x,其作用是返回x中存储的地址内存储的值

    例: int a,*p1;p1=&a;则此时*p1代表的就是变量a的内容,而不是a的地址

    这也就是为什么使用printf("%d",*p);时是用p而不是p了,因为 p才是代表a的值,p则存储的变量a的地址

    综上:除开定义时,其余时间*p等同a,p等同&a

    在定义时,int *p1=&a;也是合法的且作用与上相同,个人理解为是一种指针格式的“声明”(?)这也解释了当某个函数参数定义为为int *a时实际使用时传入的也是&的取地址变量了(个人理解)

2.一些用法

二级指针

  • 如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。也称为“二级指针”。

    1
    2
    3
    4
    5
    6
    char c = 'a';
    char *cp;
    cp = &c;
    char **cp2;
    cp2 = &cp;
    printf("c = %c", **cp2);// c=a

指针访问数组元素

  • 在指针指向数组元素时,允许以下运算:

    • 加一个整数(用+或+=),如p+1
    • 减一个整数(用-或-=),如p-1
    • 自加运算,如p++,++p
    • 自减运算,如p–,—p

    如果指针变量p已指向数组中的一个元素,则p+1指向同一数组中的下一个元素,p-1指向同 一数组中的上一个元素。【*(p+1)】

    (p+N)在地址上的变化为p内存放的地址+数组元素类型长度*N

    • 其对p所有的基本运算均符合上述规律

指针与字符串

定义字符串除了使用字符数组存放之外,还可以通过字符串指针的方式指向字符串。

1
2
// 数组名保存的是数组第0个元素的地址, 指针也可以保存第0个元素的地址
char *str = "abc"
  • 使用注意事项

    • 此方法不可修改字符串内容

      1
      2
      3
      4
      //使用字符数组来保存的字符串是保存栈里的,保存栈里面东西是可读可写,所有可以修改字符串中的的字符
      //使用字符指针来保存字符串,它保存的是字符串常量地址,常量区是只读的,所以我们不可以修改字符串中的字符
      char *str = "lnj";
      *(str+2) = 'y'; // 错误
    • 不能够直接接收键盘输入

      1
      2
      3
      4
      // 错误的原因是:str是一个野指针,他并没有指向某一块内存空间
      // 所以不允许这样写如果给str分配内存空间是可以这样用 的
      char *str;
      scanf("%s", str);

指向函数的指针

函数在内存中占据空间,因此也有起始地址。

格式:返回值类型 (*指针变量名)(形参);

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <string.h>
int sum(int a,int b)
{
return a+b;
}
int main()
{
int (*p)(int x,int y);
p=sum;
printf("%d",(*p)(1,2)); //打印3
return 0;
}
  • 函数指针有以下几点特点:

    • 当把一个函数名(注意是名字,不带输入参数)作为一个值使用时,该函数会自动转换成指针,指向该函数的内存地址,因此在对指针初始化时取地址符可选:

      1
      2
      p=sum;
      p=&sum;//都正确
    • 当使用指针调用函数时,解引用也是可选的:

      1
      2
      printf("%d",(*p)(1,2));
      printf("%d",p(1,2));//都输出3
  • 当函数指针作形参时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "stdio.h"
#include "math.h"
void tran(double (*a)(double a1),double (*b)(double b1),double x);
int main()
{
double x=3.141592;
tran(sin,cos,x);
return 0;
}
void tran(double(*a)(double a1),double(*b)(double b1),double x)
{
printf("%f\n",(*a)(x/2));
printf("%f",(*b)(x/2));
}

调用函数时直接传入函数名即可。

同理的,也可以不声明参数为指针(因为会自动转变为指针)

1
2
3
4
5
void tran(double a(double a1),double b(double b1),double x)
{
printf("%f\n",(*a)(x/2));
printf("%f",(*b)(x/2));
}

通过形参影响实参

我们都知道,形参值的改变不能改变对应实参的值,把数据从被调用函数返回到调用函数的唯一途径时通过return。但如果通过传送地址给被调用函数,再在被调用函数中改变地址中存储的值,也就使通过形参改变对应实参的值成为了可能。

例如:交换值

1
2
3
4
5
6
7
8
9
10
11
12
void swap(int *x, int *y)
{
int c; // 因为c只是用来存放地址内保存的值,所以为一般变量
c=*x;*x=*y;*y=c; //将a、b地址中保存的值互换、地址不变
}
int main()
{
int a=1,b=2;
swap(&a,&b);//将a、b的地址传给形参x、y,此时指针变量x、y内保存的就是a、b的地址
printf("%d %d",a,b);//此时a、b地址内保存的值已经互换
return 0;
}

但如果swap函数定义成如下,又会怎么样呢

1
2
3
4
5
void swap(int *x, int *y)
{
int *c;
c=x;x=y;y=c;
}

如果这样定义函数,则程序的流程为:①调用swap函数时,x内保存的为a的地址,y内保存的为b的地址;②在swap函数中,x内保存的地址改变为b的地址,y内保存的地址改变为为a的地址;③swap函数调用结束,x,y的内存被释放;④此时a、b变量自身的地址并没有任何改变,改变的只用形参x、y所保存的地址,因此无法起到交换作用

二维数组地址详解+数组指针(重要!!!!!)

详见二维数组复习 | 小董的BLOG (gitee.io)

结构体指针

详见数据结构_链表 | 小董的BLOG (gitee.io)结构体复习 | 小董的BLOG (gitee.io)

2022.11.20:我是按照自己的理解写的,所以思路那些可能会有点奇怪,我感觉自己写的也不是很精练。。凑合看看吧

字符串

基本概念

字符串是位于双引号中的字符序列,因此在内存中以“\0”结束,所占字节比实际多一个。

(‘\0’是字符串结束的标志,因此数组中没有‘\0’时只是字符数组,且元素中间不能出现’\0’)

初始化

当数组元素个数大于存储字符内容时, 未被初始化的部分默认值是0。

1
2
3
 char name[] = "c\0ool";
printf("name = %s\n",name);
//输出结果: c
  • 注意点:
    • 对一个字符串数组, 如果不做初始化赋值, 必须指定数组长度;
    • 字符串数组最后一个字符的位置要留给字符串的结尾标示‘\0’;
    • 当用scanf函数输入字符串时,字符串中不能含有空格,否则将以空格作为串的结束符。

正确的赋值方法:

1
2
3
4
5
6
char str[10]="string";

char str[]="string";

char str[10];
scanf("%s",&str);//或scanf("%s",str);

错误的赋值方法:

1
2
char str[10];
str="string"; //这是因为数组名是一个地址常量,是不可被赋值的

※常用函数※

使用字符串函数应包含头文件”string.h”

(1)输出函数

  • 格式: puts(字符数组名)
  • 功能:把字符数组中的字符串输出到显示器。即在屏幕上显示该字符串。

  • 优点:

    • 自动换行;
    • 可以是数组的任意元素地址。
  • 缺点
    • 不能自定义输出格式
1
2
char ch[] = "dhk";
puts(ch); //输出结果: dhk
  • 当使用printf且输出格式为&s时,输出项直接为数组名,而不是数组元素

    1
    2
    3
    char s[10]="dhk";
    printf("%s",s);//正确
    printf("%s",s[0]);//错误

(2)输入函数

  • 格式: gets (字符数组名)
  • 功能:从标准输入设备键盘上输入一个字符串。

当输入的字符串中含有空格时,输出仍为全部字符串。说明gets函数并不以空格作为字符串输入结束的标志,而只以回车作为输入结束。这是与scanf函数不同的。

  • 若要使用scanf一个字符串类型,则需要注意scanf会自动把空格当作分隔符

(3)计算长度

1.sizeof()

数值包含’\0’,且当对象为字符串数组时,计算的是整个数组分配空间的长度

2.strlen()

测量字符串实际长度,不包含;\0’。

(4)连接函数

  • 格式: strcat(字符数组名1,字符数组名2) /strncat(~,~,num);
  • 功能:把字符数组2中的字符串连接到字符数组1 中字符串的后面,并删去字符串1后的串标志 “\0”。本函数返回值是字符数组1的首地址

(5)比较函数

  • 格式: strcmp(字符数组名1,字符数组名2)

  • 功能:按照ASCII码顺序比较两个数组中的字符串,并由函数返回值返回比较结果。

    • 字符串1=字符串2,返回值=0;
    • 字符串1>字符串2,返回值1;
    • 字符串1<字符串2,返回值-1。

    需要注意的是,比较顺序是从左至右依次比较每一个字符的ASCII码大小,只要出现不同字符就只按照当前字符大小比较,与之后字符大小就无关了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include <stdio.h>
    #include <string.h>
    int main(){
    char str1[10] ="acb";
    char str2[10] ="abc";
    printf("%d\n", strcmp(str1,str2));
    //输出1,由于'c'>'b',与第三个及以后的字符大小无关
    return 0;
    }

    感觉不是很好用,就拿来比较是否相同就行。

(6)拷贝函数

  • 格式: strcpy(char dest,char src); / strncpy(char dest,const char src,size_t num);
  • 功能:将参数src字符串拷贝至参数dest所指的地址。
  • 返回值:返回参数dest的字符串起始地址。
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <string.h>
int main(){
char str1[10] ="aaaaaa";
char str2[10] = "bbb";
strncpy(str1,str2,2);
printf("%s\n", str1); //结果为"bbaaaa"
//当num长度大于srt2中字符串长度时仅会打印bbb(结果可能与编译器相关)
return 0;
}

strcpy会覆盖原来的整个字符串,而strncpy只会覆盖n个字节原来的字符串,剩下的保留(当n超长时应该是默认使用strcpy)

(7)检索函数

1. char strstr(const char str,const char* substr)

  • 功能:检索子串在字符串中首次出现的位置

  • 返回值:返回字符串str中第一次出现子串substr的地址;如果没有检索到子串,则返回NULL。

    2. char strchr(const char str,char c)

  • 功能:查找字符串中第一次出现c的位置。

  • 返回值:规律同strstr()。

    3. char strrchr(const char str,char c)

  • 功能:查找字符串中最后一次出现c的位置。

  • 返回值:同上。

(8)其余函数

字符转换

1
2
int tolower(int c):转化字符为小写字母; 
int toupper(int c):转化字符为大写字母;

整型转换

1
int atoi(const char* str)

​ 返回转换后的整型数。

字符串大小写转换

1
2
strlwr(const char* str);//转小写
strupr(const char* str);//转大写

关于hexo本地图片无法上传

1.安装插件

1
npm install https://github.com/CodeFalling/hexo-asset-image --save

2.配置插件(可以先跳过看看)

打开/node_modules/hexo-asset-image/index.js,将内容更换为下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
'use strict';
var cheerio = require('cheerio');

// http://stackoverflow.com/questions/14480345/how-to-get-the-nth-occurrence-in-a-string
function getPosition(str, m, i) {
return str.split(m, i).join(m).length;
}

var version = String(hexo.version).split('.');
hexo.extend.filter.register('after_post_render', function(data){
var config = hexo.config;
if(config.post_asset_folder){
var link = data.permalink;
if(version.length > 0 && Number(version[0]) == 3)
var beginPos = getPosition(link, '/', 1) + 1;
else
var beginPos = getPosition(link, '/', 3) + 1;
// In hexo 3.1.1, the permalink of "about" page is like ".../about/index.html".
var endPos = link.lastIndexOf('/') + 1;
link = link.substring(beginPos, endPos);

var toprocess = ['excerpt', 'more', 'content'];
for(var i = 0; i < toprocess.length; i++){
var key = toprocess[i];

var $ = cheerio.load(data[key], {
ignoreWhitespace: false,
xmlMode: false,
lowerCaseTags: false,
decodeEntities: false
});

$('img').each(function(){
if ($(this).attr('src')){
// For windows style path, we replace '\' to '/'.
var src = $(this).attr('src').replace('\\', '/');
if(!/http[s]*.*|\/\/.*/.test(src) &&
!/^\s*\//.test(src)) {
// For "about" page, the first part of "src" can't be removed.
// In addition, to support multi-level local directory.
var linkArray = link.split('/').filter(function(elem){
return elem != '';
});
var srcArray = src.split('/').filter(function(elem){
return elem != '' && elem != '.';
});
if(srcArray.length > 1)
srcArray.shift();
src = srcArray.join('/');
$(this).attr('src', config.root + link + src);
console.info&&console.info("update link as:-->"+config.root + link + src);
}
}else{
console.info&&console.info("no src attr, skipped...");
console.info&&console.info($(this));
}
});
data[key] = $.html();
}
}
});


3.修改根配置文件

post_asset_folder: true

4.引入图片(可能无法使用markdown语法 “!“)

1
{% asset_img 1.jpg This is an image %} 

“1.jpg”为图片名字

注:上传至github时png格式无法使用,最好用jpg格式