C++函数、结构体、类之间的联系与区别

# 序 本文比较长,记录的内容比较多,主要从是什么?本质?比较?来记录的。请选择自己需要的部分阅读。

什么是函数?

函数基础

菜鸟

函数是一组一起执行一个任务的语句。
函数声明告诉编译器函数的名称、返回类型和参数。函数定义提供了函数的实际主体。

C++ 中的函数定义的一般形式如下:

return_type function_name( parameter list )
{
   body of the function
}

在 C++ 中,函数由一个函数头和一个函数主体组成。下面列出一个函数的所有组成部分:

  • 返回类型:一个函数可以返回一个值。return_type 是函数返回的值的数据类型。有些函数执行所需的操作而不返回值,在这种情况下,return_type 是关键字 void。
    -** 函数名称**:这是函数的实际名称。函数名和参数列表一起构成了函数签名。
  • 参数:参数就像是占位符。当函数被调用时,您向参数传递一个值,这个值被称为实际参数。参数列表包括函数参数的类型、顺序、数量。参数是可选的,也就是说,函数可能不包含参数。
  • 函数主体:函数主体包含一组定义函数执行任务的语句。

这里可以先举一个例子:
编写一个求乘的阶乘的程序。n的阶乘是从1到n所有数字的乘机,比如5的乘机是120。1*2*3*4*5=120

//当然你可以用迭代之类的,这里呢,我们选取最容易理解的。
int fact(int val)
{
	int ret = 1; //局部变量,中间值
	while(val>0){
		ret = ret * val;
		val = val - 1;
	}
	return ret;	
}
// fact是函数名
// int是函数类型
// val 是参数
// fact()是函数主体
函数的调用

是指已有某函数,在另一函数中调用要调用的函数。

int main(){
	int j = fact(5); // 调用fact函数 j等于调用函数的返回值
	return 0;
}
形参和实参
  1. 形参 类似于占位符
  2. 实参 给占位符以实际的值
局部静态变量

有些情况下,有必要令局部变量的声明周期贯穿函数调用以及之后的时间。static 类型可以实现。
具体讲举例说明。仔细比较下面两段代码

using namespace std;
int count_calls(){
    int ctr = 0;// 这里我们没有用静态变量
    return ++ctr;
}
int main(){
    for(int i = 0; i != 3; ++i)
        cout << count_calls() << endl;
    return 0;
}
//返回是
// 1
// 1
// 1
// 就是在每次调用函数count_calls是 ctr都是0,所有 返回值++ctr都是1
using namespace std;
int count_calls(){
    static int ctr = 0;//调用结束后,这个值仍然有效
    return ++ctr;
}
int main(){
    for(int i = 0; i != 10; ++i)
        cout << count_calls() << endl;
    return 0;
}
//返回是
// 1
// 2
// 3
// 意思是因为static类型的作用,使得函数返回值ctr每次都是初始值
函数声明

函数声明会告诉编译器函数名称及如何调用函数。函数的实际主体可以单独定义。

函数传参
  • 传值参数 看本文的第2段代码 这个是最简单的,比较好理解。
  • 传引用参数
    也好理解。怎么讲呢?引用符号&,就是新名字。不过引用传参到底有什么好处,这个还在理解中。
#include <iostream>
using namespace std;
 
// 函数声明
void swap(int &x, int &y);
 
int main ()
{
   // 局部变量声明
   int a = 100;
   int b = 200;
 
   cout << "交换前,a 的值:" << a << endl;
   cout << "交换前,b 的值:" << b << endl;
 
   /* 调用函数来交换值 */
   swap(a, b);
 
   cout << "交换后,a 的值:" << a << endl;
   cout << "交换后,b 的值:" << b << endl;
 
   return 0;
}
void swap(int &x, int &y)
{
   int temp;
   temp = x; /* 保存地址 x 的值 */
   x = y;    /* 把 y 赋值给 x */
   y = temp; /* 把 x 赋值给 y  */
  
   return;
}

