介绍数组和结构、函数。指针,以运算和存储(定义)为主线。这也是计算机这个黑盒子中的关键三环中的两环。(存储、运算、控制)
二维数组a[i][j],除了行与列的解释,还可以i代表第i个一维数组,j代表一维数组中第j个元素。
运算:(1)二维数组不可以缺省列的个数。int a[][3]={1,2,3,4,5,6}就是可行的赋值的,区别于,函数的形参与实参中,第二维
必须相同int arry[][4]。
(2)对一维数组而言,a是数组名,可视为列指针,a+1就是跳一列。
(3)对二维数组而言,可视为行指针,a+1就是跳一行。a[0]、a[1]这些都应该视为列指针。
(4)a是地址常量,不可以a++或者a=a+2;二维的a[0]、a[1]也是不可以进行赋值操作,都是地址常量。
赋值:(1)字符赋值char s[5]={‘a’,‘a’,‘a’,‘a’,‘a’};(2)字符串常量赋值char s[5]=“aaaaa”;或char s[]=“aaaaa”;
未赋值部分默认为’\0’,ASCII为0;
注:
(1)gets可以读取空格,但是scanf不可以;puts自带换行功能,输出一个字符串后就会换一行。
(2)改错题中,常出现,为加’\0’作为字符串结束标志。
(3)对于s[]和s[n],可以通过修改’\0’来增加字符数组长度。
(4)对于s[n],只可以放n-1个字符。
注:
(1)结构体中范式没有明确初值的成员,都默认为0;
(2)atoi、atof装换字符串为整数/浮点数
(3)优先级的细节,++p->age,(++p)->age,&p->age,第一个是age++;第二个是p++,第三是age的地址。
注:改错题中,会出现结构体定义结尾没有‘;’,do…while();也是!
将一个结构体变量中的数据传递给另一个函数,有下列3种方法:
1、用结构体变量名作参数。一般较少用这种方法。
2、用指向结构体变量的指针作实参,将结构体变量的地址传给形参。
3、用结构体变量的引用变量作函数参数。
#include <iostream>
#include <string>
using namespace std;
struct Student//声明结构体类型Student
{
int num;
char name[20];
float score[3];
};
int main( )
{
void print(Student); //函数声明,形参类型为结构体Student
Student stu; //定义结构体变量
stu.num=12345; //以下5行对结构体变量各成员赋值
stu.name="Li Fung";
stu.score[0]=67.5;
stu.score[1]=89;
stu.score[2]=78.5;
print(stu); //调用print函数,输出stu各成员的值
return 0;
}
void print(Student st)
{
cout<<st.num<<" "<<st.name<<" "<<st.score[0]
<<" " <<st.score[1]<<" "<<st.score[2]<<endl;
}
2)用指向结构体变量的指针作实参在上面程序的基础上稍作修改即可。
#include <iostream>
#include <string>
using namespace std;
struct Student
{
int num; string name; //用string类型定义字符串变量
float score[3];
}stu={12345,"Li Fung",67.5,89,78.5}; //定义结构体student变量stu并赋初值
int main( )
{
void print(Student *); //函数声明,形参为指向Student类型数据的指针变量
Student *pt=&stu; //定义基类型为Student的指针变量pt,并指向stu
print(pt); //实参为指向Student类数据的指针变量
return 0;
}
//定义函数,形参p是基类型为Student的指针变量
void print(Student *p)
{
cout<<p->num<<" "<<p->name<<" "<<p->score[0]<<" " <<
p->score[1]<<" "<<p->score[2]<<endl;
}
#include <iostream>
#include <string>
using namespace std;
struct Student
{
int num;
string name;
float score[3];
}stu={12345,"Li Li",67.5,89,78.5};
int main( )
{
void print(Student &);
//函数声明,形参为Student类型变量的引用
print(stu);
//实参为结构体Student变量
return 0;
}
//函数定义,形参为结构体Student变量的引用
void print(Student &stud)
{
cout<<stud.num<<" "<<stud.name<<" "<<stud.score[0]
<<" " <<stud.score[1]<<" "<<stud.score[2]<<endl;
}
程序(1)用结构体变量作实参和形参,程序直观易懂,效率是不高的。
程序(2)采用指针变量作为实参和形参,空间和时间的开销都很小,效率较高。但程序(2)不如程序(1)那样直接。
程序(3)的实参是结构体Student类型变量,而形参用Student类型的引用,虚实结合时传递的是stu的地址,因而效率较高。它兼有(1)和(2)的优点。
(1)结构体中含字符的排序会用到strcmp而且最好是直接插入排序,边输入边比较(代码如下)
stu[0]=stu[i];
for(j=i-1;strcmp(stu[0].name,stu[j].name)<0;j--)
stu[j+1]=stu[j];
stu[j+1]=stu[0];//注意是j+1,因为j是比待插入数小,要放在j+1这个位置
详见p174例题4-13
(2)在含字符的结构体中查找,最好用strstr
4-6
输入一行字符,统计其中包含多少字符组(不含空格),字符组间用空格隔开。【原题是单词,但是给的参答其实是指字符组,对于单词处理还需要对a-z、A-Z进行判断,比较繁琐】
#include<iostream>
#include<stdio.h>
using namespace std;
int main(){
char str[200];
int i;
int space=0;//空格标志,0表示新空格,1表示连续空格
int num=0;
printf("请输入字符串:");
gets(str);
if(str[0]==' ')
space=1;
i=0;
while(str[i])
{
if(str[i]==' ')
{
if(space==0)
{
space=1;
num++;
}
}
else space=0;
i++;
}
if(space==0) //若单词不是以空格结尾,则+1,比如”ABC .“则“.”算一个字符组
num++;
printf("total %d",num);
return 0;
}
4-12
#include<iostream>
#include<stdio.h>
#include<cmath>
using namespace std;
int p[2000],k=0;
int fac(int n)
{
for(int i=2;i<=sqrt(n);i++)
{
if(n%i==0) return 0;
}
return 1;
}
void init()
{
for(int i=2;i<=2000;i++)
if(fac(i)) p[k++]=i;
}
int main(){
init();//初始化素数数组
int goal[2000],j=k-2;
for(int i=k-2;i>=0;i--)
goal[j--]=p[i+1]-p[i]; //构造目标数组
for(int i=0;i<=k-2;i++) //枚举出所有连续子数组组合
{
int sum=goal[i];
int j;
for(j=i+1;j<=k-2 && sum<1898;j++)
sum+=goal[j];
if(sum==1898){
printf("%d-%d ",p[i],p[j]);
printf("\n");
}
}
return 0;
}
c程序设计中使用指针:
(1)使程序简洁、紧凑、高效
(2)有效地表示复杂的数据结构
(3)动态分配内存,能直接处理内存地址
(4)得到多于一个函数返回值
指针变量形式:[存储类型]数据类型 *指针名=初始地址值; 存储大小4B
变量是对程序中数据存储空间的抽象。
(1)指针 * p:指向某一类型的变量。
(2)指针变量p:专门存放地址的变量。
注:(1)存储类型区别!比如不可以用auto变量的地址去初始化static型指针。
重点(2)需要赋初地址值,改错题常考。
(3)数组名是指针常量不可以赋值运算。一个数组名实际上是一个指针常量。
(4)二维数组中,一维数组名就是行指针。
如下:
二维数组中的指针,
行指针:a+1;
列指针:&a[1][0],a[1],*(a+1),(int *)(a+1).
零指针与空类型指针:
零指针:指针变量值为零。
空类型指针:不指定p是指向哪一种类型数据的指针变量。使用时,要进行强制转化,void *p;
(1)对指针加1的含义:char类型,当前地址加1个字节;int类型,当前地址加2个字节;float类型,当前地址加4个字节。
重点(2)*p++:与++运算等级一样,运算符的结合顺序是从右往左,先p,再(p++);
比如v=*p++,则先v=*p,p++;
* q++= * p++,先 * q=*p,再p++、q++。
(3)指针的关系运算:
< 或 > 地址大小比较;
==或!= 是否指向统一数据/地址
(4)将一个类型的指针赋给另一个类型的指针需要进行指针间的强制转化。
指向一维数组的指针变量——地址变量:
定义形式: 数据类型(*指针名)[一维数组维数]
(1)int (* p)[4],叫数组指针,对于二维数组p就是指向列有四个元素的的行指针。
如,* (* p+j)->p[0][j];p[1]+2->*(p+1)+2;
(2)内存空间上,假如p只占2字节,而数组占2 * i * j字节。
(3)对于数组a,a={1,2,3,4}就是错的,p/*p={1,2,3}也是错的。
(1)指向字符串常量的指针,可以直接对地址变量赋值:char*p=“China”;但是地址常量char str[20],就不可以str=“China”。有printf(“%s”,p);等效于printf(“%s”,str);
(2)指向字符串常量的指针,又叫字符指针变量,可以反复赋值;char *p=“ASF”; p=“sdasff”;
定义:数组中的元素为指针变量。
形式:[存储类型] 数据类型数组名[数组长度];
比如:int * p[4];[]的运算优先级高于,先p[4],再数组与*结合。
int b[2][3],*p[2];
第一种赋值:p[0]=b[0];p[1]=b[1];
第二种赋值:int *p[]={b[0],b[1]};//这里的b[]就代表该行,如果是char就是字符串。
访问方式:
对第i+1个字符串:status[i];
对第i+1个字符串中的第j+1 *(status[i]+j);
(1)指针数组元素的作用相当于二维数组的行名
(2)指针数组中元素是指针变量
(3)二维数组的行名是地址常量
char *name[]={“foll”,“basic”,“great”,“for”,“com”};
printf("%s %p %d\n",name[0],name[0],name[0]);由于指针数组相当于行名,%s会输出该行整个字符串;%p对应于当前name[]本身的首地址;%d对应于name[]指向的数组的行首地址。
(1)指针数组与指针变量:char *str[];可以用来对字符串进行排序!并用strcmp(str[i],str[j])来比较,意思是比较第i个字符串和第j个字符串。
(2)字符串处理,第一种二维数组,第二种字符型指针数组
例题5-23 通过指针对一段内存进行访问,并输出其内存内容
#include<iostream>
#include<stdio.h>
#include<cstring>
using namespace std;
int main()
{
char *point;//char可以一个字节的对内存进行访问
long int b_add,e_add,i,j;
printf("enter the beginning and the end addr\n");
scanf("%lx%lx",&b_add,&e_add);
for(i=b_add;i<e_add;i++)
{
printf("%05lx",i);//当前开始地址
point=(char*)i;
for(j=0;j<16;j++)
{
if(j==8) printf(" ");
printf("%02x",*point);
point++;
}
printf("\n");
}
return 0;
}
int *p[3]:指针数组。内存空间2字节
int (p)[3]:数组指针。内存空间23字节
int *p(int):指针函数,返回指针型
int (*p)(int):函数指针,函数返回int型变量。
int (*p[3])(int):函数指针数组,函数返回int型变量。
赋值运算:
(1)把变量地址赋予指针变量 p=&a;
(2)同类型指针变量相互赋值 p=s;
(3)把数组,字符串的首地址赋予指针变量。 p=str;
(4)把函数入口地址赋予指针变量。
int func(int x); // 声明一个函数
int (*f) (int x); // 声明一个函数指针
f=func; // 将func函数的首地址赋给指针f
赋值时函数func不带括号,也不带参数,由于func代表函数的首地址,因此经过赋值以后,指针f就指向函数func(x)的代码的首地址。
5-3
变式:
int main(){
char *p[]={"one","two","three","four"};
while(*p[2]!='\0')//加入一个循环
printf("%s",p[2]++);
return 0;
则p[2]++表示地址加1(char类型),printf输出到‘\0’结束,则结果为:
three hree ree ee e
5.6 编写一个程序输入两个字符串string1 和string2,检查在string1 中是否包含有string2。如果有,则输出string2 在string1 中的起始位置;如果没有,则显示“NO”;如果string2在string1 中多次出现,则输出在string1 中出现的次数以及每次出现的起始位置,例如:
stringl=“the day the month the year”;
string2=“the”
输出结果应为:出现三次,起始位置分别是:0,8,18
又如:
stringl=“aaabacad”
string2=“a”
输出结果应为:出现五次,起始位置分别是0,1,2,4,6
#include<iostream>
#include<stdio.h>
#include<cstring>
using namespace std;
int main()
{
char string1[100],string2[100];
gets(string1);
gets(string2);
int n=0;
char *p,*t;
p=string1;
t=string1;
while(*t)
{
t=strstr(t,string2); //找出第一个相同的绝对位置
//方法二:t=strstr(string1,string2);
n=t-p; //找出相对位置
if(*t){ //有返回值不为null时
//方法二:if(t)
printf("%d ",n);
t++;
//方法二:string1[n]++;修改当前对应的第一位便于找之后的相同字符串
}
else break;
}
return 0;
}
是具有一定功能的一个程序块,是程序(比如c语言)的基本组成单位。
(1)分成三部分:函数头、参数说明和函数体。
(2)形参在函数被调用前不占内存;函数调用时为形参分配内存;调用结束,内存释放。
(3)函数存储区:返回地址、主调函数的CPU现场、形参变量单元、函数体内定义变量
特点;
各模块相对独立、功能单一、结构清晰、接口简单。
控制了程序设计的复杂性。提高程序的可靠性。
缩短开发周期。
避免程序开发的重复劳动。
易于维护和功能扩充。
一个很容易混淆的地方(做了解就好,实际编程中尽量避免这类情况,这跟编译器本身有关)
举例,对codeblock默认编译器:
int i=2;
fun(i++,i) 对应于fun(2,3);
fun(i,++i) 对应于fun(3,3);
fun(i,i++) 对应于fun(3,2);
printf也是类似的。
地址传递:
实参 形参
(1)数组名 数组名 传地址
(2)数组名 指针变量 传地址值
(3)指针变量 数组名
(4)指针变量 指针变量
小结:数据在函数间传递有3种方式:参数传递(传值、传址)、利用返回值、利用外部变量
定义返回指针值的函数
形式:类型标识符 *函数名(参数表);
int *f(int x,int y)
例题:
main()
{
int a=2,b=3;
int *p;
p=f3(a,b);
printf("%d",*p);
}
int f(int x,int y)
{
if(x>y) return &x;
else return &y;
}
函数错误,返回的x地址是形参地址,而调用完成函数就会清空形参内容,造成内存访问错误。
定义:函数在编译时被分配的入口地址。指向函数的指针变量。
形式:数据类型 (*指针变量名)()
如:int (*p)()
(1)(*p)可以指向返回值类型相同的不同函数。
(2)函数指针变量指向的函数必须有函数声明。
(3)赋值与调用:p=max;cmax(a,b)等价于c=(*p)(a,b);
(4)函数指针不可以进行算术运算。
#include<iostream>
#include<stdio.h>
using namespace std;
void process(int x,int y,int (*fun)())
{
int result;
result=(*fun)(x,y);
printf("%d",result);
}
int maxn(int x,int y)
{
printf("max=");
return (x>y?x:y);
}
int minn(int x,int y)
{
printf("min=");
return (x<y?x:y);
}
int add(int x,int y)
{
printf("sum=");
return (x+y);
}
int main(){
int a,b,maxn(int,int),minn(int,int),add(int,int);
void process(int,int,int (*fun)());
scanf("%d %d",&a,&b);
process(a,b,maxn);
process(a,b,minn;
process(a,b,add);
return 0;
}
最大公约数
int gcb(int a,int b){
if(b==0) return a;
else return(b,a%b);
}
分为:宏定义、文件包含、条件编译
宏定义:#define
文件包含:#include
条件编译:#if、#ifdef、#ifndef
#if当指定的常数表达式为真时就编译程序段1,否则编译程序段2;
#ifdef当标识符定义过,则编译程序段1,否则编译程序段2;
#ifndef当标识符未定义过,则编译程序段1,否则编译程序段2;
#include<iostream>
#include<stdio.h>
using namespace std;
#define MAX 10
int main(){
#if MAX>99
printf("ok");
#else
printf("no");
#endif // MAX
return 0;
}
argc表示命令行中参数个数加1,argv是一个指向命令名和命令行中各实参字符串常量的指针数组。