C语言基础(1) - 指针与函数

1.指针

1.1* 运算符

字符*表示指针,通常跟在类型关键字的后面,表示指针指向的是什么类型的值。这个值代表一个内存地址,因此指针相当于指向某个内存地址的路标。

* 还可以作为运算符,用来取出指针变量所指向的内存地址里面的值。

一个指针指向的可能还是指针,这时就要用两个星号**表示,指针的指针的值就是某一个指针的地址。

1.2&运算符

&运算符用来取出一个变量所在的内存地址。

&运算符与*运算符互为逆运算,下面的表达式总是成立。

int i = 5;

// 先取变量i的内存地址,再取内存地址里的值,也就是i的值
if (i == *(&i)) 

结合这2个运算符举个例子。

int a = 12;
// 指针的初始化: 先指向一个分配好的地址,然后再进行读写。不能直接写*b = n;
int *b = &a;
int **c = &b;

// a的值
LOGD("a is :%d",a);

// a的地址
LOGD("&a is :%d",&a);

// 指针b指向的内存地址
LOGD("b is :%d",b);

// 取出指针b指向的内存地址里面的值
LOGD("*b is :%d",*b);

// 指针c的指针指向的内存地址  = 指针b的地址(不是指针指向的地址)
LOGD("c is :%d",c);

// 指针c的指针指向的内存地址里的值, *c = *(&b) = b, 也就是指针b指向的内存地址
LOGD("*c is :%d",*c);

// *c = b,那 **c = *b 
LOGD("**c is :%d",**c);

打印结果:

 D/@@: a is :12
 D/@@: &a is :1239724132
 D/@@: b is :1239724132
 D/@@: *b is :12
 D/@@: c is :1239724120
 D/@@: *c is :1239724132
 D/@@: **c is :12

2.函数

2.1参数的传值引用

如果函数的参数是一个变量,那么调用时,传入的是这个变量的值得拷贝,而不是变量本身。

void swap(int x, int y) {
  int temp;
  temp = x;
  x = y;
  y = temp;
}

int a = 1;
int b = 2;
swap(a, b); // 无效

如果要传入变量本身,请传入变量的地址。

void swap(int* x,int* y){
  int temp;
  temp = *x;
  *x = *y;
  *y = temp;
}

int a = 1;
int b = 2;
swap(&a, &b); 

2.2函数指针

如果在程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址。而且函数名表示的就是这个地址。既然是地址我们就可以定义一个指针变量来存放,这个指针变量就叫作函数指针变量,简称函数指针。

定义: 方法的返回值 (*方法的名称)(方法的参数)

void add(int a, int b) {
    LOGD("add:%d", (a + b));
}

void sub(int a, int b) {
    LOGD("sub:%d", (a - b));
}

void operate(void (* method)(int, int), int a, int b) {
    method(a, b);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_dawn_ndksample_JniPractise_test(JNIEnv *env, jobject thiz) {
     // 将add函数的首地址赋给指针变量method
    operate(add, 1, 2);
    // 将sub函数的首地址赋给指针变量method
    operate(sub, 9, 1);
}

打印输出:

 D/@@: add:3
 D/@@: sub:8

2.3 函数原型

在C语言里,函数必须先声明后使用。 由于程序总是先运行main()函数,导致其他函数都必须在main()函数之前声明, 否则编译会报错。

void add(){}

void sub(){}

int main(void) {
  add();
  sub();
  return 0;
}

但是,main()是整个程序的入口,也是主要逻辑,放在最前面比较好。另一方面,对于函数较多的程序,保证每个函数的顺序正确,会变得很麻烦。

C 语言提供的解决方法是,只要在程序开头处给出函数原型,函数就可以先使用、后声明。所谓函数原型,就是提前告诉编译器,每个函数的返回类型和参数类型。其他信息都不需要,也不用包括函数体,具体的函数实现可以后面再补上。

int sub(int);

int main(int num) {
  return sub(num);
}

int sub(int num) {
  return 2 * num;
}

函数原型包括参数名也可以,虽然这样对于编译器是多余的,但是阅读代码的时候,可能有助于理解函数的意图。

int sub(int);

// 等同于
int sub(int num);

2.4 函数说明符

extern说明符

对于多文件的项目,源码文件会用到其他文件声明的函数。这时,当前文件里面需要给出外部函数的原型,并用extern说明该函数的定义来自其他文件。

extern int foo(int arg1, char arg2);

int main(void) {
  int a = foo(2, 3);
  // ...
  return 0;
}

上面示例中,函数foo()定义在其他文件,extern告诉编译器当前文件不包含该函数的定义。

不过,由于函数原型默认就是extern,所以这里不加extern,效果是一样的。

static 说明符

默认情况下,每次调用函数时,函数的内部变量都会重新初始化,不会保留上一次运行的值。static说明符可以改变这种行为。

static用于函数内部声明变量时,表示该变量只需要初始化一次,不需要在每次调用时都进行初始化。也就是说,它的值在两次调用之间保持不变。

#include <stdio.h>

void counter(void) {
  static int count = 1;  // 只初始化一次
  printf("%d\n", count);
  count++;
}

int main(void) {
  counter();  // 1
  counter();  // 2
  counter();  // 3
  counter();  // 4
}

注意,static修饰的变量初始化时,只能赋值为常量,不能赋值为变量。

int i = 3;
static int j = i; // 错误

上面示例中,j属于静态变量,初始化时不能赋值为另一个变量i。

另外,在块作用域中,static声明的变量有默认值0

static int foo;
// 等同于
static int foo = 0;

static可以用来修饰函数本身。

static int Twice(int num) {
  int result = num * 2;
  return(result);
}

上面示例中,static关键字表示该函数只能在当前文件里使用,如果没有这个关键字,其他文件也可以使用这个函数(通过声明函数原型)。

const说明符

函数参数里面的const说明符,表示函数内部不得修改该参数变量。

  • 常量指针
void f(int* p) {
  // ...
}

上面示例中,函数f()的参数是一个指针p,函数内部可能会改掉它所指向的值*p,从而影响到函数外部。

为了避免这种情况,可以在声明函数时,在指针参数前面加上const说明符,告诉编译器,函数内部不能修改该参数所指向的值。

void f(const int* p) {
  *p = 0; // 该行报错
}

上面示例中,声明函数时,const指定不能修改指针p指向的值,所以*p = 0就会报错。

但是上面这种写法,只限制修改p所指向的值,而p本身的地址是可以修改的。

void f(const int* p) {
  int x = 13;
  p = &x; // 允许修改
}
  • 指针常量

想限制修改p,可以把const放在p前面。

void f(int* const p) {
  int x = 13;
  p = &x; // 该行报错
}

如果想同时限制修改p和*p,需要使用两个const。

void f(const int* const p) {
  // ...
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345