#####数组形参
数组的两个特性对我们定义和使用作用在数组上的函数有影响,这两个特性分别是不允许拷贝数组使用数组时会将其转换成指针

因为不能拷贝数组,所以没有办法以值传递的方式使用数组参数。
因为数组会被换成指针,所以当我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针。

//以下三个代码尽管形式不同,但是他们之间是等价的
void print(const int*); //这是地址,是显然的
void print(const int[]); // 意图是作用于一个数组,但是是地址
void print(const int[10];) //期望的数组大小是10,但实际上不一定

那怎么办呢?
数组参数

显式传递一个表示数组大小的形参,也就是告诉它这个数组有多大,内存分配地址的时候悠着点。

// const int ai[] 等价于 const int *ia
// size 表示数组大小
void print(const ont ai[], int size){
	for (int i=0,i!=size;++i){
		cout<<ai[i]<<endl;
	}
}
多维数组 这部分将在数组章节介绍,这里简单提一提

c++中没有真正的多维数组,所谓的多维数组都是数组的数组。

和所有数组一样,当将多维数组传递给函数时,真正传递的是指向数组首元素的指针。因为我们处理我是数组的数组,所以显然的首元素本身就是一个数组,指针就是一个指向数组的指针。

可变参数函数

C++学习之可变参数的函数与模板
所谓可变参数指的是函数的参数个数可变,参数类型不定的函数。为了编写能处理不同数量实参的函数,C++11提供了两种主要的方法:如果所有的实参类型相同,可以传递一个名为initializer_list的标准库类型;如果实参的类型不同,我们可以编写可变参数模板。另外,C++还有一种特殊的省略符形参,可以用它传递可变数量的实参,不过这种一般只用于与C函数交互的接口程序。

可变参数函数
1、initializer_list形参

如果函数的实参数量未知但是全部实参的类型都相同,我们可以使用initializer_list类型的形参(C++11新标准)。和vector一样,initializer_list也是一种模板类型。下面看看initializer_list提供的一些操作:

 1. #include<initializer_list>  // 头文件
 2. initializer_list<T> lst;    // 默认初始化,T类型元素的空列表
 3. initializer_list<T> lst{a,b,c...}; // 初始化为初始值列表的副本
 4. lst2(lst)     // 拷贝或赋值不会拷贝列表中的元素;拷贝后,
 5. lst2 = lst    // 原始列表和副本共享元素
 6. lst.size()    // 列表中的元素数量
 7. lst.begin()   // 返回指向lst中首元素的指针        
 8. lst.end()     // 返回指向lst中尾元素下一位置的指针

下面给出一个例子,需要注意的是,含有initializer_list形参的函数也可以同时拥有其他形参。另外,如果想给initializer_list形参传递一个实参的序列,必须把序列放在一对花括号内:

string func(initializer_list<string> li)
{
	string str("");
	for(auto beg=li.begin(); beg!=li.end(); ++beg)
		str += *beg;
	return str;
}

int main()
{
	cout << func({"This"," ","is"," ","C++"}) << endl;
	return 0;
}
省略符形参

函数可以用省略符形参”…”表示不定参数部分,省略符形参只能出现在形参列表的最后一个位置,它的形式如下:

void foo(parm_list, ...);
// 典型例子
int printf(const char* format, ...)

省略符形参应该仅仅用于C和C++通用的类型,因为大多数类类型的对象在传递给省略符形参时都无法正确拷贝。下面是< cstdarg >头文件中的几个宏定义:如果有需要,用的时候再看也可以,知道这么个事就行

#include<cstdarg>  // C中是<stdarg.h>

// va_list是一种数据类型,args用于持有可变参数。
// 定义typedef char* va_list;
va_list args;

// 调用va_start并传入两个参数:第一个参数为va_list类型的变量
// 第二个参数为"..."前最后一个参数名
// 将args初始化为指向第一个参数(可变参数列表)
va_start(args, paramN);

