C/C++语言知识

C/C++语言知识



变量和函数的存储类别

自动变量具有块作用域、无链接、自动存储期。它们是局部变量,属于其定义所在块(通常指函数)私有。寄存器变量的属性和自动变量相同,但是编译器会使用更快的内存或寄存器储存它们。不能获取寄存器变量的地址。

具有静态存储期的变量可以具有外部链接、内部链接或无链接。在一个文件中,函数外部声明的变量是外部变量,具有文件作用域、外部链接和静态存储期。如果在这种声明前面加上关键字static,那么其声明的变量具有文件作用域、内部链接和静态存储期。如果在函数中用 static 声明一个变量,则该变量具有块作用域、无链接、静态存储期。

具有自动存储期的变量,程序在进入该变量的声明所在块时才为其分配内存,在退出该块时释放之前分配的内存。如果未初始化,自动变量中是垃圾值。程序在编译时为具有静态存储期的变量分配内存,并在程序的运行过程中一直保留这块内存。如果未初始化,这样的变量会被设置为0,包括数组和结构体成员。

具有块作用域的变量是局部的,属于包含该声明的块私有。具有文件作用域的变量对文件(或翻译单元)中位于其声明后面的所有函数可见。具有外部链接的文件作用域变量,可用于该程序的其他翻译单元。具有内部链接的文件作用域变量,只能用于其声明所在的文件内。

示例

将下面两个文件(main1.cpp 和 main2.cpp)一起编译即可。

// main1.cpp
#include <stdio.h>
#pragma warning(disable:4996)

//外部函数原型如下,默认采用 extern 关键字,可以被其他文件访问
void report_count();
void accumulate(int k); //函数定义在其他地方
static void fun();// static 表示该函数为此文件私有,不可被外部访问。

int count; // 文件作用域,外部链接,全局变量默认初始化为 0
int main(void)
{
	int value = 2; // 自动变量  初始化为随机值
	register int i; // 寄存器变量(仅仅是建议编译器采用寄存器来存储)  初始化为随机值  不能对其取地址 

	for (i = value; i >= 0; i--) 
		accumulate(i);

	report_count();
	return 0;
}
void report_count()
{
	printf("%d\n", count);
}
// main2.cpp
#include <stdio.h>

extern int count; // 引用式声明,外部链接
static int total ; // 静态定义,内部链接
void accumulate(int k); // 函数原型
void accumulate(int k)// k 具有块作用域,无链接
{
	static int subtotal ; // 静态,无链接
		if (k <= 0)
			subtotal = 0;
		else{
			subtotal += k;
			total += k;
		}
		count++;
		printf("%d %d\n", subtotal, total);
}

函数参数和返回值

个人认为:函数参数均采用传值的方式,意味着传递的是副本(引用可等同于指针);函数的返回值,采用的也是传值的方式,一般情况下,会将函数的返回值赋值给某一个变量。

采用传指针/引用,和返回指针/引用 在传递/返回一个结构体这样的大对象时,避免了传递时生成该结构体副本/返回时生成该结构体副本。效率更高!
注意:不能返回一个局部变量的指针/引用,因为函数执行完毕之后,该内存不再存在。所以一般采用指针/引用都是应用在返回指向 new 生成的内存,

#ifndef 和 #pragma once

条件编译1条件编译2extern ‘C’

一、const 关键字

// cv-限定符
// 指的是 const 和 volatile
// 而 mutable 修饰符和 const 有关
struct MyStruct
{
	// mutable 修饰符 表示即使该结构体的变量被声明为 const,a 的值依然可以被修改。
	mutable int a; 
	double b;
};
int main() {

	const MyStruct mys = { 4,5.6 };
	mys.a++; // success
	mys.b += 0.1;// error
	return 0;
}

a、const修饰常量,表示其值不可改变,且必须在定义的时候必须初始化

b、const修饰函数参数,表示该参数的值在函数体内不能被修改

c、const修饰函数返回值

a、修饰普通返回值,如 const int f();由于该返回值是一个临时变量,随着函数调用结束后,其声明周期也就结束了,所以没有意义。

b、修饰指针,如

