一、指针与数组
1) 数组名就是数组的首地址(第一个元素的地址)。数组名是常量。
int a[10];
int b;
a = &b;//数组名是常量,不能被赋值
a++;//++给变量自身的值+1,a是常量,不能使用++ -- += -= *= /= %=
指针变量是存放地址的变量。
2) 数组中的元素在内存中连续存放。
地址的偏移运算,数组首地址+1得到谁的地址?数组中第二个元素的地址。
可以通过首地址进行偏移运算,得到每个元素的地址。
#include <stdio.h>
int main()
{
int a[5];//定义int类型的数组,5个元素
int i;
printf("a = %p\n", a);//打印数组名,数组名是数组的首地址,所以使用%p
//遍历数组,打印每个元素的地址
for(i = 0;i < 5;i++)
{
printf("%p\n", &a[i]);
//观察打印结果,发现&a[0]和a是一样的,实际上他们俩就是一回事
//观察发现给个元素的地址相差4字节,因为元素类型是int类型
}
return 0;
}
思考:既然数组名是地址,能不能偏移运算?能不能间接运算?
int main()
{
int a[5];
int i;
for(i = 0;i < 5;i++)
{
printf("%p %p\n", &a[i], a+i);// &a[i]是i元素的地址,a+i也是i元素的地址
*(a+i) = i;//a+i得到i元素的地址,*i元素的地址得到i元素本身,=给i元素赋值
//等价于a[i] = i;
}
//遍历数组打印每个元素的值
/*a[i]和*(a+i)是完全等价的,既[]这个运算符是二元运算,左值是地址,右值整数。
[]的算法是先将 地址(左值)+整数(右值) 得到一个新的地址,然后对新的地址进行间接运算,得到新地址 对应的对象本身。
*/
for(i = 0;i < 5;i++)
{
printf("%d %d\n", a[i], *(a+i));//写两遍输出,是为了说明语法,a[i]和*(a+i)是完全等价的
}
return 0;
}
指针与数组基本用法示例:
当指针存放数组首地址的时候,我们说指针指向数组。
数组名的地址类型是?跟数组元素一个类型。
当指针指向一个数组时,指针需要定义成数组的元素类型!!!!!!!!!!
#include <stdio.h>
int main( )
{
int a[10] = {12,23,34,45,56,67,78,9,98,33};
int *p;//p要指向数组a,由于数组a的元素是int,所以指针的类型也是int
p = a;//将数组的首地址赋值给指针
int i;
for(i=0; i<10; i++)
{
//p[i]运算得到数组a的i元素本身
/*
[]是二元运算符,左值是地址,右值是整数
算法:左值+右值得到一个新的地址,然后对新地址间接运算得到新地址对应的对象本身。
p中的数据的a的首地址
p[i]等价于*(a+i)
*/
//p+i运算得到数组a的i元素地址
printf("%d %p\n", p[i], p+i);
}
return 0;
}
指针与数组编程示例1:
使用指针指向一个int数组,使用指针将数组元素求和。输出最终结果。
这道题只是为了练语法。
#include <stdio.h>
int main()
{
int a[5] = {1,2,3,4,5};
int* p = a;//使用数组名初始化指针p
int i, sum = 0;
for(i = 0;i < 5;i++)
{
sum += p[i];//p[i]运算得到数组的i元素本身
}
printf("%d\n", sum);
return 0;
}
指针与数组编程示例2:
定义字符数组,将字符串放在数组中,定义指针指向数组,使用指针输出数组中保存的字符串。
#include <stdio.h>
int main()
{
char a[]="China";
char *p;
p=a;
while(*p!='\0')
{
printf("%c",*p);
p++;
}
return 0;
}
C语言对字符串的处理有两个要素:
-
字符串首地址 作为字符串的开始
-
‘\0’作为字符串的结尾
C语言因为对于字符串的 所有 的操作,都是从字符串的首地址开始,到’\0’结束。
使用%s需要传给它字符串的首地址,它会从首地址开始输出,直到’\0’为止。
int main() { char buf[] = "hello world"; //使用字符串常量初始化数组 char* p = buf;//使用指针指向字符数组buf printf("%s\n", p+6);//world p+6作为输出字符串的首地址,是w的地址,所以从w开始输出,到\0结束 printf("%s\n", buf);//hello world buf是h的地址 return 0; }
关于字符串操作函数
#include <string.h> /* strcpy的函数原型 参数1:字符数组的首地址 参数2:字符串首地址 char* strcpy(char* dst, const char* src); */ int main() { char s1[100]; char* s2 = "hello world"; strcpy(s1, s2+6); printf("%s\n", s1);//world char* p1 = "hello"; char* p2 = "hello"; printf("%d\n", strcmp(p1, p2+1));//1 比较的是hello ello两个字符串,根据字符串比较的原理(第一个不相等字符的ascii码值),实际比较的是h e的大小,所以h>e输出1 return 0; }
数组作为函数参数示例:
数组作为函数的参数,一般传递两个参数。目的是为了在被调函数中操作主调函数中定义的数组。
1 数组的首地址
2 数组的长度
#include <stdio.h>
/*
形参为了传递数组
参数1:数组首地址
参数2:数组元素个数(数组长度)
*/
void print_array(int *p, int n)
{
int i;
for(i=0; i<n; i++)
{
printf("%d, ", p[i]);//使用指针遍历数组
}
printf("\n");
}
int main()
{
int a[6] = {1,2,3,4,5,6};
int b[8] = {6,6,6,6,6,6,6,6};
print_array(a,6);//实参是数组名和数组的元素个数 1 2 3 4 5 6
print_array(b,8);//实参是数组名和数组的元素个数 6 6 6 6 6 6 6 6
return 0;
}
sizeof计算数组长度的错误理解!
sizeof计算数组元素个数,没有用!!!
#include <stdio.h>
void printArr(int* p)
{
//sizeof(p) 这里计算的是指针的长度
//所以使用sizeof计算数组长度,是扯淡,在
//已知数组的地方不需要算,在未知数组长度的地方算不了
printf("printArr:%d\n", sizeof(p)/sizeof(int));
}
int main()
{
int a[10];
//sizeof(数组名)计算数组占内存的大小
//sizeof(类型名)计算元素占内存大小
//数组占内存大小/元素占内存大小=数组元素个数
//但是,我们为什么要计算数组的元素个数?
//在定义数组的时候,长度必须以常量形式写清楚,我们没有计算数组长度的需求
printf("main:%d\n", sizeof(a)/sizeof(int));
printArr(a);
return 0;
}
指针与数组编程示例3:
编写函数fun:从n个学生的成绩中统计出低于平均分的学生人数,和平均分。
我们一般是使用返回值将函数产生的结果返回给主调函数,但是,返回值只能返回一个值,如果函数产生多个结果,仅凭返回值是不够用的。
所以我们经常使用指针类型的形参,将结果写给主调函数中的变量。
由返回值返回人数,平均分存放在形参 ptr_aver 所指的存储单元中(即平均分由参数返回)。
ptr_aver 称为输出参数。参数的目的是为了让被调函数将数据传给主调函数。
scanf就是我们遇到过的输出参数的典型应用。
int a;
scanf("%d", &a);//将被调函数scanf中读取到的值写给主调函数中的变量a
//返回值int类型,表示低于平均分的人数
//参数1和参数2是传数组给被调函数
//参数3:输出参数,用来将平均分写给主调函数中的变量
int getAvgAndNum(int* arr, int n, float* ptr_aver);
#include <stdio.h>
int getAvgAndNum(int* arr, int n, float* ptr_aver);
int main()
{
int a[] = {1,2,3,4,5,6,7,8,9,10};
float aver;
int count = getAvgAndNum(a, 10, &aver);
printf("%d %f\n", count, aver);
return 0;
}
int getAvgAndNum(int* arr, int n, float* ptr_aver)
{
float sum = 0;
int i;
for(i = 0;i < n;i++)
{
sum += arr[i];
}
*ptr_aver = sum/n;//除法中只要有一个数值小数,就是小数相除
//float a = 20/8;//a值2.000,因为20/8得整数2,将整数2赋值给a
int count = 0;
for(i = 0;i < n;i++)
{
if(arr[i] < *ptr_aver)
{
count++;
}
}
return count;
}
指针与数组编程示例4:
编写函数,实现字符串追加 功能。但不能调用字符串函数;
char s1[20] = “hello ”;
char s2[] = “world”;
调用函数后,将world放到s1数组的”hello ”后面
知识点:当数组存放字符串时,作为函数的参数只需要传递首地址,因为字符串有’\0’作为结尾。
不允许使用C语言的字符串处理函数!!!
void mystrcat(char* dst, char* src);
#include <stdio.h>
void mystrcat(char* dst, char* src);
int main()
{
char s1[100] = "hello ";
char* s2 = "world";
mystrcat(s1, s2);
printf("%s\n", s1);
return 0;
}
void mystrcat(char* dst, char* src)
{
int i = 0;
while(dst[i] != '\0')
{
i++;
}
//循环结束后i在dst的\0位置
int j = 0;
while(src[j] != '\0')
{
dst[i++] = src[j++];
}
dst[i] = '\0';
}
逻辑能写成并列的就不写成嵌套的!
太大的函数一定要拆分成若干小函数!
指针与数组编程示例5:
编写一个函数,将数组当做参数,将数组中n个数倒序。
12 34 56 32 6
6 32 56 34 12
#include <stdio.h>
void reverse(int* arr, int n);
int main()
{
int a[10] = {1,3,5,7,9,0,8,6,4,2};
reverse(a, 10);
int i;
for(i = 0;i < 10;i++)
{
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
void reverse(int* arr, int n)
{
int i;
for(i = 0;i < n/2;i++)
{
int t = arr[i];
arr[i] = arr[n-1-i];
arr[n-1-i] = t;
}
}
指针与数据编程示例6:
编写一个函数,可以对任意整型数组进行升序排序。
这题就属于封装固定的算法。
#include <stdio.h>
void sort(int* arr, int n);
int main()
{
int a[10] = {12,34,56,78,54,32,2,6,8,43};
sort(a, 10);
int i;
for(i = 0;i < 10;i++)
{
printf("%d ", a[i]);
}
printf("\n");
return 0;
}
void sort(int* arr, int n)
{
int i, j;
for(i = 0;i < n-1;i++)
{
for(j = 0;j < n-1-i;j++)
{
if(arr[j] > arr[j+1])
{
int t = arr[j];
arr[j] = arr[j+1];
arr[j+1] = t;
}
}
}
}
指针与数据编程示例7:
对35选7进行封装
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void CreateWinnum(); //生成中奖号码
void UserInputnum(); //用户输入自己选中的号码
void PrintNum(int *arr); //打印号码
int ReplNum(int *arr,int n); //判断生成和输入的号码是否合法
int Wincount(int *Win,int *User); //记录中奖号码个数
void printwin(); //打印中奖信息
int winnum[7]={0}; //存储生成的奖号
int usernum[7]={0}; //存储用户输入的号码
int WinCount=0; //记录中奖号码个数
int main()
{
srand(time(0));
CreateWinnum();
UserInputnum();
PrintNum(winnum);
PrintNum(usernum);
Wincount(winnum,usernum);
printwin();
return 0;
}
//生成中奖号码
void CreateWinnum()
{
for (int i = 0; i < 7; ++i) {
winnum[i]=rand()%35+1;
if( ReplNum(winnum,i) )
{
--i;
}
}
}
//用户输入自己选中的号码
void UserInputnum()
{
printf("请用户输入所选号码:\n");
for (int i = 0; i < 7; ++i) {
scanf("%d",&usernum[i]);
if(usernum[i]<0 || usernum[i]>36)
{
--i;
printf("超出可选范围,请重新输入!\n");
}
else if(i>0 && ReplNum(usernum, i))
{
--i;
printf("与前面号码重复,请重新输入!\n");
}
}
}
//打印号码
void PrintNum(int *arr)
{
for (int i = 0; i < 7; ++i) {
printf("%d ",arr[i]);
}
printf("\n");
}
//判断生成和输入的号码是否在允许的范围内并且是否与前面的重复
int ReplNum(int *arr,int n)
{
int count=0;
if(arr[n]< 0 || arr[n]>35)
{
return 1;
}
else if(n>0)
{
for (int i = 0; i < n; ++i)
{
for (int j = i + 1; j < n + 1; ++j)
{
if (arr[i] == arr[j])
{
count++;
break;
}
}
}
if(count)
{
return 1;
}
}
return 0;
}
//记录中奖号码个数
int Wincount(int *Win,int *User)
{
for (int i = 0; i < 6; ++i) {
for (int j = i+1; j < 7; ++j) {
if(Win[i]==User[j])
WinCount++;
}
}
}
//打印中奖信息
void printwin()
{
if (WinCount)
{
int sum=10;
for (int i = 0; i < WinCount; ++i) {
sum*=10;
}
printf("恭喜您中了%d的大奖\n",sum);
}
else
{
printf("抱歉,很遗憾您没中奖!");
}
}
二、空指针和野指针
野指针:
空指针:
#include <stdio.h>
int main()
{
int* p;
*p = 10;
int* p1 = NULL;
*p1 = 10;
return 0;
}
#include <stdio.h>
int main()
{
int a[5] = {1,2,3,4,5};
int i;
for(i = 0;i < 6;i++)
{
a[i] = i*-1;
}
return 0;
}
三、内存分配
1. 内存分区(变量可能被分配的内存分区)
栈:内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。它主要存放静态数据、全局数据和常量
堆:内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。它主要存放静态数据、全局数据和常量
静态:内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。它主要存放静态数据、全局数据和常量
2. 堆空间内存的申请与释放
#include <stdlib.h>
1) 申请内存: void *malloc(size_t size);
参数:申请内存的大小
返回值:无类型指针
2) 释放内存: void free(void *p);
参数:malloc申请内存的返回值
3. 使用注意事项:
1) malloc 和 free 成对去写。(malloc 申请的空间,使用结束后,要及时的free )
malloc不free会造成 内存泄漏。
2) malloc 申请内存,会成功或失败,如果成功,则可以使用申请来的堆空间。 如果失败,会返回 NULL
NULL就是0 空指针。内存不够就会失败。
3) 在堆内存被释放掉之后,及时将指针 p 归零,以防出现使用这块已经被释放掉的内存,造成不必要的风险。
示例:
#include <stdio.h>
#include <stdlib.h>
int main()
{
//int *p = (int *)malloc(10*sizeof(int));
void* pv = malloc(10*sizeof(int));
int* p = (int*)pv;
if(p == NULL)
{
printf("error\n");
return 0;
}
int i;
for(i=0; i<10; i++)
{
scanf("%d", &p[i]);
}
for(i=0; i<10; i++)
{
printf("%d, ", p[i]);
}
free(p);
p = NULL;
return 0;
}
四、位运算
为什么使用位运算?
1.左移和右移
左移示例
#include <stdio.h>
int main()
{
char a = 1;
a = a<<1;
printf("%d\n", a);
a = a<<2;
printf("%d\n", a);
return 0;
}
右移示例
#include <stdio.h>
int main()
{
unsigned char a = 8;
a = a>>1;
printf("%d\n", a);
a = a>>2;
printf("%d\n", a);
return 0;
}
2.按位取反
~
将变量中所有的位,1变成0,0变成1
#include <stdio.h>
int main()
{
unsigned char a = 0x01;
a = ~a;
printf("%#x\n", a);
return 0;
}
3.按位与
& 双目运算 对应的位,有一个为0结果就是0
#include <stdio.h>
int main()
{
unsigned char a = 0xA5;
unsigned char b = 0x8C;
unsigned char c = a&b;
printf("%#x\n", c);
return 0;
}
按位与的作用
1)给指定的位写0
unsigned char a;
a = a&~(1<<n);//将变量a的n位写0
#include <stdio.h>
int main()
{
unsigned char a = 0xA5;
a = a&~(1<<5);
printf("%#x\n", a);
return 0;
}
2)读取指定位的值
if((a&1<<n) == 0)
{
//a的n位是0
}
else
{
//a的n位是1
}
读取示例
#include <stdio.h>
int main()
{
unsigned char a = 0xA5;
int i;
for(i = 0;i < 8;i++)
{
if((a&1<<i) == 0)
{
printf("a的%d bit is 0\n", i);
}
else
{
printf("a的%d bit is 1\n", i);
}
}
return 0;
}
4.按位或
| 指定的二进制位,写1
a = a|1<<n;
#include <stdio.h>
int main()
{
unsigned char a = 0xA5;
a = a|1<<3;
printf("%#x\n", a);
return 0;
}
5.按位异或
^ 指定位取反 对应的位,相同得0,不同得1
a = a^1<<n;
1010 0101
0000 1000
----------
1010 1101
#include <stdio.h>
int main()
{
unsigned char a = 0xA5;
a = a^1<<2;
printf("%#x\n", a);
a = a^1<<2;
printf("%#x\n", a);
return 0;
}
五、宏定义(无参)
为什么使用宏定义?
#define STUDENT_NUM 45
#define CHAIR_NUM 45
#define PC_NUM 45
for(i = 0;i < 45;i++)
{
}
#include <stdio.h>
#define STUDENT_NUM 10
int main()
{
int num = STUDENT_NUM;
printf("%d\n", num);
return 0;
}