// 检索参数,va_arg的第一个参数是va_list变量,第二个参数指定返回值的类型
// 每一次调用va_arg会获取当前的参数,并自动更新指向下一个可变参数。
va_arg(args,type);

// 释放va_list变量
va_end(args);

例子:

int add_nums(int count,...)
{
	int result = 0;
	
	va_list args;
	va_start(args, count);
	for(int i=0; i<count; ++i)
		result += va_arg(args, int);
	va_end(args);
	return result;
}

int main()
{
	cout << add_nums(4, 25, 25, 50, 50) << endl;
	return 0;
}

编译器是将参数压入栈中进行传递的。传递实参的时候,编译器会从实参列表中,按从右到左的顺序将参数入栈,对于add_nums(4, 25, 25, 50, 50)的调用,则入栈的顺序是 50, 50, 25, 25, 4 (注意没有可变参数与不可变参数之分)。由于栈的地址是从高到低的,所以在知道了第一个参数地址和参数的类型之后,就可以获取各个参数的地址。

需要补充函数指针等内容

什么是结构体?

菜鸟教程
C/C++数组允许定义可存储相同类型数据项的变量,但是结构体是C++中另一种用户自定义的可用的数据类型,它允许您存储不同类型的数据项。

定义结构

为了定义结构,我们必须使用struct语句。struct语句定义了一个包含多个成员的新的数据结构,格式如下:

struct type_name {
member_type1 member_name1;
member_type2 member_name2;
member_type3 member_name3;
.
.
} object_names;

下面是声明一个结构体Books,变量为book:

struct Books
{	
	char title[50];
	int book_id;
}book;
访问结构成员

为了访问结构的成员,我们使用成员访问运算符(.)。成员访问运算符是结构变量名称和我们要访问的结构成员之间的一个句号。 详细可以参考菜鸟教程的链接。

结构作为函数参数

需要说明的是,结构体的成员默认都是public的。

您可以把结构作为函数参数,传参方式与其他类型的变量或指针类似。您可以使用上面实例中的方式来访问结构变量:

#include <iostream>
#include <cstring>
 
using namespace std;
void printBook( struct Books book );
 
// 声明一个结构体类型 Books 
struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};
 
int main( )
{
   Books Book1;        // 定义结构体类型 Books 的变量 Book1
   Books Book2;        // 定义结构体类型 Books 的变量 Book2
 
    // Book1 详述
   strcpy( Book1.title, "C++ 教程");
   strcpy( Book1.author, "Runoob"); 
   strcpy( Book1.subject, "编程语言");
   Book1.book_id = 12345;
 
   // Book2 详述
   strcpy( Book2.title, "CSS 教程");
   strcpy( Book2.author, "Runoob");
   strcpy( Book2.subject, "前端技术");
   Book2.book_id = 12346;
 
   // 输出 Book1 信息
   printBook( Book1 );
 
   // 输出 Book2 信息
   printBook( Book2 );
 
   return 0;
}
void printBook( struct Books book )
{
   cout << "书标题 : " << book.title <<endl;
   cout << "书作者 : " << book.author <<endl;
   cout << "书类目 : " << book.subject <<endl;
   cout << "书 ID : " << book.book_id <<endl;
}
指向结构的指针

您可以定义指向结构的指针,方式与定义指向其他类型变量的指针相似。说明:关于指针是什么?这个我以往的博客有讲,请自行搜索。

struct Books *struct_pointer;

现在,您可以在上述定义的指针变量中存储结构变量的地址。为了查找结构变量的地址,请把 & 运算符放在结构名称的前面,如下所示:

struct_ponter = &Book1;

为了使用指向该结构的指针访问结构的成员,您必须使用 -> 运算符,如下所示:

struct_pointer->title;

让我们使用结构指针来重写上面的实例,这将有助于您理解结构指针的概念:

#include <iostream>
#include <cstring>