const int* f(); //函数  表示该返回值所指向的值不能被修改
int* res=f();  // 错误
const int* res=f(); //正确

c、修饰引用,如

const int& f1(int& a) { return a; }
int& f(int& a) { return a; }
int main() {
	int a = 3;
	f1() = 89;//错误 函数调用表达式不能作为左值。
	f(a) = 90;//正确
	cout << a << endl; // 90
	return 0;
}

d、const 和 指针

	int a = 3, b = 4;
	int* p = &a;
	p = &b;
	*p = 20;

	const int* p1 = &a;  //修饰指针所指向的值,此指针可以修改,但是此指针指向的值不能被修改
	p1 = &b;
	//*p1 = 20;

	int const* p2 = &a;  // 同上  (可以认为此二者的const均修饰 *)
	p2 = &a;
	//*p2 = 20;
	
	int* const p3 = &a;  //仅修饰指针,指针不能修改,但是指针指向的值可以被修改
	//p3 = &b;
	*p3 = 20;

	const int* const p4 = &a; // 均修饰,指针不能修改,且其指向的值也不能被修改
	//p4 = &b;
	//*p4 = 20;

e、C const 和 C++ const

	const int len = 9;
	int array[len]; // cpp 允许,而 c 不允许

二、printf

	
	printf("%d\n");//打印出内存中的随机值
	int a=90;
	int b = printf("%d\n", a);
	printf("%d\n", b); // b为3  它为printf打印出字符的个数!  负值表示出错。

三、数组、字符串

a、字符数组:存储 char 的数组;字符串:一系列 char 和 结尾的 ‘\0’ 构成

b、一维数组

	char a[20];
	printf("%d\n", a[0]); // 正确,打印出随机值
	int b;
	printf("%d\n", b); // 运行时错误,b 未初始化
	
	//和普通变量一样,应该在声明时来初始化 const 数据,因为一旦声明为const,便不能再给它赋值。
	const int m[4]={1,2,3,4};

	int n[4]={1};  // n 中值为: 1 0 0 0
	// 由于数组一旦有部分元素被初始化,其余元素就会被初始化为 0,所以:对一个数组初始化全为 0 的方式如下
	int nn[4]={0};

	int mm[]={3,4};// 数组大小为2;
	int len=sizeof(mm)/sizeof(int);//  或者:sizeof(mm)/sizeof(mm[0]);

	#define N 5  // c 
	constexpr auto N1 = 5; // cpp 常量表达式
	int main() {
		int i[N1]; // 初始化数组大小
		int ii[3 * 7]; // 初始化数组大小
		const int j = 8;
		int k[j];// const 只能在cpp中 初始化数组大小
		return 0;
}

c、二维数组

	int sq[2][3] = { 5,6,7,8 }; //不带括号的从第一行顺序初始化
	/* 5 6 7
	*  8 0 0
	*/
	int s[2][3] = { {5,6},{7,8} };
	/* 5 6 0
	*  7 8 0
	*/

四、数组和指针

a、一维数组

	int a[7] = { 5,6,7,8 }; 
	int* p = &a[0];
	int* p1 = a;
	printf("%d\n", *p);
	printf("%d\n", *(p+1)); //指针加1,指针的值递增它所指向类型的大小。	
	printf("%d\n", p[1]);

	p + 2 == &p[2]; // 相同的地址
	*(p + 2) == p[2]; // 相同的值

	*(p + 2); // p 第3个元素的值
	*p + 2; // p 第1个元素的值加2    * 的运算符优先级比算术运算符要高。

a.a、 常见运算符优先级

Little_ant_

b、二维数组

	int zippo[3][2] = { 0,1,2,3,4,5 };
	printf("%d\n", zippo[2][1]);
	printf("%d\n", *(*(zippo + 2) + 1));
	// *(zippo+2)  zippo[2][0] 的地址
	// *(zippo+2)+1  zippo[2][1] 的地址
	//*(*(zippo+2)+1)  zippo[2][1] 的值
	
	int(* p)[2] = zippo; // 指向二维数组的指针 采用的括号的原因是因为 [] 比 * 的优先级高。
	printf("%d\n", **p);  // 等同于 zippo[0][0]
	printf("%d\n", *(*(p + 2)+1)); // 等同于 zippo[2][1]

	//int* p[2];  //此时 p 是一个存放 int* 的一维数组,数组长度为 2.

