指针和数组
直接访问:按变量的地址存取变量的值(通过数组名访问)
间接访问:通过存放变量的地址的变量去访问元素(通过指针访问)
1. 指针和一维数组
int a[5] = {1, 2, 3, 4, 5};
int *p = a;
直接访问:
int a[5] = {5, 4, 3, 2, 1};
int *p = a;
printf("%p %p %p\n", a, a+1, a+2);
printf("%p %p %p\n", p, p+1, p+2);
printf("%d %d %d\n", a[0], *(a+1), *(a+2));
间接访问:
int a[5] = {5, 4, 3, 2, 1};
int *p = a;
printf("%p %p %p\n", a, a+1, a+2);
printf("%p %p %p\n", p, p+1, p+2);
printf("%d %d\n", *(a+2), *(a+3));
printf("%d %d\n", *(p+2), *(p+3));
printf("%d %d\n", a[0], p[0]);
a和p本质上不同,a是地址常量,p是变量,a不能执行++操作,但是p可以
访问数组元素a[i]的值:
直接访问:a[i] *(a+i)
间接访问:p[i] *(p+i)
访问数组元素a[i]的地址:
直接访问:&a[i] a+i
间接访问:&p[i] p+i
int a[3] = {3, 2, 1};
int *p = a;
printf("%d\n", *p++); // 3 再打印一次的话就是2
printf("%d\n", *a++); // 错误,a地址常量
运算方法:
1) ++和 * 都是单目运算符,优先级相同
2) 运算顺序从右向左进行运算
int a[3] = {3, 2, 1};
int *p = a;
printf("%d\n", 下列打印);
*(p++) // 3 实际上指针指向第二个元素的地址
(*p)++ // 打印出来的是3 实际上第一个元素值变为4
++*p // 打印出来的是4 自加完之后的值
++(*p) // 同上
*++p // 2,先将会指针向高地址方向移动一个数据单位,然后取地址内容
*(++p) // 同上
2. 指针和二维数组
int a[2][3] = {1, 2, 3, 4, 5, 6}; // a数组名,表示第一行首地址,a+1:第二行首地址
在a前面加 *,表示将行地址降级为列地址
*a:第一行第一列的地址
*a+1:第一行第二列的地址
*(a+1):第二行第一列的地址
*(a+1)+1:第二行第二列的地址
直接访问:
*(*(a+i)+j):拿到i+1行j+1列的元素
*(a[i]+j):拿到i+1行j+1列的元素
a[i][j]:拿到i+1行j+1列的元素
间接访问
数组指针
定义:本质上是指针,指向的数组(行指针)
格式:存储类型 数据类型 (*指针变量名)[列数];
int a[2][3] = {1, 2, 3, 4, 5, 6};
int (*p)[3] = a;
p:int (*)[3]:运算是三个三个算的
p可以代替a进行元素访问,但是本质不同
访问 a[i][j] 地址:
p[i] + j == a[i]+j
*(p+i)+j
访问 a[i][j] 的内容
*(*(p+i)+j) *(p[i] + j)
大小:
sizeof(p) == 4字节
例题:
int a[2][3]={1,2,3,4,5,6};用数组指针遍历二维数组。
若有程序段:int a[2][3],(*p)[3]; p=a;则对a数组元素的正确引用是
A) (p+1)[0] B) *(*(p+2)+1) c) *(p[1]+1) D) p[1]+2
下面程序的运行结果是 。
main ( )
{ int x[5]={2,4,6,8,10}, *p, **pp ;
p=x , pp = &p ;
printf(“%d”,*(p++));
printf(“%3d”,**pp);
}
A) 4 4 B) 2 4 C) 2 2 D) 4 6
已有定义int k=2;int *ptr1,*ptr2;且ptr1和ptr2均已指向变量k,下面不能正确执行的赋值语句是 。
A) k = *ptr1 + *ptr2 B) ptr2 = k
C) ptr1 = ptr2 D) k = *ptr1*(*ptr2)
若有语句:int *p,a=4;和p=&a;下面均代表地址的一组选项是
A) a, p, *&a B)&*a, &a, *p C) *&p, *p, &a D) &a, &*p, p
下面程序段的运行结果是 。
char a[ ]=”language” , *p ;
p=a ;
while (*p!=’u’) { printf(“%c”,*p-32); p++ ; }
A) LANGUAGE B) language C) LANG D) langUAGE
以下代码的运行结果是 ( ) (H3C) (printf参数运算顺序)
int main()
{
int arr[]={11,12,13,14,15};
int *ptr = arr;
*(ptr++) += 100; // 展开: *ptr = *ptr +100; ptr++;
printf("%d %d\n",*ptr,*(++ptr));
return 0;
}
A) 13 13 B) 112 13 C) 12 12 D) 12 13
若有定义:int a[2][3];则对a数组的第i+1行第j列+1元素值的正确引用是。
A)*(*(a+i)+j) B)(a+i)[j] C)*(a+i+j) D)*(a+i)+j
若有定义:int a[5],*p=a;则对a数组元素地址的正确引用是。
A)p+5 B)*a+1 C)&a+1 D)&a[0]
指针数组
1. 定义:
本质是数组,里面存放的是指针
2. 格式:
存储类型 数据类型 *数组名[元素个数]
int *arr[3] = {};
3. 应用实例:
3.1. 用于存放普通变量的地址
int a = 10, b = 20, c = 30;
int *arr[3] = {&a, &b, &c};
访问b的地址:
arr[1] *(arr+1)
访问 b 的值
*(arr[1]) *(*(arr+1))
3.2. 用于存放二维数组的每一行第一个元素的地址(列地址)
int a[2][3] = {1, 2, 3, 4, 5, 6};
int *arr[2] = {a[0], a[1]};
访问 a[1][2] 的地址:
arr[1]+2 *(arr+1)+2
arr:第一个元素的地址
arr+1:第二个元素的地址
*(arr+1):第二个元素:a[1]:第二行第一列的地址
*(arr+1)+2:第二行第三列的地址 &a[1][2]
3.3. 用于存放字符串
char str[32] = "hello"; // 存放的是整个hello字符串
char *str = "hello"; // 存放的是hello字符串的首地址
char str[32] = "hello";
char *strs = "hello";
printf("%s\n", str);
printf("%s\n", strs);
printf("%p %p\n", strs, &str[0]);
printf("%c %c\n", *(strs+1), str[1]);
使用指针数组存放字符串
char *str[3] = {"hello", "world", "xiaomisu7"};
打印 "xiaomisu7" 字符串
char *str[3] = {"hello", "world", "xiaomisu7"};
printf("%s\n", str[2]); // xiaomisu7
printf("%s\n", *(str+2)); // xiaomisu7
printf("%s\n", *(str+2)+4); // misu7
打印 'm' 这个字符
printf("%c\n", *(*(str+2)+4)); // m
printf("%c\n", *(str[2]+4)); // m
练习:
用指针将整型组s[8]={1,2,3,4,5,6,7,8}中的值逆序存放。
puts 函数用于打印以空字符 '\0' 结尾的字符串,而不是用于打印整数数组
练习:
用变量a给出下面的定义(3C科技、宇视科技,神思电子,中安云科,北京凝思软件)
a) 一个整型数:· int a;
b) 一个指向整型数的指针: int *a;
c) 一个指向指针的的指针,它指向的指针是指向一个整型数: int **a;
d) 一个有10个整型数的数组: int a[10];
e) 一个有 10个指针的数组,该指针是指向一个整型数的 int *a[10];
f) 一个指向有 10个整型数数组的指针: int (*a)[10];
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数:
int (*p)(int)
h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数
int (*arr[10])(int)
函数
1. 定义
一个完成特定功能的代码模块
2. 三要素
功能、参数、返回值
3. 格式
存储类型 数据类型 函数名(参数列表)
{
函数体;
}
1) 没有参数:参数列表可以省略,也可以用void
2) 没有返回值:数据类型为 void , 函数内部没有return语句。
3) 有返回值:要根据返回值的数据类型定义函数的数据类型
4) 定义子函数时直接定义在主函数上面,如果定义在主函数下面需要提前声明函数
4. 函数声明
数据类型 函数名(参数列表); // 形参
5. 函数调用
1) 没有返回值:直接调用:函数名(实参);
2) 有返回值:如果需要接收返回值,就要定义一个与返回值类型相同的变量去接收
如果不需要接收返回值,就直接调用函数
#include <stdio.h>
void fun()
{
printf("hello\n");
}
void add1(int a, int b)
{
printf("sum = %d\n", a + b);
}
int add2(int a, int b)
{
return a + b;
}
int sub(int a, int b);
int main(int argc, char const *argv[])
{
fun();
add1(1, 2);
int sum = add2(3, 5);
printf("%d\n", sum);
int su7 = sub(7, 3);
printf("%d\n", su7);
return 0;
}
int sub(int a, int b)
{
return a - b;
}
练习1:编写一个函数,函数的2个参数,第一个是一个字符,第二个是一个char *,返回字符串中该字符的个数。
#include <stdio.h>
int count(char c, char *s)
{
int n = 0;
while (*s != '\0')
{
if(c == *s)
{
n++;
}
s++;
}
return n;
}
int main(int argc, char const *argv[])
{
printf("请输入一个需要查找的字符:");
char c = getchar();
char str[32];
printf("请输入一个字符串:");
scanf("%s", str);
int num = count(c, str);
printf("查找到该字符个数为:%d个\n", num);
return 0;
}
练习2:编程实现strlen函数的功能,strlen计算字符串实际长度,不包含’\0’
6. 函数传参
6.1. 值传递
单向传递,将实参传递给形参使用,改变形参实参不会受到影响
#include <stdio.h>
int fun(int num, int sum)
{
num++;
sum++;
return num + sum;
}
int main(int argc, char const *argv[])
{
int a = 3, b = 4;
int ret = fun(a, b);
printf("%d %d %d\n", a, b, ret); // 3 4 9
return 0;
}
6.2. 地址传递
双向传递,在函数中修改形参,实参也会随之变化
#include <stdio.h>
int fun(int *num, int *sum)
{
(*num)++;
(*sum)++;
return *num + *sum;
}
int main(int argc, char const *argv[])
{
int a = 3, b = 4;
int ret = fun(&a, &b);
printf("%d %d %d\n", a, b, ret); // 4 5 9
return 0;
}
6.3. 数组传递
和地址传递一样,参数中存在数组的定义,它也会认为是一个指针
#include <stdio.h>
char *fun2(char a[32])
{
a = "hello";
char *str = "hello";
// 如果是数组的话是 32, 如果是指针的话是4
printf("%d\n", sizeof(a)); // 4
return a;
}
int main(int argc, char const *argv[])
{
char *ch = fun2("abc");
printf("%s\n", ch);
char *str = "hello";
return 0;
}
补充:
char *p = "hello";
// p 在栈区开辟4字节空间存放字符串常量“hello”的首地址
// “hello“:存放在常量区 // 所以是不能被修改的
char buf[32]="hello";
//buf:在栈区开辟32字节空间,存放"hello"字符串
// 所以当时说怎么去存放字符串,使用字符串数组,可以遍历修改
char * GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str=NULL;
str = GetMemory();
printf(str);
}
请问运行 Test 函数会有什么样的结果?
答:编译时会报警告,提示局部变量;运行结果不同的编译器环境下,可能会有不同的结果,可能会出现打印乱码或正常输出;也有编译器会出现段错误
练习:
2. a=3,b=5,对a和b的值进行交换。 (北京君正集成电路)
3. 写一个函数判断某一年是不是闰年
动态内存开辟(开辟堆区空间)
为什么存在动态内存开辟
<1>在技术方面,普通的空间申请,都是在全局或者栈区,全局一般不太建议大量使用,而栈空间有限,那么如果一个应 用需要大量的内存空间的时候,需要通过申请堆空间来支持基本业务。
<2>在应用方面,程序员很难一次预估好自己总共需要花费多大的空间。想想之前我们定义的所有数组,因为其语法约束,我们必须得明确"指出"空间大小.但是如果用动态内存申请(malloc)因为malloc是函数,而函数就可以传参,也就意味着,我们可以通过具体的情况,对需要的内存大小进行动态计算,进而在传参申请,提供了很大的灵活性
开辟空间
#include <stdlib.h>
void *malloc(size_t size);
功能:在堆区开辟空间
参数:size:开辟空间的大小 (单位:字节)
返回值:
成功:返回开辟空间的首地址
失败:NULL
释放空间
#include <stdlib.h>
void free(void *ptr);
功能:释放堆区空间
参数:ptr:堆区空间的首地址
返回值:无
free(p);
// 对p进行释放的时候需要对 p 赋值一个NULL,避免它成为野指针
p = NULL;
例子:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
// 为开辟n个数据类型的大小
int n;
scanf("%d", &n);
int *p = (int *)malloc(n*sizeof(int));
// 容错判断
if(p == NULL)
{
printf("开辟失败\n");
return -1;
}
printf("开辟成功\n");
// 使用堆区空间
for (int i = 0; i < n; i++)
{
*(p+i) = i;
}
// 释放堆区空间
free(p);
p = NULL;
return 0;
}
注意:
1.手动开辟堆区空间,要注意内存泄漏
当指针指向开辟堆区空间后,又对指针重新赋值,则没有指针指向开辟带队去空间,就会造成内存泄漏
2.使用完堆区空间后及时释放空间
思考:如下代码的输出结果
char * fun(char *p)//p=NULL
{
p = "hello";
// 这个就是把 hello 赋值到 p 里面去了
return p;
}
main()
{
char *m = NULL;
m = fun("abc");
printf("%s\n", m);
}
代码出现段错误,原因?
解决方案:
总结 如果在子函数中开辟堆区空间,想在主函数中拿到堆区空间首地址有两种方法:
a. 通过返回值
char * fun()
{
char *p = (char *)malloc(32);
strcpy(p, "hello");
return p;
}
int main()
{
// 定义一个指针接收返回值
char *m = fun();
printf("%s\n",m);
free(m);
m = NULL;
return 0;
}
b. 通过传参
void fun(char **p) // &m
{
*p = (char *)malloc(32); // m
strcpy(*p, "hello");
}
int main()
{
char *m = NULL;
fun(&m);
printf("%s\n", m);
free(m);
m=NULL;
return 0;
}
string 函数族
1. strcpy
#include <string.h>
char *strcpy(char *dest, const char *src);
功能:实现字符串的复制
参数:dest:目标字符串首地址
src:源字符串首地址
返回值:目标字符串首地址
#include <stdio.h>
#include <string.h>
int main(int argc, char const *argv[])
{
char s[32];
char a[32] = "hello";
strcpy(s, a);
printf("%s\n", s);
return 0;
}
复制包括\0
char *strncpy(char *dest, const char *src, size_t n);
功能:实现字符串的复制
参数:dest:目标字符串首地址
src:源字符串首地址
n:字符个数
返回值:目标字符串首地址
复制src前n个字符
#include <stdio.h>
#include <string.h>
int main(int argc, char const *argv[])
{
char s[32] = "world";
char a[32] = "hello";
strncpy(s, a, 2);
printf("%s\n", s);
return 0;
}
2. strlen
#include <string.h>
size_t strlen(const char *s);
功能:计算字符串的实际长度
参数:s:字符串的首地址
返回值:实际长度
3. strcat
#include <string.h>
char *strcat(char *dest, const char *src);
功能:用于字符串拼接
参数:dest:目标字符串首地址
src:源字符串首地址
返回值:目标字符串首地址
#include <stdio.h>
#include <string.h>
int main(int argc, char const *argv[])
{
char s[32] = "hello";
char a[32] = "world";
strcat(s, a);
printf("%s\n", s);
return 0;
}
char *strncat(char *dest, const char *src, size_t n); 拼接src的前n个字符
#include <stdio.h>
#include <string.h>
int main(int argc, char const *argv[])
{
char s[32] = "hello";
char a[32] = "world";
strncat(s, a, 2);
printf("%s\n", s);
return 0;
}
4. strcmp
#include <string.h>
int strcmp(const char *s1, const char *s2);
功能:用与字符串比较
参数:s1、s2 用于比较字符串的首地址
返回值:
从字符串首个字符开始比较字符的 ASCII的大小,如果相等继续向后比较
1 s1 > s2
0 s1 == s2
-1 s1 < s2
#include <stdio.h>
#include <string.h>
int main(int argc, char const *argv[])
{
char s1[] = "hello";
char s2[] = "hellohello";
char *s3 = "nihao";
char *s4 = "shijie";
char *s5 = "hello";
int ret = strcmp(s1, s2); // s1 < s2 = -1
int ret1 = strcmp(s1, s3); // s1 < s3 = -1
int ret2 = strcmp(s2, s4); // s2 < s4 = -1
int ret3 = strcmp(s3, s2); // s3 > s2 = 1
int ret4 = strcmp(s1, s5); // s1 == s5 = 0
if(!strcmp(s1, s5)) !false == true
{
printf("相等\n");
}
return 0;
}
int strncmp(const char *s1, const char *s2, size_t n); 比较前n个字符的大小
递归函数
1. 定义:自己调用自己
2. 执行过程分为两个阶段
a. 递推阶段:从原问题出发,按递推公式从未知到已知最终达成递归的终止条件
b. 回归阶段:按递归的终止条件求出结果,逆向逐步带入递归公式,回到原问题求解
3. 递归的两个必要条件
a. 存在限制条件,当满足这个限制条件的时候,递归便不再继续。
b. 每次调用之后越来越接近这个限制条件
4. 大事化小
例子:
求一个5-1的乘积
#include <stdio.h>
int fun(int n)
{
if(n == 1)
{
return 1;
}
return n*fun(n-1);
}
int main(int argc, char const *argv[])
{
printf("%d\n", fun(5));
return 0;
}
例子:打印一个数的每一位
接收一个整型值,按照顺序打印它的每一位
示例:1234
输出:1 2 3 4
思路:按照顺序打印它的每一位我们就用 1234%10就会等于 4,想打印其他的数1234怎么来呢,1234/10 = 123,再继续 123%10等于3,以此类推
#include <stdio.h>
void fun(int n)
{
if(n > 9)
{
fun(n / 10);
}
printf("%d\n", n % 10);
}
int main(int argc, char const *argv[])
{
fun(1234);
return 0;
}
标签:return,int,基础,笔记,C语言,char,地址,printf,hello
From: https://blog.csdn.net/weixin_58298518/article/details/140016253