using namespace std;
void printBook(strct Books *book);
struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};
int main()
{
	Books Book1;        // 定义结构体类型 Books 的变量 Book1
	Books Book2;        // 定义结构体类型 Books 的变量 Book2
	 // Book1 详述 strcpy是复制的意思
	 strcpy(Book1.title,"C++教程");
	 strcpy( Book1.author, "Runoob"); 
	 strcpy( Book1.subject, "编程语言");
	 Book1.book_id = 12345;
	
	// Book2 详述
   	strcpy( Book2.title, "CSS 教程");
   	strcpy( Book2.author, "Runoob");
   	strcpy( Book2.subject, "前端技术");
   	Book2.book_id = 12346;
	
	// 通过传 Book1 的地址来输出 Book1 信息
   	printBook( &Book1 );
   	
   	// 通过传 Book2 的地址来输出 Book2 信息
   	printBook( &Book2 );
   	return 0;
}
// 需要调用的函数 
// 该函数以结构体指针作为参数
void printBook(strct Books *book)
{
	cout << "书标题  : " << book->title <<endl;
   	cout << "书作者 : " << book->author <<endl;
   	cout << "书类目 : " << book->subject <<endl;
   	cout << "书 ID : " << book->book_id <<endl;
}

//当上面的代码被编译和执行时,它会产生下列结果:

/*书标题  : C++ 教程
书作者 : Runoob
书类目 : 编程语言
书 ID : 12345
书标题  : CSS 教程
书作者 : Runoob
书类目 : 前端技术
书 ID : 12346*/
// 其实是什么意思呢?
/*指针是地址,地址里有内容。明白吧,我们可以根据地址找内容啊*/

什么是类?

定义一个类,本质上是定义一个数据类型的蓝图。这实际上并没有定义任何数据,但是它定义了类的名称意味着什么,也就是说,它定义了类的对象包括什么,以及可以在这个对象上执行那些操作。
类定义是以关键字 class 开头,后跟类的名称。类的主体是包含在一对花括号中。类定义后必须跟着一个分号或一个声明列表。例如,我们使用关键字 class 定义 Box 数据类型,如下所示:

class Box
{
   public:
      double length;   // 盒子的长度
      double breadth;  // 盒子的宽度
      double height;   // 盒子的高度
};
对象

类提供了对象的蓝图,所以基本上,对象是根据类来创建的。声明类的对象,就像声明基本类型的变量一样。下面的语句声明了类 Box 的两个对象:

Box Box1;          // 声明 Box1,类型为 Box
Box Box2;          // 声明 Box2,类型为 Box
public、private、protected

public、protect和private使用-c++

class Base
  {
      public; //公有成员
      protected; //受保护成员
      private; //私有成员
  }
  1. public 不做介绍
  2. private 私有成员变量或者函数在类的外部是不可以访问的,甚至是不可以查看的。只有类和友元函数可以访问,不能被派生类访问。
    比如,我们把美国看做一个类,它的私有成员是它的那些州,中国不可以越过美国政府直接查看加州情况,但是加州的友元函数XX州可以看加州的情况。美国的派生类加拿大也不可以直接访问加州情况。好理解吧。
  3. protected 成员可以被派生类访问
    比如欧盟,欧元是一体的,可以在德国使用欧元也可以在英国使用欧元,瑞士也可以使用。好理解吧。顺便说一句,美元全世界通用,也就是public的。
访问权限
public的访问权限

public修饰的成员变量和成员函数,类内的成员函数和类的实例变量可访问。
实际上,类的成员函数,可以访问本类内的任何成员变量和成员函数。

#include<iostream>
#include<string>

using namespace std;
class AccessTest
{
	public:
		int pub_men;
		int pub_fun(){};//这表示一个函数
	protected:
		int prot_men;
		int prot_fun(){};
	private:
		int priv_men;
		int priv_fun(){};
};//注意这里有分号,结构体这里也有分号,但是函数没有

int main()
{
	AccessTest at;//at是类AccessTest的对象,对象可以理解为只作用于某类的变量
	at.pub_men; // OK,类的变量(对象)可以访问public成员
	at.pub_fun(); //OK 类的变量(对象)可以访问public的成员函数
	return 0}
protected访问权限