b.b、数组作为函数参数

// int array[8][4];  //将该数组作为函数参数
int fun1(int(*p)[4]);
int fun2(int p[][4]);
int fun1(int p[][]);// 错误的传参

int fun3(int* p);
int fun4(int p[]);

//一般而言,声明一个指向N维数组的指针时,只能省略最左边方括号中的值:
//第1对方括号只用于表明这是一个指针,而其他的方括号则用于描述指针所指向数据对象的类型。
//下面的 ar 表示其指向一个12×20×30的int数组。

int fun5(int ar[][12][20][30]);
int fun6(int(*ar)[12][20][30]);

c、野指针

	//一、未初始化的指针
	int* pp;
	printf("%p\n", pp); //报错 pp未初始化
	printf("%d\n", *pp); // 报错 pp未初始化
	*pp=9; //报错 pp未初始化
	/*
	pp未被初始化,其值是一个随机值,所以不知道 9 将储存在何处。这可能不会出什么错,也可能会擦写数据或代码,或者导致程序崩溃。
	创建一个指针时,系统只分配了储存指针本身的内存,并未分配储存数据的内存。

	要么设置它的值为NULL,要么让它指向已有变量/数组的地址,要么让它指向 malloc/new 分配的内存。
*/
	
	//二、指针被释放之后,仅仅释放掉其指向的内存,指针本身并未被释放掉。
	int* uu = new int;
	delete uu;
	uu = NULL;  //建议 delete/free 之后,令指针值为 NULL


int* fun() {
	int a = 9;
	return &a;
}
int main() {
	//三、指针操作超出了变量的作用范围,
	int* res = fun(); // 此时 a 所在的内存已经被释放掉了。
	return 0;
}

d、通用指针 void*

// 通用指针 void* : 可指向任何指针,但解引用时需要先进行转换。
	int a = 3;
	int* p;
	void* v = &a;
	printf("%d\n", *v);// 报错,不知道 v 指向内容的长度。
	p = (int*)v;
	printf("%d\n", *p);

五、简单的类型转换

Java 变量

	double dd = 9.87;
	int iii = dd;//错误
	int i=(int)dd; //正确

C 变量

	// 隐式类型转换
	double dd = 9.87;
	int iii = dd;

C 指针

	// 指针只能显式类型转换
	double* a = new double(8.98);
	int* b = a; //报错,无法转换
	int* b = (int*)a;
	delete a;
	a=NULL

1、c 语言: 字符串和其他类型的转换

#include<iostream>
#pragma warning(disable:4996)

using namespace std;
int main() {
	// c 语言: 字符串和其他类型的转换
	char* a = new char[30];
	double b = 8.2385;
	sprintf(a, "%.2lf", b); // printf中一般可以用%f代替%lf
	//a 此时为一个字符串。
	cout << a << endl;

	double c;
	sscanf(a, "%lf", &c); // scanf中%lf与%f是严格区分的
	cout << c << endl;
	delete[] a;
	return 0;
}

2、cpp 初始化

	//数组
	//列表初始化:
	int a[]{ 2,3,4 }; // 2 3 4
	int b[6]{ 2,3,4 };// 2 3 4 0 0 0
	int d[3]{};// 0 0 0

	int c[3] = {};// 等同于 c 中的 int c[3]={0};
	//结构体
	struct MyStruct { int a; double b; };
	MyStruct mys{ 4,5.6 };
	MyStruct mm = { 4,5.7 };
	//字符串初始化
	//char* a = "jjjj";//错误
	const char* a = "jjjj";//正确  a 是一个字符串常量
	char b[] = "uuuuu";  // b 是一个字符串
	b[0] = 'o';
	cout<<sizeof(b)<<endl; // 6
	// new 初始化
	int* a = new int(9); //单值变量

	// 数组/结构体的列表初始化
	struct MyStruct { int a; double b; };
	MyStruct* mys = new MyStruct{1, 3.4};//结构体
	int* b = new int[5] {};
	int* c = new int[] {2, 3};
	int* d = new int[4]{ 1 };

