在Java语言中,是不存在指针这个名词的,而在C/C++中,是存在指针的,指针就是一个变量,它指向一个地址。
1、指针
在项目中,使用指针时,一定要赋初始值,否则该指针就是一个野指针。
int* p1 = 0;
例如下面的代码:
int a = 10;
int* p2 = &a;
printf("a的地址:%#x", &a);
printf("p2的值:%#x", p2);
printf("p2的地址:%#x", &p2);
(1)指针的值
&
代表取地址,取的是int类型a的内存地址,并使用p2指针指向该内存地址。
所以p2的值就是a的地址,但是p2的地址,跟a的地址没有关系,因为两个变量在内存中的位置就不一样。
如果想要获取p2指向的内存的值,那么就需要解引用,解析地址的值。
printf("p2指向的内存的值:%d", *p2);
*p2就是解引用,得到的就是指向的a地址的值,10.
(2)指针所占的字节数
printf("p2所占的字节:%d",sizeof(p2));
通过sizeof
可以获取指针所占的字节数,在32位的环境下,int类型就占4字节,在64位的环境下,就是8个字节。
(3)通过指针修改内存值
之前通过*p2可以对指针解引用,从而获取地址对象的值,那么通过重新赋值,是可以改变内存值的。
*p2 = 100;
printf("a的值:%d", a);
这个时候,内存的值就已经改变为了100.
(4)指针的自增
指针指向的是一个地址,当指针执行自增操作时,就会将指针指向的地址也发生相应的偏移。
p2++;
printf("p2的值:%#x", p2);
所以通过指针的特性,可以遍历数组中的值。
int array[] = { 1,2,3,4 };
int* array_int = array;
for (size_t i = 0; i < 4; i++)
{
printf("数组中的值:%d\n", *array_int++);
}
上述数组array
指针赋值的时候 为什么没有加&
,是因为数组在内存中是一块连续的内存,当输出array
的值的时候,其实输出的就是array
的内存首地址,也就是元素11的地址,然后执行指针递增,才能输出数组中全部的元素。
(5)多级指针
int b = 10;
//指针p7保存b的地址
int* p7 = &b;
int** p8 = &p7;
指针p7指向的是变量b的地址,那么指针p7也有地址,那么取指针p7的地址,就是指针的指针p8。
2、函数
函数就是Java中的方法,跟Java不同的是,在使用这个函数之前,就必须要将这个函数显式地声明。
void test() {
}
int main()
{
test();
}
(1)函数含参
主要分为两种:传递值和传递引用
void changed(int i) {
i = 10;
}
int i1 = 3;
changed(i1);
当调用函数changed
的时候,如果传入的是一个int值,那么不会改变该int值的内存值,即便是在函数体中,对形参的值做了修改。
但是如果是传入该变量的地址,那么就可以通过指针来修改内存值。
void changed1(int* i) {
*i = 10;
}
int i1 = 3;
changed1(&i1);
在之前谈到了多级指针,在函数中将会使用到很多。例如有一个p指针,指向了a的内存地址,如果想要将p指针的内存地址修改为指向b的内存地址,那么就可以使用多级指针完成。
void changed2(int** i) {
int j = 100;
*i = &j;
}
int i1 = 3;
int* p = &i1;
changed2(&p);
(2)可变参数
在Java中,采用String... args
来表示可变参数,在C语言中,使用…来表示可变参数。
#include <stdarg.h>
void add(char *c,...)
{
}
在使用可变参数的时候,第一个参数是必须要传递进去的。
如何获取可变参数列表中的所有值,就依赖于这个#include <stdarg.h>
头文件,通过这个头文件,可以使用va_list
和var_arg
来获取
//获取可变参数的列表
va_list list;
//与va_end成对出现,从第一个形参之后开始,这也是为什么必须要有第一个形参的原因
va_start(list, b);
for (size_t i = 0; i < 5; i++)
{
//取出int类型的数据
int j = var_arg(list, int);
printf("可变的参数:%d\n", j);
}
//取出char类型的数据
char k = var_arg(list, char);
printf("可变的参数:%c\n", k);
va_end(list);
(3)函数指针
函数指针指的就是指向函数的指针,先来看一个例子
void set(void (*p)(char*), char* msg) {
p(msg);
}
set函数中,存在两个形参,其中第一个是一个函数指针,第二个是一个char类型的指针,也就是说这个函数可以传递一个函数给它。
重点看下面这个函数指针,这个跟普通的函数其实结构是类似的,只是被放在了函数的形参中
void (*p)(char*)
#######################################
void:函数的返回值;
*p:p变量来表示这个函数,在方法体中可以调用这个函数
char*:指的是函数p的参数列表
在使用的时候,因为需要传递一个函数,因此需要再创建一个正常的函数体,作为第一参数传递进去。
void target(char* p) {
pritnf("第一个形参参数:%c\n", p);
}
void(*p)(char*) = target;
set(p,"hello");
或者
set(target,"hello");
输出: 第一个形参参数:hello
也就是说其中的target函数就是在set形参中的p,
然后执行了target方法,输出了msg,也就是target中的p形参
但是使用上面第一种输出方式太麻烦,可以使用typedef
来创建别名。
typedef void (*Func)(char*);
Func fun = target;
set(fun,"hello");
使用函数指针,也可以用来做回调使用。
void http(void (*Success)(char*), void (*Failure)(char*)) {
Success("成功");
}
void httpOk(char* msg) {
}
void httpFail(char* msg) {
}
void (*Success)(char*) = httpOk;
void (*Failure)(char*) = httpFail;
http(httpOk, httpFail);
以上是常规写法,如果使用typedef
来写。
typedef void (*Success)(char*);
typedef void (*Failure)(char*);
void http(Success success, Failure fail) {
success("成功");
}
void httpOk(char* msg) {
}
void httpFail(char* msg) {
}
http(httpOk, httpFail);
3、预处理器
常见的预处理器有:
include
:导入头文件
if
:if
elif
:else if
else
:else
endif
:结束if
define
:宏定义
ifdef
:如果宏定义
ifndef
:如果没有宏定义
undef
:取消宏定义
宏分为:宏变量和宏函数
(1)宏变量
#define I 1
int main()
{
int d = I;
}
定义一个宏变量I
为1,那么在.cpp文件中,可以自定义一个变量d = I,那么d的值就是1.
也就是说,在代码中所有的I
都会被替换成1,做文本替换的作用。
(2)宏函数
#define test(i) printf("输出:d%\n",i)
test(100);
宏函数和宏变量基本一致,定义了的宏函数,可以直接在代码中使用,输出传入的参数。
因为宏函数只是执行文本替换,它在使用时也会有问题需要注意。
#define ADD(x,y) x*y
ADD(1 + 10, 10 + 10);
我们预期得到的是11*20,但是因为宏函数只会执行文本替换,实际的运算是:
1+10*10+10 = 11+100
宏函数的优点:使用到宏函数的地方会执行文本替换,节省函数调用的开销;
缺点:不会对代码进行检查,生成的目标可能和预期不一致。