protected修饰的成员变量、成员函数,类的实例变量无法访问。但是类的友元函数、友元类可以访问。

#include<string>
#include<iostream>

using namespace std;

class AccessTest{
	friend void Atest();//这是友元函数,下面会有介绍
	friend class CAtest; //这是友元类 下面会有介绍
	public:
    		int pub_mem;
    		void pub_fun() {}

	protected:
    		int prot_mem;
    		void prot_fun() {}

	private:
   	 	int priv_memb;
    		void priv_fun() {}
};

class CAtest{
	public:
		void x(){
			AccessTest t; //t是类AccessTest的对象(变量)
			t.prot_fun(); //OK 友元类可以访问protected成员函数
			int x = t.prot_men; //OK 友元类可以访问protected的成员变量
		}
};

void Atest(){
	AccessTest t; // t是类AccessTest的对象(变量)
	t.prot_fun(); // OK, 友元函数可以访问protected的成员函数
	int x = t.prot_men; //OK,友元函数可以访问protected的成员函数
}// 看这里没有分号

int main(){
	// main里的代码是实例,就是需要输入真实的值
	AccessTest at;//at是类AccessTest的另一个对象
	at.prot_men; //ERROR, 类的实例变量无法访问protected成员变量
	at.prot_fun();//ERROR 类的实例变量无法访问protected的成员函数
	Atest();
	return 0;
}
private访问权限

private的访问权限与上述protected的访问权限是类似的。
private成员变量、成员函数,无法通过类的实例变量进行访问。但是可以通过类的友元函数、友元类进行访问。

#include<iostream>
#include<string>

using namespace std;

class AccessTest {
    friend void Atest();
    friend class CAtest;

public:
    int pub_mem;
    void pub_fun() {}

protected:
    int prot_mem;
    void prot_fun() {}

private:
    int priv_memb;
    void priv_fun() {}

};

class CAtest {
public:
    void x() {
        AccessTest t;
        t.priv_fun();       //OK,友元类可以访问private成员函数
        int x = t.priv_memb;  //OK,友元类可以访问private成员变量
    }
};

void Atest() {
    AccessTest t;
    t.priv_fun();       //OK,友元函数可以访问private成员函数
    int x = t.priv_memb;  //OK,友元函数可以访问private成员变量
}

int main() {
    AccessTest at;
    at.priv_memb;       //ERROR,类实例变量无法访问private成员变量
    at.priv_fun();      //ERROR,类实例变量无法访问private成员函数
    Atest();
    return 0;
}

从上面来看,protectedprivate没有区别。区别主要体现在继承上面。后面会讲.
一句话总结:public在任何地方都能访问。protectedprivate不能被实例访问,但可以被友元函数访问。

继承访问权限控制

c++的类继承分为publicprotectedprivate继承,不像java那样只有一种继承。

public继承

派生类通过public继承,基类的各种权限不变 。这里派生类其实可以看做类的子类

派生类的成员函数,可以访问基类的public成员、protected成员,但是无法访问基类的privated成员。
派生类的实例变量,可以访问基类的public成员,但是无法访问protected、private成员,仿佛基类的成员之间加到了派生类一般。

#include <iostream>
#include <string>

using namespace std;

class AccessTest {
public:
    int pub_mem;
    void pub_fun(){}

protected:
    int prot_mem;
    void prot_fun(){}

private:
    int priv_mem;
    void priv_fun(){}
};

class DAccessTest:public AccessTest {
// 这是派生类。AccessTest是基类,DAccessTest是派生类。:的意思是谁是谁的派生类,即DAccessTest是AccessTest的派生类。
public:
    void test() {
        int x=pub_mem;     //OK 派生类可以访问public的成员变量
        pub_fun();         //OK 派生类可以访问public的成员函数

        int y=prot_mem;    //OK 派生类可以访问protected的成员变量
        prot_fun();        //OK 派生类可以访问protected的成员函数

        int z=priv_memb;   //ERROR 派生类不可以访问private的成员变量
        priv_fun();        //ERROR 派生类不可以访问private的成员函数
    }
};

