指针:
含义:是一个值,一个值代表着一个内存地址,类似于存放路径
* 运算符 :
1 字符*
表示指针
作用:通常跟在类型关键字的后面,表示指针指向的是什么类型的值
int * foo, * bar;
声明指针后会随机分配一个内存空间,指针指向的数据是随机的
指针的初始化:
int* p;
int i;
p = &i;
*p = 13;
未初始化的指针数据最好这样定义:int* p = NULL;
2 运算符
作用:用来取出指针变量所指向的内存地址里面的值
* foo 取出定义的指针的值
/*注意,数组名指向的地址是不能更改的*/
& 运算符:
作用:用来取出一个变量所在的内存地址
int i = 5;
if (i == *(&i)) // 正确,*(&i)含义:先取出i的内存地址,在获取内存地址对应的值
运算:
1 指针与整数值的加减运算
指针与整数值的运算,表示指针的移动。
2 指针与指针的加法运算
两个指针进行加法是非法的
3 指针与指针的减法
相同类型的指针允许进行减法运算,返回它们之间的距离,即相隔多少个数据单位。
高位地址减去低位地址,返回的是正值;低位地址减去高位地址,返回的是负值
返回值:ptrdiff_t
类型,一个带符号的整数类型别名
4 指针与指针的比较运算
比较的是各自的内存地址哪一个更大,返回值是整数1
(true)或0
(false)
函数:
基础结构:
#include <stdio.h>
/* 基础结构*/
int main(void)
{
int m = addNum(3); /* 使用函数, 3 传递的参数*/
printf("m=%d", m);
}
int addNum(int a) /* int addNum 定义函数名称 (int a 传递参数,可以传递多个,使用逗号分开) */
{ /*{函数体:使用此方法需要执行的语句}*/
return a; /* return a; return 返回值,返回计算之后的数据 */
}
参数:
传递的参数是拷贝的数据,非自身数据,不会实时更新数据;
除非
1 函数返回最新的数据,才可以获取并赋值最新的数据
2 直接修改自身的署,传入变量的地址(函数不要返回内部变量的指针)
void Swap(int* x, int* y) {
int temp;
temp = *x;
*x = *y;
*y = temp;
}
int a = 1;
int b = 2;
Swap(&a, &b);
//参数为数组
int sum_array(int a[n], int n) {
// ...
}
int sum = sum_array(a, 4);
//等同于
int sum = sum_array((int []){3, 5, 7, 3}, 4);
/*多维数组*/
int sum_array(int n, int m, int a[n][m]);
参数数量是不确定时,声明函数的时候,可以使用省略号...
表示可变数量的参数
注意:...
符号必须放在参数序列的结尾,否则会报错
#include<stdio.h>
#include<stdarg.h> /* 可以操作可变参数的类*/
double average(int i, ...);
double average(int i, ...) {
double total = 0;
va_list ap; /*va_list 一个数据类型,用来定义一个可变参数对象*/
/* va_start(ap, i); 将参数i后面的参数统一放入ap*/
va_start(ap, i); /*va_start 一个函数,用来初始化可变参数对象 va_start(可变参数对象,正常参数,用来为可变参数定位)*/
printf("i=%d \n", i);
for (int j = 1; j <= i; ++j) {
/* va_arg(ap, double)用来从ap依次取出一个参数,并且指定该参数为 double 类型*/
total += va_arg(ap, double); /* va_arg 一个函数,用来取出当前那个可变参数 va_arg(可变参数对象,当前可变参数的类型)*/
printf("total=%d \n", total);
}
printf("ap=%d \n", ap);
va_end(ap); /* va_end 一个函数,用来清理可变参数对象*/
return total / i;
}
int main(void)
{
int total=average(0,8,10);
printf("total=%d", total);
}
递归函数:
函数可以调用自身,这就叫做递归(recursion)
例如:
#include <stdio.h>
int main(void)
{
int m = addNum(3);
printf("m=%d", m);
}
int addNum(int a)
{
a++ ;
if (a <= 5)
{
addNum(a);
}
else
{
return a;
}
}
注意:在使用递归函数时,注意做好判断,不然容易出现死循环
入口函数:
main()是程序的入口函数,所有的程序必须要有
类似于页面加载方法,首先执行此方法
其他的方法需要通过main调用
#include <stdio.h>
int main(void)
{
int m =3;
printf("m=%d", m);
return 0; /* 表示函数结束运行,返回0; 返回非零数据表示运行失败*/
}
函数指针:
调用函数的方法
// 写法一 常用的 函数名本身就是指向函数代码的指针,通过函数名就能获取函数地址
print(10)
// 写法二
(*print)(10)
// 写法三
(&print)(10)
// 写法四
(*print_ptr)(10)
// 写法五
print_ptr(10)
#include<stdio.h>
int addNum(int a)
{
return a;
}
int main(void) /*调用其他方法时,其他方法要么写在最前面, 或者使用函数原型*/
{
// int m = addNum(3);
// printf("m=%d", m);
int m = (*addNum)(10);
printf("m=%d", m);
}
优点:
可以明确参数是一个函数
参数一就是一个函数
int compute(int (*myfunc)(int), int, int);
函数原型:提前告诉编译器,每个函数的返回类型和参数类型
int twice(int);
int main(int num) {
return twice(num);
}
int twice(int num) {
return 2 * num;
}
注意,函数原型必须以分号结尾。
一般来说,每个源码文件的头部,都会给出当前脚本使用的所有函数的原型。
exit函数:
作用:终止程序的运行
前提: #include<stdlib.h>
返回值(stdlib.h已定义,无需定义):
EXIT_SUCCESS(相当于 0)表示程序运行成功
EXIT_FAILURE(相当于 1)表示程序异常中止
atexit()函数:
用来登记exit()执行时额外执行的函数,用来做一些退出程序时的收尾工作
说明符:
extern 说明符 :
extern int foo(int arg1, char arg2);
extern 表示:导入使用,声明此方法未在此文件定义,是从其他文件内引入使用的
int main(void) {
int a = foo(2, 3);
// ...
return 0;
}
static 说明符:
1 用于函数内部声明变量时,表示该变量只需要初始化一次,不需要在每次调用时都进行初始化
2 static
可以用来修饰函数本身,表示该函数只能在当前文件里使用,如果没有这个关键字,其他文件也可以使用这个函数(通过声明函数原型)
3 用在参数里面,修饰参数数组
int sum_array(int a[static 3], int n) { /* 数组长度至少为3*/
}
注意:
static
修饰的变量初始化时,只能赋值为常量,不能赋值为变量
局部作用域中,static
声明的变量有默认值0
const 说明符:
1 函数内部不得修改该参数变量
void f(const int* p) {
int x = 13;
p = &x; // p本身的地址是允许修改的
*p = 0; //const声明的* p 不能修改,修改之后报错
}
void f(int* const p) { //不允许修改p,就使用const定义
int x = 13;
p = &x; // 该行报错
}
/* p和*p 都不允许修改 */
void f(const int* const p) {
}
内存管理:
分类:
系统管理:局部变量存放的内存区域(”栈“(stack)),运行结束后自动从内存卸载
用户手动管理:全局变量存放的内存区域(”堆“(heap)),运行结束后不会自动卸载,需要用户手动释放,此现象叫”内存泄漏“(memory leak)
void 指针:
void:不定类型的指针
可以指向任意类型的数据,但是不能解读数据
void 指针与其他所有类型指针之间是互相转换关系
注意,由于不知道 void 指针指向什么类型的值,所以不能用*运算符取出它指向的值
内存操作:
分配内存:
malloc:
#include <stdio.h>
#include <stdlib.h> /*内存管理的操作*/
int main(){
/* malloc(分配的内存字节数--非负整数) 任意类型的数据分配内存 返回值:无类型的 void 指针*/
//malloc不会对所分配的内存进行初始化,需要自行初始化后再使用,想要初始化为0,还要额外调用memset()函数
int* p = malloc(sizeof(int));
*p = 12;
if (p == NULL) {
// 内存分配失败
}
printf("%d\n", *p);
int* n = (int*) malloc(sizeof(int)); //强制转换数据为整数类型
printf("n=%d\n",*n);
int* i = (int*) malloc(sizeof(*i));
printf("i=%d\n",*i);
//作用:
//1 为数组和自定义数据结构分配内存
int* t = (int*) malloc(sizeof(int) * 10);
for (int i = 0; i < 10; i++)
t[i] = i * 5;
printf("t==%d",t);
//2 创建动态数组
int m;
int* a = (int*) malloc(m * sizeof(int)); //可以根据变量m的不同,动态为数组分配不同的大小
}
calloc:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(){
// calloc(数据类型的值的数量,数据类型的单位字节长度) 分配内存, 返回值:无类型的 void 指针,分配失败时,返回 NULL
//calloc所分配的内存全部初始化为0
int* p = calloc(10, sizeof(int));
printf("p==%d, %d \n",p,*p);
// 等同于
int* n = malloc(sizeof(int) * 10);
memset(n, 0, sizeof(int) * 10);
printf("n==%d, %d \n",n,*n);
}
释放内存:
free:
#include <stdio.h>
#include <stdlib.h>
int main(){
//free(malloc返回的内存地址) 释放malloc函数分配的内存
//分配的地址释放之后,不可操作和再次释放
// 注意:函数调用成功后,一定要释放内存,否则会导致多次调用函数所生成的内存下次无法访问
int* p = (int*) malloc(sizeof(int));
*p = 12;
printf("address=%d",p);
free(p);
}
修改内存:
realloc:
#include <stdio.h>
#include <stdlib.h>
int main(){
//realloc(已经分配好的内存块指针,内存块的新大小,单位为字节) 修改已经分配的内存块的大小,可以放大也可以缩小 , 返回新的内存块的指针,分配失败返回null
// 已经分配好的内存块指针--参数设置为null或者不写,会创建一个新的指针
// 内存块的新大小 --------参数设置为0,会释放内存块
//realloc不会对内存块进行初始化
int* p = calloc(10, sizeof(int));
float* new_p = realloc(p, sizeof(*p * 40));
if (new_p == NULL) {
printf("Error reallocing\n");
return 1;
}else{
printf("win--- \n");
}
}
拷贝内存:
memcpy拷贝:
#include <stdio.h>
#include <string.h>
//自定义复制内存函数:
void* my_memcpy(void* dest, void* src, int byte_count) {
char* s = src;
char* d = dest;
while (byte_count--) {
*d++ = *s++;
}
return dest;
}
int main(){
//memcpy(拷贝地址,被拷贝地址,拷贝的字节数) 前两个参数:不限制指针类型,各种类型的内存数据都可以拷贝
char s[] = "Goats!";
char t[100];
// memcpy(t, s, sizeof(s)); // 拷贝7个字节,包括终止符
int m = my_memcpy(t, s, sizeof(s));
printf("m=%s\n", m); // "Goats!"
return 0;
}
memmove复制:
#include <stdio.h>
#include <string.h>
int main(){
//memmove(拷贝地址,被拷贝地址,移动的字节数) 允许目标区域与源区域有重叠。如果发生重叠,源区域的内容会被更改
char x[] = "Home Sweet Home";
printf("x=%s\n", (char *) memmove(x, &x[5], 10));
}
比较内存:
memcmp:
#include <stdio.h>
#include <string.h>
int main(){
//memcmp(比对者1,比对者2,比较的字节数) 返回值:整数
//相同时返回0,参数一大于参数二时返回大于0,参数一小于参数二返回小于0
// char* s1 = "abc";
// char* s2 = "acd";
// int r = memcmp(s1, s2, 3); // 小于 0
// printf("memcmp result=%d",r);
//比较内部带有字符串终止符\0的内存区域
char s1[] = {'b', 'i', 'g', '\0', 'c', 'a', 'r'};
char s2[] = {'b', 'i', 'g', '\0', 'c', 'a', 't'};
printf ("3=%d \n",memcmp(s1, s2, 3) == 0) ;// true
printf ("4=%d \n",memcmp(s1, s2, 4) == 0) ;// true
printf ("7=%d \n",memcmp(s1, s2, 7) == 0) ;// false
}
restrict 说明符 :
restrict :内存区域只有当前指针一种访问方式,其他指针不能读写该内存,这种指针称为“受限指针”(restrict pointer)