1.排序
形参中的数组,编译器会把他当作指针处理
形参写在函数上,和写在函数内是一样的,只不过是
写在函数上具有对外的属性
2.关于数组地址
看下面函数的运行结果,你会发现,b+1和&b+1的区别,b表示的是数组元素首地址,所以b+1就是将从首元素向后移动一个元素后得到的第二个元素的地址,但是&b+1的结果优点出乎意料,它相对于首元素地址向后移动了40个位置,而40刚好是数组b的长度,怎么解释呢,其实就是&b表示的是整个数组的地址,把整个数组当作一个整体,那么加一就是移动这样一个整体后的地址了
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void main() {
int b[10];
printf("b:%d,b+1:%d,&b+1:%d", b, b + 1, &b + 1);
system("pause");
}
打印结果:
b:19922588,b+1:19922592,&b+1:19922628请按任意键继续. . .
1)数组首元素的地址和数组地址是两个不同的概念
2)数组名代表数组首元素地址,他是个常量
解释如下:变量本质是内存空间的别名,而数组在定义时就会分配内存,内存就固定了,所以数组名起名以后就不能更改了
3)数组首元素的地址和数组值相等
C语言规定:
int a[10]
&a表示整个数组的地址,a表示数组首元素的地址,区别体现在地址+1的结果
如何定义一个数组数据类型
int a;//定义了一个int类型a
typedef int (MyArrayType)[5];//定义一个数组数据类型MyArrayType,
MyArrayType myArray; //相当于int myArray[5]
myArray[0] = 0;
...
myArray[4]=4;
定义数组指针的第一种方法
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void main() {
char *MyArray[] = {"1111","2222","3333"};
//数组指针,一个指针,指向了数组
//定义了一个数组数据类型
typedef int(MyArrayType)[5];
int i = 0;
//用类型定义变量,相当于int myArray[5]
MyArrayType myArray;
//定义一个指针变量,这个变量指向数组
MyArrayType *pArray;
//定义一个数组,相当于一级指针
int myArray2[5];
//数组指针指向这个数组,相当于二级指针
pArray = &myArray2;
for (i = 0; i < 5; i++) {
(*pArray)[i] = i + 1;
}
for (i = 0; i < 5; i++) {
printf("%d", (*pArray)[i]);
}
system("pause");
}
定义数组指针的第二种方法
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void main() {
//定义声明一个数组指针类型
typedef int(*PArrayType)[5];
//告诉编译器给我分配一个指针变量
PArrayType pArray;
//定义一个数组,相当于一级指针
int myArray2[5];
//数组指针指向这个数组,相当于二级指针
pArray = &myArray2;
int i = 0;
for (i = 0; i < 5; i++) {
(*pArray)[i] = i + 1;
}
for (i = 0; i < 5; i++) {
printf("%d", (*pArray)[i]);
}
system("pause");
}
定义数组指针的第三种方法
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void main() {
//直接定义一个指向数组的数组指针变量
int(*PArrayType)[5];
//定义一个数组,相当于一级指针
int myArray2[5];
//数组指针指向这个数组,相当于二级指针
PArrayType = &myArray2;
int i = 0;
for (i = 0; i < 5; i++) {
(*PArrayType)[i] = i + 1;
}
for (i = 0; i < 5; i++) {
printf("%d", (*PArrayType)[i]);
}
system("pause");
}
3.内存四区的建立流程
内存四驱的建立流程.png4.写一段程序证明栈的生长方向(开口向上还是开口向下)
在栈内存中定义两个变量ab一个数组buf,比较他们的地址。如图所示,如果栈的开口向上,那么先入栈的变量地址会小于后入栈的变量地址,如果开口向下则反之,所以可以根据这个特性来判断,我们打印了ab的地址,结果是a地址大于b,那么可以确定当前环境下,栈开口是向下的,可以默认栈的开口是向下的,这种情况比较多。
同时可以看到我们定义了一个数组,可以看到,虽然栈开口向下,但是数组中元素的存储方式并没有像栈中元素ab一样,你在栈开口向上的情况下试一下会发现,栈的开口不影响数组元素的存储形式,数组存储永远是图中所示的样子
栈属性.png
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void main() {
int a;
int b;
printf("&a:%d,&b:%d\n", &a, &b);
char buf[2] = {'a','b'};
printf("&buf[0]:%d,&buf[1]:%d\n", &buf[0], &buf[1]);
system("pause");
}
打印结果
&a:8387696,&b:8387684
&buf[0]:8387672,&buf[1]:8387673
请按任意键继续. . .
5.strcpy函数的5中推演方式
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
/*
移动from和to指针,当from指针到达末尾的时候跳出
*/
void copy1(char *from, char *to) {
for (; *from != '\0'; from++, to++) {
*to = *from;
}
//在字符串的末尾加上\0,因为拷贝的时候跳过了\0
*to = '\0';
return;
}
/*
把自增运算放在了循环中,++优先级大于*
但是因为++是放在后边的,所以整体执行顺序还是先
将*from赋值给*to,然后from和to做自增运算
*/
void copy2(char *from, char *to) {
for (; *from != '\0';) {
*to++ = *from++;
}
*to = '\0';
return;
}
/*
这种写法可以减少手动在末尾添加0的操作,
(*to = *from) != '\0'这行代码做了两部操作
第一先赋值,第二判断是否是0,所以当到达末尾
得的时候,0也被拷贝了
*/
void copy3(char *from, char *to) {
while ((*to = *from) != '\0') {
from++;
to++;
}
}
/*
这个操作更加简化,所以操作写在了判断条件中
*/
void copy4(char *from, char *to) {
while ((*to++ = *from++) != '\0') {
}
}
/*
最简版
*/
void copy5(char *from, char *to) {
while (*to++ = *from++) {
}
}
void main() {
char *from = "hello world";
char to[100];
copy5(from, to);
//C语言打印数据会将数组元素打印出来
printf("to:%s\n", to);
system("pause");
}
6.字符串反转的两种方式
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
//两头堵模型实现字符串反转
void inverse1(char *str) {
int length;
char *start;
char *end;
length = strlen(str);
start = str;
end = str + length - 1;
while (start < end) {
char c = *start;
*start = *end;
*end = c;
++start;
--end;
}
}
/*
递归调用实现字符串反转,这一点利用了栈的结构特点,先入后出
不断的将字符串中每一个字符入栈,最后打印每一个字符,由后往前
*/
void inverse2(char *str) {
if (str == NULL) return;
//到达字符串末尾,结束递归
if (*str == '\0') return;
inverse2(str + 1);
printf("%c", *str);
}
/*
将反转后的字符串保存到全局变量中
*/
//全局变量被修改,多线程操作中会涉及到线程安全的问题
char buf[100];
void inverse3(char *str) {
if (str == NULL) return;
//到达字符串末尾,结束递归
if (*str == '\0') return;
inverse3(str + 1);
strncat(buf, str,1);
}
void main() {
char a[] = "abcdefg";
//inverse1(a);
//printf("%s\n", a);
//inverse2(a);
memset(buf, 0, sizeof(buf));
inverse3(a);
printf("buf = %s\n", buf);
system("pause");
}
7.const深入理解
int main(){
const int a;
int const b;
const char *c;
char * const d;
const char * const e;
}
1.const修饰常量,前两种情况是相同的,表示常量ab都不能被修改
2.const修饰指针,第三种是一个指向常整型的指针,他所指向的内存数据不能被修改,但是他本身可以被修改,也就是所,可以修改他指向的地址
3.const修饰指针,第四种是一个常量指针,指针变量不能被修改,但是他指向的内存空间可以被修改,不能改变指向,但是值可变
4.const修饰指针,第五种是一个指向常整型的常量指针,指针和他指向的内存空间都不能被修改
8.二级指针做函数参数的作用
通过二级指针做参数的好处是,可以用于修改原一级指针所指向的内存,同时也可以修改原一级指针的指向。那么直接用一级指针做参数进行传递就做不到这两点了吗,答案是,可以做到其中以点,就是操作原一级指针指向的内存,但是无法对原一级指针进行操作
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
/*
二级指针做函数参数,用于修改传入一级指针的指向
*/
int getMem(char **a1, int *myLen1, char ** a2, int *myLen2) {
char *temp1 = NULL;
char *temp2 = NULL;
temp1 = (char *)malloc(100);
if (temp1 == NULL) {
return -1;
}
strcpy(temp1, "hello");
//让传入的二级指针指向的指针指向temp指向的内存空间
//这行代码的意思:*a1表示p1,让p1指向了temp1指向的空间
*a1 = temp1;
temp2 = (char *)malloc(100);
if (temp2 == NULL) {
return -1;
}
strcpy(temp2, " world");
*a2 = temp2;
}
/*
通过二级指针释放一级指针指向的内存
*/
void freeMem(char** temp1, char **temp2) {
if (temp1 == NULL || temp2 == NULL)
{
return;
}
free(*temp1);
*temp1 = NULL;
free(*temp2);
*temp2 = NULL;
}
/*
如果传入一级指针释放内存会怎样?
*/
void freeMem(char* temp1, char *temp2) {
if (temp1 == NULL || temp2 == NULL)
{
return;
}
free(temp1);
//这一步只是将局部变量temp1设置为null,所以并不影响main函数
//中p1这个变量,所以传递一级指针,无法操作原一级指针变量
temp1 = NULL;
free(temp2);
//这一步只是将局部变量temp2设置为null,所以并不影响main函数
//中p2这个变量,所以传递一级指针,无法操作原一级指针变量
temp2 = NULL;
}
void main() {
char *p1 = NULL;
int len1 = 0;
char *p2 = NULL;
int len2 = 0;
int ret;
ret = getMem(&p1, &len1, &p2, &len2);
printf("%s\n", p1);
printf("%s\n", p2);
freeMem(&p1, &p2);
system("pause");
}
9.二级指针做输入,指向指针数组
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void printArr(char** temp, int num) {
int i = 0;
for (; i < num; i++) {
//注意,这里*(temp) = *(temp+0) ,二级指针指向
//指针数组,*(temp+i)表示的是原数组arr中的第i
//个元素的地址,而我们知道,C语言直接打印数组
//元素的地址,就会将数组元素打印出来
printf(" *(temp + %d) = %s\n", i, *(temp + i));
}
}
void bubbleSort(char **temp, int num) {
int i = 0, j = 0;
for (int i = 0; i<num - 1; i++) {//外层循环控制排序趟数
for (int j = 0; j<num - 1 - i; j++) {//内层循环控制每一趟排序多少次
if (strcmp(*(temp + j),*(temp + j + 1))>0)
{
char *smallest = *(temp + j);
*(temp + j) = *(temp + j + 1);
*(temp + j + 1) = smallest;
}
}
}
}
void main() {
//这是一个指针数组,存放的是是每个元素的地址
char *arr[] = {"aaaaa","ccccc","ddddd","bbbbb"};
int num = sizeof(arr) / sizeof(arr[0]);
printf("数组的长度是:%d\n", num);
printf("排序之前\n");
printArr(arr, num);
bubbleSort(arr, num);
printf("排序之后\n");
printArr(arr, num);
system("pause");
}
二级指针做输入第一种形式.png
顺便复习一下冒泡排序
原理:依次比较相邻的两个数,将小数放在前面,大数放在后面。即在第一趟:首先比较第1个和第2个数,将小数放前,大数放后。然后比较第2个数和第3个数,将小数放前,大数放后,如此继续,直至比较最后两个数,将小数放前,大数放后。重复第一趟步骤,直至全部排序完成。
举例说明:要排序数组:int[] arr={6,3,8,2,9,1};
第一趟排序:
第一次排序:6和3比较,6大于3,交换位置: 3 6 8 2 9 1
第二次排序:6和8比较,6小于8,不交换位置:3 6 8 2 9 1
第三次排序:8和2比较,8大于2,交换位置: 3 6 2 8 9 1
第四次排序:8和9比较,8小于9,不交换位置:3 6 2 8 9 1
第五次排序:9和1比较:9大于1,交换位置: 3 6 2 8 1 9
第一趟总共进行了5次比较, 排序结果: 3 6 2 8 1 9
第二趟排序:
第一次排序:3和6比较,3小于6,不交换位置:3 6 2 8 1 9
第二次排序:6和2比较,6大于2,交换位置: 3 2 6 8 1 9
第三次排序:6和8比较,6大于8,不交换位置:3 2 6 8 1 9
第四次排序:8和1比较,8大于1,交换位置: 3 2 6 1 8 9
第二趟总共进行了4次比较, 排序结果: 3 2 6 1 8 9
第三趟排序:
第一次排序:3和2比较,3大于2,交换位置: 2 3 6 1 8 9
第二次排序:3和6比较,3小于6,不交换位置:2 3 6 1 8 9
第三次排序:6和1比较,6大于1,交换位置: 2 3 1 6 8 9
第二趟总共进行了3次比较, 排序结果: 2 3 1 6 8 9
第四趟排序:
第一次排序:2和3比较,2小于3,不交换位置:2 3 1 6 8 9
第二次排序:3和1比较,3大于1,交换位置: 2 1 3 6 8 9
第二趟总共进行了2次比较, 排序结果: 2 1 3 6 8 9
第五趟排序:
第一次排序:2和1比较,2大于1,交换位置: 1 2 3 6 8 9
第二趟总共进行了1次比较, 排序结果: 1 2 3 6 8 9
最终结果:1 2 3 6 8 9
由此可见:N个数字要排序完成,总共进行N-1趟排序,每i趟的排序次数为(N-i)次,用双重循环语句,外层控制循环多少趟,内层控制每一趟的循环次数
10.二级指针做输入,第二种形式
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void main() {
int i = 0;
char **p2 = NULL;
int num = 5;
//在堆空间中开辟内存空间,大小是sizeof(char*)*num,
//用于存储5个char*类型的指针变量
p2 = (char **)malloc(sizeof(char*)*num);
//开辟空间之后给每个元素赋值
for (i = 0; i < num; i++) {
//给每个char*类型的指针变量开辟指向的内存空间,这个空间
//用于存放char类型的数据,最大可存100个char
*(p2+i) = (char*)malloc(sizeof(char) * 100);
//赋值
sprintf(*(p2 + i), "存入我的数据%d,", i + 1);
}
for (i = 0; i < num; i++) {
printf("%s\n", *(p2 + i));
}
//释放
for (i = 0; i < num; i++) {
if (*(p2 + i) != NULL) {
free(*(p2 + i));
*(p2 + i) = NULL;
}
}
if (p2 != NULL)
{
free(p2);
p2 = NULL;
}
system("pause");
}
void main2() {
int i = 0;
char **p2 = NULL;
int num = 5;
//在堆空间中开辟内存空间,大小是sizeof(char*)*num,
//用于存储5个char*类型的指针变量
p2 = (char **)malloc(sizeof(char*)*num);
//开辟空间之后给每个元素赋值
for (i = 0; i < num; i++) {
//给每个char*类型的指针变量开辟指向的内存空间,这个空间
//用于存放char类型的数据,最大可存100个char
p2[i] = (char*)malloc(sizeof(char) * 100);
//赋值
sprintf(p2[i], "存入我的数据%d,", i + 1);
}
for (i = 0; i < num; i++) {
printf("%s\n", p2[i]);
}
//释放
for (i = 0; i < num; i++) {
if(p2[i] != NULL) {
free(p2[i]);
p2[i] = NULL;
}
}
if (p2 != NULL)
{
free(p2);
p2 = NULL;
}
system("pause");
}
二级指针第二种形式.png
11.二级指针操作字符串练习
切割字符串并存储在堆内存中,然后打印切割结果
char *p1 = "abcdef,acccd,eeee,aaaa,e3eeeee,sssss,ssccccccc,";
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int splitString(const char *buf1, char c, char** myp, int *count) {
char *p = NULL, *ptmp = NULL;
int tmpcount = 0;
//两个指针都指向了字符串指针
p = buf1;
ptmp = buf1;
do {
//从p中找char c
p = strchr(p, c);
if (p != NULL) {
//大于0说明指针已经有移动位置,新的字符串和旧的不是同一个,
//如果字符串第一个字符就找到了符合条件的,那么p-pmt = 0
if (p - ptmp > 0) {
//找到一个逗号后,将逗号之前的字符串拷贝到二级指针指向的空间中
strncpy(*(myp + tmpcount), ptmp, p - ptmp);
//在截出来的字符串后边添加字符串结束符\0
*(*(myp + tmpcount) + (p - ptmp)) = '\0';
tmpcount++;
ptmp = p = p + 1;
/*strncpy(myp[tmpcount], ptmp, p - ptmp);
myp[tmpcount][p - ptmp] = '\0';
tmpcount++;
ptmp = p = p + 1;*/
}
}
else {
break;
}
} while (*p != '\0');
*count = tmpcount;
return *count;
}
void main() {
int ret = 0, i = 0;
char *p1 = "abcdef,acccd,eeee,aaaa,e3eeeee,sssss,ssccccccc,";
char cTemp = ',';
int count;
//开辟空间
char **p = NULL;
p = (char **)malloc(10 * sizeof(char *));
if (p == NULL) {
return;
}
for (i = 0; i < 10; i++) {
*(p + i) = (char *)malloc(sizeof(char)*30);
//p[i] = (char *)malloc(sizeof(char) * 30);
}
ret = splitString(p1, cTemp, p, &count);
printf("得到了%d个元素\n", count);
if (ret == 0)
{
printf("error\n");
}
for (i = 0; i < count; i++) {
printf("%s\n", *(p + i));
}
//回收内存
for (i = 0; i < 10; i++) {
if (*(p + i) != NULL) {
free(*(p + i));
*(p + i) = NULL;
}
}
if (p != NULL)
{
free(p);
p = NULL;
}
//释放
/*for (i = 0; i < num; i++) {
if (p2[i] != NULL) {
free(p2[i]);
p2[i] = NULL;
}
}
if (p2 != NULL)
{
free(p2);
p2 = NULL;
}*/
system("pause");
}
12.定义数组指针的三种方式
1)通过数组类型定义数组指针
typedef int(ArrayType)[5];
ArrarType *pointer;
- 声明一个数组指针类型
typedef int (*MyPointer)[5];
MyPointer myPoint; - 直接定义
int(*pointer)[n];
ponter为数组指针变量名
type (int)为指向的数组的类型
n 为指向的数组的大小
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void main() {
//直接定义一个指向数组的数组指针变量
int(*PArrayType)[5];
//定义一个数组,相当于一级指针
int myArray2[5];
//数组指针指向这个数组,相当于二级指针
PArrayType = &myArray2;
int i = 0;
for (i = 0; i < 5; i++) {
(*PArrayType)[i] = i + 1;
}
for (i = 0; i < 5; i++) {
printf("%d", (*PArrayType)[i]);
}
system("pause");
}
void main2() {
char *MyArray[] = {"1111","2222","3333"};
//数组指针,一个指针,指向了数组
//定义了一个数组数据类型
typedef int(MyArrayType)[5];
int i = 0;
//用类型定义变量,相当于int myArray[5]
MyArrayType myArray;
//定义一个指针变量,这个变量指向数组
MyArrayType *pArray;
//定义一个数组,相当于一级指针
int myArray2[5];
//数组指针指向这个数组,相当于二级指针
pArray = &myArray2;
for (i = 0; i < 5; i++) {
(*pArray)[i] = i + 1;
}
for (i = 0; i < 5; i++) {
printf("%d", (*pArray)[i]);
}
system("pause");
}
网友评论