目录
以下程序发生死循环
/*
* - i 和arr都是局部变量,局部变量存放在栈区上。
* - 栈区内存的使用习惯是:先使用高地址空间,再使用低地址空间
* - 数组随着下标的增长地址是由低到高变化的。
*
* - 如果是先定义的i,再定义的数组。那么数组越界就可能会访问到i变量。就可能造成死循环
* VC6.0h环境,数组后面就是i变量地址
* gcc环境,数组后面过一个整型,才是i变量的地址
* vs2013,数组与i变量地址中间,是两个整型
*/
#include <stdio.h>
int main()
{
//因为这里是先定义i变量,所以i在高地址。后定义的arr,arr数组的下标如果越界,则会访问到i变量所在的地址
//这里使用的是gcc编译器,数组过后一个整型,就是i变量。也就是下标为11的就是i变量所在地址。
//每次当i=11时,arr[11]=0,此时就会死循环。
long i = 0;
long arr[16] = {1,2,3,4,5,6,7,8,9,1};
for(i=0; i <= 17 ; i++)
{
arr[i] = 0;
printf("hehe\n");
}
return 0;
}
我们自己调试代码一般都是在Debug模式下,但提交的时候是在Release模式下的,所以Debug模式下没有出现的问题,Release模式下可能出现。
#include <stdio.h>
int main()
{
int i = 0;
int arr[10] = {0};
// for(i=0 ; i<=12 ; i++)
// {
// arr[i] = 1;
// printf("haha\n");
// }
//Debug模式下不做优化,i的地址在数组地址的后面,相差很少,一旦越界,就可能造成以上程序死循环。
//printf("%p\n",&i); //0000008b755ffcac
//printf("%p\n",&arr[9]); //0000008b755ffca4
//Release模式下会做优化,i的地址放到了数组地址的前面,并且相差很多,就算数组越界,也不会造成程序死循环。
printf("%p\n",&i); //000000226cbffd0c
printf("%p\n",&arr[9]); //000000226cbffd34
}
字符串拷贝
#include <stdio.h>
void myStrCpy(char* dest,char* src)
{
//当不是字符串结尾的时候拷贝
while(*src != '\0')
{
*dest++ = *src++;
}
//执行到这里,*src中存储的就是\0了,*dest也++了,虽然此时循环停止了,但是我们可以手动拷贝\0到dest中
*dest =*src;
}
int main()
{
char arr1[20] = "abcdefgh";
char arr2[] = "hello";
myStrCpy(arr1,arr2);
printf("%s",arr1);
return 0;
}
简化
#include <stdio.h>
void myStrCpy(char* dest,char* src)
{
//当没有到达字符串结尾的时候,循环一直进行。这里是后置++,先赋值再运算。
//()中表达式的结果就是*src的结果。例如:b=3,a=b这个表达式的结果应该是a,但是a=b,也就是说b就是这个表达式的结果。
//当src指向的是'h'时,它对应的ASCII码是:104。非零条件为真,执行空语句。此时后置++生效,继续判断()中的结果
//只有当src='\0'的时候,也就是dest='\0',字符串结尾符'\0'拷贝了过去,字符串拷贝完成。而'\0'的ascii码就是0,也就是条件为假,循环结束。
while(*dest++ = *src++);
//参考:
//int i = 1;
//printf("%d\n",i='\0'); //0
}
int main()
{
char arr1[20] = "abcdefgh";
char arr2[] = "hello";
myStrCpy(arr1,arr2);
printf("%s",arr1);
return 0;
}
断言的使用
#include <stdio.h>
#include <assert.h>
void myStrCpy(char* dest,char* src)
{
//当程序执行到断言的位置时,对应的断言应该为真。若断言不为真时,程序会中止执行,并给出错误信息。
//当传入的两个数组中有一个是空指针,则输出错误信息
assert(dest != NULL);
assert(src != NULL);
while(*dest++ = *src++);
}
int main()
{
char arr1[20] = "abcdefgh";
char arr2[] = "hello";
myStrCpy(arr1,arr2);
//myStrCpy(arr1,NULL);当传入NULL,则会报错
//Assertion failed: src!=NULL, file D:/Program Files (x86)/CLion 2021.3.1/Project/First/pointer.c, line 42
printf("%s",arr1);
return 0;
}
const修饰指针变量
#include <stdio.h>
int main()
{
int nu = 66;
//const修饰的变量,被称为常变量,不能被修改。但其本质上还是变量,只是增加了不可被修改的属性。
const int num = 10;
//在使用const修饰之后,num无法被直接修改了,修改会报错:assignment of read-only variable 'num'
//num = 30;
//但是我们取地址放到指针中后,就可以修改。
//int* p = #
//*p = 20;
//p是指针变量,*p是指针变量指向的变量。
//const修饰指针变量的时候,如果const是放在*的左边,修饰的是*p,表示指针指向的内容,不能通过指针来改变的。
//const int * p 与int const * p使用起来没有差异。只要是在*的左边就是修饰*p的
//const int* p = #
//*p = 20; //不能通过指针来修改
//p = ν//指针变量指向哪里是可以修改的。原来指向num变量,现在可以指向nu变量
//*p = 200; //但仍然不可以使用*p,报错:assignment of read-only location '*p'
//printf("%d\n",nu);
//const修饰指针变量,如果是放在*的右边,修饰的是指针变量p,表示指针变量不能修改指向哪里。
//int * const p = #
//p = ν//指针变量指向哪里是不可修改的。原来指向num变量,就只能指向num变量。不能再指向其他变量。
//*p = 40; //但是可以修改其指向的变量的内容。
//printf("%d\n",num);
//如果*左边和右边都有const了,*左边的const修饰*p,表示指针变量指向的变量不可以修改。*右边的const修饰p,表示p不能修改指向的位置。
const int * const p = #
//*p = 20;
//p = ν
//但如果是将其指针变量的地址取出来,保存到一个指针变量中,那么通过二级指针就可以修改p与*p。
// int** pp = &p;
// ** pp = 555;
// printf("%d\n",num);
// //可以修改指向哪个变量
// *pp = &nu ;
// **pp = 666;
// printf("%d\n",nu);
//如果是二级指针使用const,则最左边的const是修饰**pp的,中间的const是修饰*pp的,最右边的const是修饰pp的。
int const * const * const pp = &p;
//三级指针以此类推......
return 0;
}
继续优化我们写的strcpy函数
#include <stdio.h>
#include <assert.h>
//库函数中的字符串拷贝函数是char * strcpy ( char * destination, const char * source );
//它的形参在源数组前加上了一个const,为什么要加这个东西呢?
//我们拷贝是要把src指向的内容放入dest指向的空间中,从本质上讲,是希望dest指向的内容被修改,而src指向的内容不修改。
//也就是*src不能被修改,所以在*的左边加上了const,表示*src不能被修改。
void myStrCpy(char* dest,const char* src)
{
assert(dest != NULL);
assert(src != NULL);
//const表示被修饰的变量不可被修改。防止我们在编写的过程中写反。
// 也就是说如果我们这里写反了,那么编译都不会过去,报错:assignment of read-only location '*src++'
//while(*src++ = *dest++);
while(*dest++ = *src++);
}
int main()
{
char arr1[20] = "abcdefgh";
char arr2[] = "hello";
myStrCpy(arr1,arr2);
printf("%s",arr1);
return 0;
}
strcpy优化最终版
#include <stdio.h>
#include <assert.h>
//库函数中的字符串拷贝函数是char * strcpy ( char * destination, const char * source );
//库函数返回类型是一个指针,返回的是目标的起始地址。
char* myStrCpy(char* dest,const char* src)
{
assert(dest != NULL);
assert(src != NULL);
//我们定义一个指针,接收传入函数的目标起始地址,保存到变量中。
//使用ret进行拷贝地址的变化。
char* ret = dest;
while(*ret++ = *src++);
//拷贝完成后返回起始地址。
return dest;
}
int main()
{
char arr1[20] = "abcdefghjklmn";
char arr2[] = "hi baby!";
printf("%s",myStrCpy(arr1,arr2));
return 0;
}
自己编写strlen
//是求其长度,所以*str不可以被修改。
//这里可以设置为size_t类型,也就是unsigned int,无符号整型
size_t myStrLen(const char* str)
{
size_t count = 0;
assert(str != NULL); //也可以写成assert(str),因为字符串如果为空的话,就是'\0',0就是假。
while(*str++ != '\0')
{
count++;
}
return count;
}
int main()
{
char arr1[20] = "abcdefghjklmn";
char arr2[] = "hi baby!";
printf("%d",myStrLen(arr2));
return 0;
}
错误出现的类型、Debug与Release的区别
-
C程序常见的错误
- 编译错误:语法出现问题 - 链接错误:一般是标识符不存在或拼写错误。 - 运行时错误:逻辑错误。栈溢出也属于运行时错误
-
Debug和Release的区别
- Debug被称为调试版本,程序调试找BUG的版本 - Release被称为发布版本,测试人员测试的就是Release版本 - Debug版本包含调试信息,不做优化。 - Release版本不可以调试。但是会做优化,程序大小和运行速度上效果最优。
编写方法,调整奇偶数顺序
编写方法,调整奇数偶数顺序,使奇数都位于偶数的前面。也就是说奇数位于前半部分,偶数位于后半部分。
#include <stdio.h>
void print(int arr[], int sz)
{
int i;
for (i = 0; i <sz ; ++i) {
printf("%d ",arr[i]);
}
printf("\n");
}
void move(int arr[],int sz)
{
//起始下标
int left = 0;
//结束下标
int right = sz-1;
while(left < right)
{
//与2取余,如果等于1,则说明是奇数。如果是奇数,则left+1,然后继续循环,知道碰到偶数才停下。
//注意:当数组中出现全部都是奇数的情况时,这里就可能出现越界的情况,所以加上判断条件:left<right
while(left<right && arr[left]%2 == 1)
{
left++;
}
//与2取余,如果等于0,则说明是偶数。如果是偶数,则left-1,然后继续循环,知道碰到奇数才停下。
//注意:当数组中出现全部都是偶数的情况时,这里就可能出现越界的情况,所以加上判断:right>0。
//但是如果全是偶数的话,那么left一定是比0大的,所以判断简化为:left<right
while(left<right && arr[right]%2 == 0)
{
right--;
}
//如果left在right的左边再交换
if(left < right)
{
int tmp = arr[left];
arr[left] = arr[right];
arr[right] = tmp;
}
}
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int sz = sizeof(arr)/sizeof(arr[0]);
print(arr,sz);
move(arr,sz);
print(arr,sz);
return 0;
}
//另外两种思路
//1、把找到的奇数放到首元素位置,其他的顺延。//个人感觉顺延要动整个数组的元素,难以实现,即使实现了,当数组中元素过多时,则效率可能会变低。
//2、再创建一个等长的数组。找到奇数就放从前往后放;找到偶数就从后往前放。//比较麻烦,效率较低。