int main() {
    DAccessTest dt; // dt是DAccessTest的对象
    int x = dt.pub_mem; // 类的实例可以访问public
    int y=dt.prot_mem;   //ERROR  类的实例不可以访问protected
    int z=dt.priv_memb;  //ERROR 类的实例bu可以访问privete
    return 0;
}

一句话总结:public继承看成派生类将基类的public,protected成员囊括到派生类,但是不包括private成员。

prtected继承

派生类通过protected继承,基类的public成员在派生类中的权限变成了protectedprotectedprivate不变。
派生类的成员函数,可以访问基类的public成员、protected成员,但是无法访问基类的private成员。
派生类的实例变量,无法访问基类的任何成员,因为基类的public成员在派生类中变成了protected

// 这里主要显示了main函数里的实例变量,其余可以看上述public继承
int main() {
    DAccessTest dt;
    int x = dt.pub_mem; //ERROR,基类的成员现在是派生类的保护成员
    int y = dt.prot_mem; //ERROR,基类的成员现在是派生类的保护成员
    int z = dt.priv_memb; //ERROR
    return 0;

一句话总结:可将protected继承看成派生类将基类的public,protected成员囊括到派生类,全部作为派生类的protected成员,但是不包括private成员。

private继承
  • 派生类通过private继承,基类的所有成员在派生类中的权限变成了private
  • 派生类的成员变量或者函数可以访问基类的publicprotected,但是不可以访问private
  • 派生类的实例变量是无法访问任何的。
#include<iostream>
#include<string>

using namespace std;
class AccessTest
{
	friend void Atext();
	friend class CAtest;
	public:
		int pub_men;
		void pub_fun(){};
	protected:
		int prot_men;
		void pub_fun(){};
	private:
		int priv_men;
		void priv_fun(){};
};

class DAccessTest:private AccessTest// private的继承
{
	public:
		void test()
		{
			int x = pub_mem; //OK,派生类可以访问public的变量
			void pub_fun(); //OK 

			int y = prot_men; //OK
			void prot_fun(); //OK

			int z = priv_mem; //ERROR
			void priv_fun(); //ERROR
		}
};

int main()
{
	DAccessTest dt;
	int x = dt.pub_mem;//ERROE,派生类的对象实例是无法访问public的
	int y = dt.prot_mem; //ERROR
	int z = dt.priv_mem; //ERROR
	return 0;
}

一句话总结:实例呢,啥也干不了,因为是private继承啊,人家是私有的。派生类呢,给点面子,可以访问public and protected,为啥呢?因为它是基类的派生类啊。

构造函数
  • 类的构造函数是类的一种特殊函数,它会在每次类的新对象时执行。
  • 构造函数的名字与类的名字完全一致,并且不会返回任何值。
  • 主要用于设置初始值。
#include<iostream>
using namespace std;

class Line{
	public:
		void setLength(double len);//一个函数
		Line();//构造函数,与类的名字完全一致
};
Line::Line(void)// ::的意思是成员,一般在类外执行
{
	cout<<"Object is being created"<<endl;//初始值
}

Line::setLength(double len)
{
	length = len;
}

int main()
{
	Line line; //对象
	//设置长度
	line.setLength(6.0);
	cout<<"Length pf line:"<<line.setLength(6.0)<<endl;
	return 0;
}
类的析构函数
  • 同理,也是与类的名字完全相同,只是前面加了(~)
  • 没有返回值
  • 有助于在跳出程序(比如关闭文件、释放内存 )前释放资源
class Line{
	Line();//构造函数
	~Line();//析构函数
}

Line::~Line()
{
	cout<<"Object is being deleted" <<endl;
}

函数、结构体、类之间的异同

  1. 注意花括号{}是否有分号。函数没有,结构体、类有
  2. 结构体与类有很多相似之处,比如都可以狙击一系列函数。但是注意,结构体是public的,而然类还有protected and private
  • 6
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值