3、函数

a、默认的函数参数

int fun(int node=4){
	return node;
}
int main() {
	int res = fun();
	cout << res << endl;  // 4
}
//参数有多个的时候,只能从右往左设置参数的默认值

b、函数模板

#include<iostream>
#pragma warning(disable:4996)
using namespace std;

//函数模板:
template<typename T>
void swap1(T& a, T& b) {
	T tmp = a;
	a = b;
	b = tmp;
}
int main() {
	int a = 8, b = 9;
	double aa = 8.1, bb = 9.1;
	swap1(a, b); // 9 8
	swap1(aa, bb);// 9.1 8.1

	// std::swap 函数
	swap(a, b); // 8 9
	swap(aa, bb);// 8.1 9.1
	return 0;
}

4、结构体,C字符串、string赋值

struct a {
	char c[20];
	int i;
	double d;
};
int main() {
	a aa = { "kjisf",9,2.34 };
	a bbb = aa; // 结构体可以直接赋值
	cout << bbb.c << " " << bbb.i << " " << bbb.d << endl;

	string s = "lkjsf";
	string ss = s;// string 可以直接赋值(重写赋值操作符)

	// C字符串不可直接赋值,需要采用 strcpy 函数 或者 for循环依次赋值
	char tmp[6];
	strcpy(tmp, "12345");
	char* b = new char[6];
	strcpy(b, "12345");
	delete[] b;
	return 0;
}

5、类

类中的private: 可以省略,这是类默认的访问策略。

像一些简单的函数定义(方法体),如Java中的getter、setter,可以直接写在类声明文件中,位于类声明中的函数定义自动成为内联函数。
或者在类定义文件中,与其他函数的定义放在一起,此时需要在函数定义之前显式添加 inline 关键字,来表明其是一个内联函数。

a、初始化

类初始化如下:

class C {
	int a, b;
	double c;
public:
	C(int a, int b, double c) {
		this->a = a;
		this->b = b;
		this->c = c;
	}
};
int main() {
	C c = C(2, 3, 4.5);// 显式调用构造函数
	C cc(1, 2, 8.9);// 隐式调用构造函数
	
	C c1 = C{ 2, 3, 4.5 }; // 列表初始化
	C cc1{1, 2, 8.9};
	
	C* ccc = new C{ 1,2,3.4 };
	C* cccc = new C(3, 4, 5.6);
	return 0;
}

和Java一样,不提供自己的构造函数,程序只能使用默认的构造函数;如果提供了自己的构造函数,而且还想使用默认构造函数的话,就需要显式声明了。

class C {
	int a, b;
	double c;
public:
	C(int a, int b, double c) {
		this->a = a;
		this->b = b;
		this->c = c;
	}
	C() { // 实现默认的构造函数,提供隐式初始值
		a = b = 0;
		c = 0.0;
	}
	~C(){} // 默认的析构函数,如果构造函数采用了new,那么在析构函数中需要采用delete。
};
int main() {
	C c = C(); //显式调用 默认构造函数
	// 如果默认构造函数未作初始化,则 c 中的类成员为随机值。
	C cc; //隐式调用
	C* c1 = new C();
	C* cc1 = new C;
	return 0;
}

b、赋值

int main() {
	C c = C();  //初始化 (可能会创建临时对象)
	c=C(); //赋值,一定会创建临时对象,创建一个临时对象,然后赋值给变量 c,最后调用析构函数
	return 0;
}

c、const 成员

a、const 成员函数

public:
	void show() const; // 保证调用show方法时,不会修改调用者的成员变量。

void C::show() const{
	cout<<"haha"<<endl;
}

b、const 成员变量

class C {
private:
	const int a;
public:
	C(int aa) :a(aa) {} 只能采用列表初始化来进行
};

5、string类

int main() {
	string s = "jlsjl";
	string s1("kskfj");
	string ss = s;// 赋值操作
	string sss = ss + s;// 拼接

	string ssss;
	getline(cin, ssss);// 从标准输入接收一行输入
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值