Learn
C Programming
from ScratchA step-by-step methodology with
problem solving approach

Mohammad Saleem Mir

www.bpbonline.com

Copyright © 2024 BPB Online

All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews.

Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the author, nor BPB Online or its dealers and distributors, will be held liable for any damages caused or alleged to have been caused directly or indirectly by this book.

BPB Online has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, BPB Online cannot guarantee the accuracy of this information.

First published: 2024

Published by BPB Online

WeWork

119 Marylebone Road

London NW1 5PU

UK | UAE | INDIA | SINGAPORE

ISBN 978-93-55516-060

www.bpbonline.com

Dedicated to

In recognition of your unwavering support, boundless encouragement, and enduring belief in my dreams, this book on C programming is dedicated to the pillars of my life – my beloved parents and cherished family members. Your love and guidance have been the foundation upon which I've built my journey in the world of programming.

With heartfelt gratitude and endless love,

Mohammad Saleem Mir

About the Author

Mohammad Saleem Mir is currently working as Senior Assistant Professor at Higher Education Department, J&K, India. He has more than 15 years of teaching experience. During the course of his journey as a Teacher, he has taught at Post graduate as well as under-graduate levels. He has taught in general all the subjects associated to computer Sciences but took lead when it came to guiding students through Programming subjects, with the aim of building strong analytical skills and inculcate logical and reasoning skills into the students. The author holds bachelor’s degree in Sciences, Master’s Degree in Computer Applications/ Sciences from Kashmir University besides being a Doctorate student. He is currently pursuing Ph. D in the fields of Machine Learning/ Artificial Intelligence/ Medical Sciences. He has published a number of research papers in national as well as international Journals, besides presenting his research in various conference. The author has delivered inspirational talks in various conferences/ Refresher courses and various Training Programmes conducted by various Universities and colleges. The author remains actively involved in technical seminars and workshops.

About the Reviewer

Srividya Richard, Sri is a passionate and a dedicated educator with a Masters of Engineering in Computer Science from Anna University. She considers herself as a self-confident and an enthusiastic person with a flair to logically conclude any given task with complete precision and accuracy. She is extremely goal oriented and an excellent team player.

She has a decade long experience in teaching Engineering graduates at a reputed institution. Her research interests lie in the area of Machine learning, Data Analytics, Artificial Intelligence. She has published many research articles in her areas of interests in refereed journals and conferences. Sri’s love for technology and the English language has contributed immensely in reviewing and proofreading several articles that ranges from research thesis to books. During her academic career, Sri had the opportunity to mentor and execute several real time IT projects. She has also conducted various trainings and workshops and served as a committee member in organizing conferences and research seminars. The exposure to interdisciplinary projects and industry interactions has had a significant impact in honing her skills.

As an AI enthusiast, Sri is delighted to embark on the path of equipping professionals with AI skills. She looks forward to leveraging the expertise gained over the years to empower individuals for both personal and professional development.

Acknowledgement

This book is my first experience to pen down something for my esteemed readers. I sincerely hope they would appreciate it and let me know an honest review of the book. I would like to acknowledge with gratitude, the support and love of my family. They all kept me going, and conceiving this book would not have been possible without their support and encouragement.

I am also grateful to BPB Publications for their guidance and expertise in bringing this book to fruition. It was a long journey of revising this book, with valuable participation and collaboration of reviewers, technical experts, and editors.

Finally, I would like to thank all the readers who have taken an interest in my book and for their support in making it a reality. Your encouragement has been invaluable.

Preface

I feel grateful to share my knowledge, analyses, and conclusions to raise the level of curiosity with regards to programming among the students of computer sciences, at the end of the day, we all will be beneficiaries. When one chooses computer sciences as a career, it may be noted that programing is an indispensable part of computer sciences and one cannot be a good computer scientist unless (s)he is good (if not exceptional) programmer. Well begun is half done, as they famously say; beginning on a good note, knowing fundamentals of programming clearly and building a solid base as far taking up more advanced form of programming, later in your career, is concerned is important. For this very, reason, this book focuses on giving an insight into the fundamentals of programming beforehand, and a sincere effort has been made to cover major concepts in detail so that the readers are kept interested.

C is a foundational programming language and ought to be learned systematically. Getting measure of the features of C programming is very important. This book covers all the fundamental concepts, rest assured the readers would have a great time going through the book contents and would benefit from this book. The book is written in unpretentious manner. The target audience being programming beginners, who would be able to understand the concepts explained in the book quite easily. Each concept in the book is synchronously supplemented by coding examples to enhance clarity. The book has the following 10 chapters:

Chapter 1: Programming Methodology – In this Introduces you to the world of Programming Methodology, Problem Solving, Program Design etc.

Chapter 2: C Programming Fundamentals – This chapter would acquaint you to the basic structure of the C Programming language, coding environment, and the syntax and references to write C Programs.

Chapter 3: Control Statements – This chapter throws light on various control statements used in C programming.

Chapter 4: Functions – This chapter gives an account of perhaps the most important topic in programming languages that is, functions.

Chapter 5: Arrays – In this chapter, we will introduce arrays, multi-dimensional arrays, strings and more how we can work with them.

Chapter 6: Pointers – In this chapter, we will learn about pointers, a concept that is found difficult by the programming novices, but not anymore.

Chapter 7: Structures and Unions – In this chapter, we will learn to work with user defined data types like Structures and Unions.

Chapter 8: File Handling – In this chapter, we will learn about how to work with hard disk files and different associated operations with programming examples.

Chapter 9: C Preprocessors – In this chapter we will learn how to define and use Preprocessors in C and their importance.

Chapter 10: C Graphics – In this chapter we will see the other side of the C Output, first one being the Text mode. Graphics programming in C used to drawing various geometrical shapes using inbuilt C functions.

Code Bundle and Coloured Images

Please follow the link to download the

Code Bundle and the Coloured Images of the book:

https://rebrand.ly/9m5lv1u

The code bundle for the book is also hosted on GitHub at https://github.com/bpbpublications/Learn-C-Programming-from-Scratch. In case there's an update to the code, it will be updated on the existing GitHub repository.

We have code bundles from our rich catalogue of books and videos available at https://github.com/bpbpublications. Check them out!

Errata

We take immense pride in our work at BPB Publications and follow best practices to ensure the accuracy of our content to provide with an indulging reading experience to our subscribers. Our readers are our mirrors, and we use their inputs to reflect and improve upon human errors, if any, that may have occurred during the publishing processes involved. To let us maintain the quality and help us reach out to any readers who might be having difficulties due to any unforeseen errors, please write to us at :

errata@bpbonline.com

Your support, suggestions and feedbacks are highly appreciated by the BPB Publications’ Family.

Did you know that BPB offers eBook versions of every book published, with PDF and ePub files available? You can upgrade to the eBook version at www.bpbonline.com and as a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at :

business@bpbonline.com for more details.

At www.bpbonline.com, you can also read a collection of free technical articles, sign up for a range of free newsletters, and receive exclusive discounts and offers on BPB books and eBooks.

Piracy

If you come across any illegal copies of our works in any form on the internet, we would be grateful if you would provide us with the location address or website name. Please contact us at business@bpbonline.com with a link to the material.

If you are interested in becoming an author

If there is a topic that you have expertise in, and you are interested in either writing or contributing to a book, please visit www.bpbonline.com. We have worked with thousands of developers and tech professionals, just like you, to help them share their insights with the global tech community. You can make a general application, apply for a specific hot topic that we are recruiting an author for, or submit your own idea.

Reviews

Please leave a review. Once you have read and used this book, why not leave a review on the site that you purchased it from? Potential readers can then see and use your unbiased opinion to make purchase decisions. We at BPB can understand what you think about our products, and our authors can see your feedback on their book. Thank you!

For more information about BPB, please visit www.bpbonline.com.

Join our book’s Discord space

Join the book’s Discord Workspace for Latest updates, Offers, Tech happenings around the world, New Release and Sessions with the Authors:

https://discord.bpbonline.com

CHAPTER 1Programming Methodology

Introduction

In this chapter, we will explore the fascinating world of computers and computing, gaining a comprehensive understanding of their principles and capabilities. Our primary focus will be on programming as we delve into various programming styles and methodologies. We will also explore the inner workings of computer systems, understanding their components and how they function together. Along the way, we will develop a strong foundation in algorithms, learning how to design and implement them effectively. To aid our understanding, we will utilize flowcharts as visual tools for representing and analyzing algorithms. By the end of this chapter, you will possess a deep knowledge of programming principles, problem-solving techniques, and the powerful role that computers play in solving real-world challenges. Let us embark on this exciting journey of exploration and discovery.

Structure

The chapter discusses the following topics:

Programming methodology

Computer as a problem-solving tool

Computers and computing

Computer system

Programming styles

Algorithms

Flowcharts

Objectives

The objective of this chapter is to provide a comprehensive understanding of programming methodology, emphasizing the use of computers as problem-solving tools. It explores the concepts of computers and computing, delves into the components and functioning of computer systems, and explores various programming styles. The chapter aims to develop a solid foundation in algorithms, their design, and implementation. Additionally, it covers the use of flowcharts as visual tools for algorithm representation and analysis. By the end of the chapter, readers will have gained a deep knowledge of programming principles, problem-solving techniques, and effective use of computers in solving real-world challenges.

Programming methodology

Programming methodology refers to the systematic approach and techniques used in software development. It provides a framework for software engineers to follow while designing, coding, testing, and maintaining software applications.

A computer program is a set of related commands or instructions to solve a given computer problem. Programming is the process of conceptualizing and subsequently writing a computer program that is executed to produce the desired result. Programming involves conceptualizing and writing a program, compiling the program, executing the program, and debugging the program. A computer program aims to solve real-life problems ranging from simple to more complex ones. Programming methodology is the method to analyze real-life complex problems, provide software solutions, and control the associated activities of the software development process. Some popular programming methodologies include:

Waterfall methodology: This is a sequential software development model that follows a set of steps in a linear fashion, each step building upon the previous one.

Agile methodology: This iterative approach to software development emphasizes collaboration between developers and stakeholders, flexible planning, and adaptive delivery.

Scrum methodology: This agile methodology is designed specifically for software development projects. It focuses on iterative, incremental delivery of working software, emphasizing teamwork, flexibility, and continuous improvement.

DevOps methodology: This software development approach emphasizes collaboration between development and operations teams to ensure that applications are delivered quickly, reliably, and securely.

Each of these methodologies has its own advantages and disadvantages, and the choice of methodology depends on the specific needs and constraints of the software development project. The choice of the most appropriate methodology helps the software development team deliver a high-quality product that meets the needs of its users.

Problem-solving methodology and techniques

Computer problem-solving methodology is a systematic approach to identify, analyze, and resolve computer-related issues.

The steps involved in this methodology are:

Define the problem: Identify the issue and gather information about it, including error messages, symptoms, and the context in which the problem occurred.

Analyze the problem: Use critical thinking and troubleshooting techniques to understand the root cause of the issue.

Formulate a hypothesis: Develop a potential solution based on the information gathered in the previous step.

Test the hypothesis: Implement the proposed solution and check if it resolves the problem.

Evaluate the results: If the solution worked, document it and move on to the next problem. If it did not work, go back to Step 3 and formulate a new hypothesis.

Implement a permanent solution: If a solution is found, implement it permanently to prevent future problems.

Document the solution: Document the solution, including the steps taken, the outcome, and any lessons learned, to help with future troubleshooting.

By following this methodology, computer problems can be solved efficiently and effectively.

Writing a good program is an expertise; hence, it needs to follow certain practices:

Meaningful names for identifiers: Writing understandable, legible, and maintainable code in programming requires giving identifiers (such as variables, functions, classes, and more) meaningful names. Here are some suggestions for selecting identifiers with meaningful names:

Assigning identifiers meaningful names: We should assign identifiers meaningful names that appropriately reflect their function and intended use. Use descriptive names like num_students or current_date instead of ambiguous or general ones like x or temp.

Consistency in naming: We should maintain consistency by using the same naming patterns across our source code.

Steer clear of acronyms: They might make your code more difficult to read and comprehend. If an acronym is not well-known and frequently used in your programming community, do not use it.

Name classes and objects with nouns: Classes and objects should be named with nouns since they reflect actual objects. For instance, Student should be the class name for a class representing a student.

Name functions and methods using verbs: Verbs describe activities that may be taken; hence, they should be used to name functions and methods. For instance, the function Sum should compute the sum of two values.

Clarity of expression: Expression represents a specified operation. Hence, they need to be clearly understood and depicted with no concession on clarity of expression.

Comments and indentations: Comments are used for documentation purposes in a program. Comments (denoted by // for a single line comment) tend to guide a programmer through debugging; indentation is the ideal technique for writing a program in such a way that helps to understand the flow of a program.

Use of blank lines or blank spaces: Blank lines are used to separate blocks of code that are long. The standard for the use of spaces in programming languages matches what we follow in normal English rules, meaning that symbols in C, like =, +, and more, should precede and follow with at least one space.

Depiction of statements: Statements represent meaningful commands through which we perform operations in C. They should be put on separate lines. In case of a block of statements (explained later), which is denoted by {and}, the opening brace, {should appear on the line after the block-level statement, and the closing brace}, should appear after the last statement of the block-level statement. A block of statements is indented with relation to the braces.

Features of a good program

A good computer program should have the following features:

Usability: The program should be easy to use and understand, with a clear and intuitive user interface.

Reliability: The program should function as expected and not cause unintended consequences or errors.

Performance: The program should be fast and responsive without significant lag or delay.

Scalability: The program should be able to handle increasing demands and data processing requirements as the user base grows.

Security: The program should have robust security measures to protect against hacking, data breaches, and other security threats.

Flexibility: The program should allow users to customize and modify their settings and preferences to fit their needs.

Compatibility: The program should work seamlessly with other software and hardware components without compatibility issues.

Support: The program should have adequate documentation, training, and technical support to help users resolve any issues.

By having these features, a good computer program will provide a positive user experience, increase productivity, and provide long-term value to its users.

Reasons for using a structured, systematic approach to problem-solving:

To ensure consistency

To make everyone aware of how others are approaching the project.

To keep the procedure objective and resistant to personal prejudices and impressions.

To facilitate group decision-making

To help a group follow the model rather than using different ways all at once by focusing on the six phases in the problem-solving model, which also helps define the agenda. A group can more easily agree by employing a process and facts to guide their judgments.

To successfully resolve issues.

Computer as a problem-solving tool

Computers are information processing devices that transform information/data according to prescribed rules, organized in an algorithm implemented as a program.

Algorithm: A precise, step-by-step method of doing a task in a finite amount of time.

How to use the computer as a problem-solving tool?

We need to:

Analyze and describe the initial state and target stage of events with respect to input data.

Design an algorithm for transforming input into output based on a chosen set of applicable operations.

Implement the algorithm as a program using a particular programming language.

Computers and computing systems

Computers are devices capable of executing programs that operate on data fed (input) to produce the desired output.

Computing systems are the hardware and software components that provide computing services and functionality. These systems can range from simple devices, such as a calculator, to complex systems, such as a supercomputer or cloud computing infrastructure.

Here are some common types of computing systems:

Personal computers: These are the most common computing systems used by individuals and businesses. They typically consist of a desktop or laptop computer and a range of peripheral devices, such as a keyboard, mouse, and monitor.

Mobile devices: These include smartphones and tablets, which have become increasingly popular for personal and business use. Mobile devices typically have limited processing power and storage capacity but are highly portable and offer a range of features and capabilities.

Servers: Servers are specialized computing systems that provide services to users and other computers on a network. They are used for web hosting, email hosting, database management, and file sharing.

Supercomputers: Supercomputers are high-performance computing systems for complex scientific and engineering tasks, such as weather modeling, computational fluid dynamics, and molecular modeling. They typically consist of many processors and a significant amount of memory and storage.

Embedded systems: Embedded systems are specialized computing systems built into other devices, such as cars, medical devices, and industrial equipment. They are designed to perform specific tasks and are often used in real-time systems where speed and reliability are essential.

Cloud computing infrastructure: Cloud computing infrastructure is a computing system that provides access to a range of computing resources, such as storage, processing power, and applications, over the internet. Businesses and individuals often use cloud computing to store and access data, run applications, and perform other computing tasks without needing on-site hardware and software.

In a computer (computing device), the input devices, such as the keyboard and mouse, provide data to the computer. The output devices, such as the monitor and speakers, display or produce output from the computer. The system bus connects all the computer components, allowing them to communicate.

The Central Processing Unit (CPU) is the brain of the computer, responsible for executing instructions and performing calculations. The memory, which includes RAM, ROM, and cache, provides temporary and permanent storage for data and instructions that the CPU uses.

Finally, the storage component, which includes hard disk drives and other types of storage, provides permanent storage for data and programs even when the computer is turned off. Figure 1.1 depicts the entire computer system housing all the above-mentioned components and functionality:

Figure 1.1: Computer system functionality

A list of steps that describe how a computer processes data and completes tasks can be used to condense the fundamental scheme of operation of a computer. An overview of a computer’s basic operations is as follows:

Input: A computer receives information and commands from input devices such as keyboards, mice, touchscreens, or storage units. User inputs are transformed via input devices into a language that a computer can understand.

Processing: The CPU, sometimes referred to as the brain of the computer, is responsible for calculations and data handling. The CPU retrieves instructions from memory, decodes, processes, and finally saves the finished products back in memory.

Memory: The memory of the computer houses the information and instructions that the CPU needs to process data. Memory comes in various forms, including RAM, which is used for short-term storage, and storage technologies like hard drives or SSDs, which are used for long-term storage.

ALU: ALU is a part of the CPU that conducts arithmetic operations on data, such as addition and subtraction, as well as logical operations, such as AND, OR, and NOT.

Control unit: The control unit oversees how orders are carried out. It obtains instructions, decodes them, and controls how the other components behave.

Output: Processed data is transferred to output devices, including displays, printers, speakers, and storage devices, so that people may see or utilize it.

Storage: Long-term storage gadgets like hard drives or SSDs keep data even when the computer is off. For later usage, data may be saved and retrieved.

Execution cycle: The computer functions in a succession of fetch-decode-execute cycles that are referred to as execution cycles. A memory command is retrieved and decoded to reveal the operation carried out, and the outcome is subsequently stored.

Operating system: The operating system controls hardware resources, presents user interfaces, and enables seamless operation of programs. It makes hardware and software communication easier.

Software and apps: Software programs, such as operating systems and apps, give instructions to the computer on how to carry out certain activities.

Application software enables users to carry out a variety of functions, including word processing, gaming, and internet surfing.

Communication: Using communication interfaces, such as network adapters or wireless connections, computers may communicate with other hardware and networks.

Feedback loop: The computer program repeatedly repeats these stages, reacting to human input and carrying out the requested actions.

This is only a high-level overview, and the actual functioning of a contemporary computer entails numerous minute details and sophisticated interactions between hardware and software components. To understand how computers absorb information and carry out tasks, one must first understand this fundamental framework.

Computer system

Hardware and software are the two main parts of a computer system. The physical component is the computer hardware, and the software is a group of programs (instructions) that enable the hardware to function.

The components of the computer system are:

Hardware

Software

Computer system = Hardware + Software + User

All physical parts of the computer (or everything that we can touch) are known as Hardware.

Software = Programs that give intelligence to a computer.

User = Person who operates a computer.

Hardware

Computer hardware refers to the physical components of a computer, which include the following:

CPU: The CPU is the brain of the computer and performs all the calculations and processing required to run applications and programs.

Memory: Memory, also known as Random Access Memory (RAM), stores data and instructions that the CPU needs to access quickly.

ROM: A form of computer memory called Read-Only Memory (ROM) is used to store permanent or semi-permanent data and instructions that are necessary for a computer system to operate properly. Unlike volatile and transient RAM, ROM keeps its data even when the power is turned off. ROM is essential for starting the computer up and initializing its hardware.

Hard drive: The hard drive stores all the data and programs on a computer. It is where the operating system, applications, and user files are stored.

Motherboard: The motherboard is the main circuit board of a computer, and it connects and controls all the other components of the computer.

Power Supply Unit: The PSU converts AC power from the wall outlet into DC power that can be used by the computer’s components.

Graphics Processing Unit: The GPU is responsible for rendering images and videos on the computer’s monitor.

Sound card: The sound card is responsible for providing audio output from the computer.

Optical drive: The optical drive is used to read and write CDs, DVDs, and Blu-ray discs.

Keyboard and mouse: The keyboard and mouse input data and commands into the computer.

Monitor: The monitor displays the output from the computer’s GPU.

These components work together to enable a computer to perform various tasks, from basic word processing to complex video editing and gaming. Understanding the different components of a computer’s hardware is essential for building and upgrading a computer and troubleshooting possible issues.

Software

Computer software refers to instructions or programs that tell a computer what to do. It is a collection of programs, data, and other instructions that enable a computer to perform various tasks and operations.

There are two main types of software: system software and application software.

System software: This is the software that enables the computer to operate and manage its hardware components. Types of system software include:

Operating systems: Such as Windows, macOS, Linux, and Android.

Device drivers: These programs enable the computer to communicate with peripheral devices such as printers, scanners, and cameras.

Utilities: These programs help users maintain and optimize their computers, such as antivirus software, disk cleaners, and backup software.

Application software: This software is designed to perform specific tasks or applications for users. Types of application software include:

Productivity software: Software like word processing, spreadsheet, and presentation software used for business, education, and personal purposes.

Multimedia software: Software like video editing, audio editing, and image editing software used for creative purposes.

Gaming software: Software such as video games and gaming platforms used for entertainment purposes.

Web browsers: Browsers like Google Chrome, Mozilla Firefox, and Microsoft Edge are used to access the internet and browse websites.

Computer software is a critical component of modern computing, enabling computers to perform various tasks and operations. Advances in software technology have enabled computers to become increasingly powerful, versatile, and capable of handling complex tasks, making them an essential tool in many areas of our lives.

Application software can be of two main categories:

Custom-built software

Packaged software

Custom-built software

Software specially created and developed to satisfy the distinct demands and requirements of a single business or person is called custom-built. It offers substantial functionality, performance, and usability advantages compared to off-the-shelf software, which is pre-designed and available for purchase. A group of software engineers and developers usually creates custom software after closely communicating with the customer to comprehend their needs and goals. The development process often includes the requirements collecting, design, programming, testing, and deployment phases.

One of the main advantages of custom-built software is the ability to handle special business goals and requirements that off-the-shelf software may not satisfy. This might involve integration with current systems, particular industry laws, and distinctive business processes.

Because it may be created to expand and change in accordance with the needs of the business, custom software can also offer better flexibility and scalability than off-the-shelf software. It may also end up being more affordable in the long term.

Packaged software

It is commonly referred to as off-the-shelf software, is ready-made software that has been developed for a particular market or sector and is offered for purchase by anybody who requires it. This kind of software may be offered to many consumers and is frequently created by software companies.

Packaged software is meant to offer a variety of capabilities, and it frequently focuses on user-friendliness with a clear feature set and simple navigation. QuickBooks, Adobe Creative Suite, and Microsoft Office are some examples of bundled software. Custom-built software is specifically designed and developed to meet the unique demands and requirements of a particular company or individual. Comparing custom-built software to off-the-shelf or pre-designed commercial software, there are clear differences in capability, performance, and usability.

Since the development expenses are split among many clients, one of the main advantages of packaged software is that it is often less expensive than custom-built software.

Compiler

A compiler is software that converts source code written in a high-level programming language into machine code that a computer can execute. Lexical analysis, syntactic analysis, semantic analysis, code creation, and code optimization are some of the phases in the compilation process. The initial step in the lexical analysis process is breaking down the source code into individual tokens or words, such as keywords, identifiers, operators, and literals. The following step, syntax analysis, verifies that the code adheres to the grammar requirements of the computer language.

The compiler does semantic analysis to ensure the code is understandable and well-formed once it has been parsed and verified. This process includes checks for type compatibility, variable scope, and other language-specific norms.

Code creation and optimization are included in the compilation process’s last phases. While code optimization focuses on enhancing the effectiveness and performance of the produced code, code generation translates source code into machine code.

After the compilation process is finished, the computer may run the generated machine code without the aid of an interpreter or virtual machine. Because of this, compiled programs run quicker and more effectively than interpreted ones.

Assembler

A low-level programming language called Assembler is used to create applications that may be run directly by a computer’s CPU. Mnemonic codes associated with certain machine instructions are used to write assembler code.

The fundamental steps in assembler programming are writing source code, using an assembler to turn it into machine language, and running the resulting executable program on the target computer. Assembly language programs are generally more difficult to create compared to high-level programming languages due to the requirement for a deep comprehension of the underlying hardware architecture. Nevertheless, they offer greater hardware control and enable the development of faster and more efficient programs than those built using high-level languages. Assemblers typically include macro processing, conditional assembly, and symbolic addressing to make writing and maintaining large and complex programs easier. Common assemblers include Netwide Assembler (NASM), Microsoft Assembler (MASM), and GNU Assembler (GAS).

Debugger

A debugger is software that programmers use to find and correct flaws or problems in their code. Programmers can use debuggers to step through their code line by line, halt the running of the program at certain intervals, investigate variables, and check the status of the program at various points. This enables programmers to locate and isolate issues in their code before modifying and testing it to fix them.

Debugging tools come in various forms, from straightforward command-line tools to sophisticated Integrated Development Environments (IDEs), GDB, Visual Studio Debugger, and Xcode Debugger are some examples of well-liked debuggers.

Interpreter

A computer program called an interpreter interprets and runs code written in a high-level programming language. Contrary to compilers, which convert source code into machine code before execution, interpreters read each line of source code and immediately execute it.

Interpreters are frequently utilized in scripting languages like Python, Ruby, and JavaScript, where quick development times and usability are more essential than pure efficiency. Interpreters can also be employed for languages like Lisp and Prologue that need dynamic behavior or instantiate code.

Because they can recognize and report problems as soon as they are encountered. Interpreters have the benefit of being able to provide better error messages than compilers. They can be more flexible than compilers because they can execute code at runtime, making it possible to create dynamic and interactive programs. However, interpreters can be slower than compilers because they must read and interpret code for each line of execution.

Language translators

Language translators are computer programs that automatically translate text from one language into another, facilitating communication between speakers of various languages. They are used in various settings, including business, education, healthcare, and government, to translate spoken or written communication.

Rule-based, statistical, and neural machine translators are a few examples of the numerous types of language translators. Statistical machine translators utilize statistical models to identify the most likely translation, unlike rule-based machine translators, which employ rules to translate text from one language to another. The most sophisticated machine translation tools employ artificial intelligence to learn from massive quantities of data and produce more accurate translations.

Google Translate, Microsoft Translator, and DeepL are some examples of popular language translation software. These tools are continually improving to provide better translations. However, it is important to note that language translation is not perfect. There are often errors or nuances lost in translation, particularly when dealing with idiomatic expressions, cultural references, or complex technical language.

Programming styles

A set of rules or guidelines governing how we can write the source code for a computer program in a particular programming language is known as programming style (followed by programming language). A programming language may follow any of the following programming styles and let a user code in concurrence with that particular programming.

Let us discuss different programming styles to design a program:

Procedural

Modular

Structured

Object-oriented

Procedural programming

Procedural programming is based on the model of using procedures. A procedure called a subprogram, subroutine, or method is a set of instructions to solve a (sub) problem. Control can be assigned to any procedure within a program, including other procedures or itself. Refer to the following Figure 1.2:

Figure 1.2: Procedural programming

Features of procedural programming:

Less abstraction levels are between the source code and the machine.

Procedures or sub-programs can be invoked from any place in the code.

A procedure may accept arguments and return values to the calling procedure.

Procedural programming languages adhere to structured programming techniques using block-based control flow.

Re-usability of code (in procedures).

Ease of following the logic of a program.

Procedural programming is a sub-paradigm of imperative programming since each computation step is described explicitly, even by defining procedures.

Modular programming

In modular programming, a program is divided into independent, substitutable modules. A program can easily be written in modular form. Modularity is all about making blocks of code that are testable and can be stacked together to constitute an entire application. The programming style involves a problem to be divided into sub-programs. Moreover, several modules can combine to form a superior module. Ideally, code that we need to reuse makes a module that we can import rather than copy-paste. Using a modular programming style makes our programs readable, easier to scale, and nicer. In modular programming, the focus is entirely on writing code. A module that refers to another module to initiate its execution is known as a calling module, and the module that was referred to is known as a called module. The called module may be a subprogram such as a function or procedure. Refer to the following Figure 1.3:

Figure 1.3: Modular programming

The steps in modular programming are:

Define a problem.

Strategize the steps desirable to solve a problem.

Split the problem into subtasks.

Model a subprogram for each sub-task.

Call the subprograms in the main program.

We may use one of the top-down or bottom-up variants while implementing modular programming. Regardless of which of the two approaches are used, writing a program in modular form makes it easier to detect the source of the error and to test it in isolation as compared to writing the program as one function. The individual modules can be retested if an error is detected after testing the program.

Top-down modular programming

The basic property of top-down design prescribes that a program may be divided into a main module and a set of associated modules. Each module may be subdivided into sub-modules as per programming style. This programming style is also known as stepwise refinement because of the hierarchical structure it imposes on the program design. It begins by defining the solution at the apex level of functionality and flouting it further into minor routines that can be simply documented and coded. The division of modules processes until the module consists only of straightforward processes that are intrinsically understood and cannot be further subdivided.

Top-down algorithm design is a programming approach that involves breaking down the requirements into smaller and simpler fragments organized in a hierarchy of modules. Each fragment has a single entry point and a single exit point, allowing control to flow downward through the structure without any unconditional branches to higher levels. The top-down programming generates modules based on functionality, usually functions, procedures, or methods. Refer to the following Figure 1.4:

Figure 1.4: Top-down programming

Bottom-up modular programming

Bottom-up design is a system design approach that can be thought to be the opposite of what is done in top-down design. It is a style of programming where an application is constructed beginning with primitives of the programming language and gradually constructing more and more complicated features until all the application has been formulated. Starting the design with specific modules. Once these modules are designed or developed, these modules are connected to make a bigger module (more complex structures), ending at the top. The procedure is repeated till the whole system is constructed. Bottom-up model enables us to have the luxury of decision-making at low levels and to decide upon the re-usability of components. The bottom-up method is widely used for testing because each of the lowest-level functions is written and tested first. Since the lowest-level modules have already been tested, any detected errors are probably due to the higher-level modules. This process continues, moving up the levels until the main function is finally tested. Refer Figure 1.5:

Figure 1.5: Bottom-up programming

Some key differences between the Bottom-Up and Top-Down programming models are discussed in Table 1.1:

Feature

Comparison

Approach

Bottom-up is a code-first approach, where developers start by writing small code modules or functions and building up from there. Top-down is a requirements-first approach, where developers start with defining the high-level goals and requirements of the system and breaking them down into smaller modules.

Focus

Bottom-up focuses on the details of individual components and how they fit together. Top-down focuses on the high-level goals and requirements of the system and how they can be achieved through the individual components.

Complexity

Bottom-up is often used for complex systems, where it is easier to manage complexity by building up from small components. Top-down is often used for simpler systems or systems with well-defined requirements.

Testing

Bottom-up is easier to test at the module level since each module can be tested separately. Top-down testing requires integration testing to ensure all the modules work together correctly.

Table 1.1: Bottom-up versus Top-down programming

Bottom-up and Top-down programming models are two different approaches to developing software applications, each with advantages and disadvantages. The choice of approach depends on the specific requirements of the system, the complexity of the application, and the preferences of the development team.

Structured programming

This programming style is known by various names: Procedural decomposition, Structured programming, modular programming, and so on. Structured programming is a programming model that enables the creation of programs with reusable components.

Structured programming aims to enhance the accuracy and efficiency of program development by employing organized control flow constructs such as conditional statements, loop structures, block formations, and subroutines. This stands in contrast to the use of tests and jumps (such as the go-to statement), which are less structured and can lead to less precise and more time-consuming programming.

In structured programming, the level of abstraction among the modules is low as the implementation particulars of various data structures must be shared amongst the modules and are thereby exposed. This entices other modules to use these implementation details, thereby creating undesirable dependencies between different program parts. The key disadvantage is that all decisions made from the beginning of a project rest on the high-level specification of the application.

Advantages of structured programming:

It is easier to comprehend.

This approach is user-friendly.

It is easier to maintain.

It is problem-specific rather than machine-specific.

It requires less effort and time, which makes development easier.

Relatively easy to debug.

Generally, it is machine-independent

Disadvantages of structured programming:

Structured programming is designed to be an inflexible set of rules for organizing code. This can make code more predictable and easier to maintain, but it makes it less flexible and adaptable to changing requirements.

The structure can become more complicated as programs become more complex, leading to difficulty in debugging, testing, and maintenance.

Structured programming is not designed to handle non-linear control flow. For example, if you want to jump from one part of the program to another, it can be difficult in a structured programming paradigm.

Structured programming requires that code be organized into functions and modules, which can lead to code duplication. This can be especially problematic when working with large programs with many modules and functions.

Structured programming can lead to tightly coupled code and is difficult to reuse in other projects, leading to duplication of effort and a lack of consistency across projects.

Object Oriented Programming

The motivation for switching over to a fresh paradigm like the Object-Oriented Programming (OOP) approach was to eradicate some of the flaws that were inherently there in the procedural/ modular approach. OOP considers data a critical component in program development and does not allow it to flow arbitrarily around the system. It ensures that data is closely tied to the function that operates on it and protects it from accidental modification from external functions. In object-oriented programming, an object is an instance of a class that contains data and behavior and may interact with other objects through methods. Figure 1.6 illustrates how data and methods (functions) are organized in object-oriented programs. Only the function related to that object can access that object’s data. However, one object’s function can access another object’s function.

Figure 1.6: Object-Oriented Programming

Features of Object-Oriented Programming:

The data structure contains all the functions that work on an object’s data.

Data is concealed and inaccessible to external functions.

Objects communicate with one another using functions.

Anytime new information or features are required, they may be quickly added.

OOP uses a bottom-up approach to design programs.

Algorithms

An algorithm is a series of instructions that, when correctly followed, solves a specific computer problem. An algorithm is a process expressed as a series of stages and specifies a set of instructions that must be followed in order to get the intended result. An algorithm is called programming language agnostic since it can be implemented in any computer language. An algorithm does not follow a strict syntax, as is the case with writing a computer program since it is not to be executed on a computer. The practice of devising algorithms is primarily done for designing or understanding purposes. Once the design is anticipated with the aid of an algorithm, the algorithm may be implemented using any programming language.

Features of an algorithm

An algorithm should have the below-mentioned features:

An algorithm must have zero or more inputs, also known as the information or data, to process to function.

An algorithm must have at least one output, the information or results generated after input processing.

An algorithm must contain a set of exact instructions that are easy to understand and execute to produce the intended outcome. The algorithm must be well-defined at every stage, with no opportunity for interpretation.

An algorithm must be finite, meaning it must finish after a set number of steps and not continue endlessly or loop infinitely.

An algorithm must solve the issue it was created to address in a fair length of time and with an acceptable expenditure of resources.

An algorithm has to be broad enough to handle a variety of input data rather than simply a small subset.

An algorithm must be implementable given the resources and technology at hand in order to be considered practical.

Writing an algorithm

There are no well-defined standards (as already discussed above) for writing algorithms. One of the reasons is that since we do not have to run algorithms on a computer, which would have necessitated a proper way of writing it (syntax), a user can choose how to write it, and it is problem-specific.

Basic coding features like repetition statements, conditional statements, and so on are shared by all programming languages. We can design an algorithm in general, which may be later implemented using any programming language. Algorithms are often written in stepwise format; however, this may not always be the case. After the problem area is well defined, the process of formulating an algorithm for the problem is carried out. To conclude, we may say that we need to be conscious of the problem at hand going forward and building the solution.

Further, the way of execution of the program shall be categorized into three ways:

Sequence statements

Selection statements

Iteration or looping statements

Sequence statements: The algorithm steps are executed one after another sequentially.

Here are some examples of the same:

Algorithm to print a welcome message:

1. Start

2. output "Hello world"

3. Stop

Algorithm to add two numbers:

1. Start

2. Input num1 and num2

3. sum = num1 + num2

4. output sum

5. End

This algorithm takes two numbers (num1 and num2) as input, adds them, and outputs their sum.

Algorithm to find an average of 2 numbers:

1. Start

2. Input num1 and num2

3. sum = num1 + num2

4. avg = sum/2

5. output avg

6. End

This algorithm takes two numbers (num1 and num2) as input, adds them, divides their sum by 2 (number of input values), and outputs the average.

Algorithm to subtract two numbers:

1. Start

2. Input num1 and num2

3. diff = num1 - num2

4. output diff

5. End

This algorithm takes two numbers (num1 and num2) as input, subtracts the second number from the first, and outputs their difference.

Algorithm to multiply two numbers:

1. Start

2. Input num1 and num2

3. product = num1 * num2

4. output product

5. End

This algorithm takes two numbers (num1 and num2) as input, computes their product and outputs their product.

Algorithm for division:

1. Start

2. Input num1 and num2

3. quotient = num1 / num2

4. output quotient

5. End

This algorithm takes two numbers (num1 and num2) as input, divides the first number by the second, and outputs the quotient.

Algorithm to find the area of a circle:

1. Start

2. input r

3. area = 3.14 x r x r

4. output area

5. Stop

This algorithm takes the radius (r) of a circle as input and calculates the area of the circle as per the formula. Please remember that we need not take the value of Pi (3.14) as input, as it is a constant.

Algorithm to find the area of a rectangle:

1. Start

2. input L and B

3. area = L * B

4. output area

5. Stop

This algorithm takes the length (L) and breadth (B) of a rectangle as input and calculates the area of the rectangle as per the formula.

In the above-mentioned examples, all the instructions/ steps are executed one after the other. These examples follow a sequential pattern of execution.

Selection statements: A selective statement algorithm is a computer program that allows a computer to make decisions based on certain conditions or criteria. Here are some examples of selective statement algorithms:

If-else statement: The if-else statement is a conditional statement that executes a certain block of code if a certain condition is met and another block of code if the condition is not met. For example:

if age >= 18

output "You are an adult."

else:

output "You are not an adult yet."

Here, the algorithm checks whether the age is greater than or equal to 18. If it is, the program prints You are an adult. If it is not, the program prints You are not an adult yet.

Algorithm to check whether a person can vote:

1. Start

2. input age

3. if age < 18, go to step 5

4. output "Can vote" and go to step 6

5. output "Cannot vote"

6. Stop

This algorithm takes age as input and checks whether the age is greater than or equal to 18. If so, You are an adult would be displayed; otherwise, You are not an adult yet would be displayed.

Algorithm to check whether the given number is positive or negative:

1. Start

2. input n

3. if n>= 0 then go to step 5

4. output "Given number is negative" and go to step 6

5. output "Given number is positive"

6. Stop

This algorithm takes a number (n) as input and checks whether it is less than 0. If so, Given number is negative would be displayed. Otherwise, Given number is positive would be displayed.

Iterative statements: Iterative algorithms are a class of algorithms that solve problems by repeatedly refining an initial estimate or solution until a desired level of accuracy or convergence is achieved. These algorithms are particularly useful when it is difficult or impossible to obtain an exact solution to a problem using analytical methods.

Iterative algorithms can be very efficient for solving certain types of problems, but they can also be computationally expensive if the number of iterations required to converge is very large. Choosing an appropriate stopping criterion and optimizing the algorithm’s parameters can help to minimize the computational cost of iterative algorithms.

In iterative statements, we start from an initial value (initialization) and add (increment) or subtract (decrement) a value to the initial value with each iteration and try to reach a final value (limit).

Algorithm to print natural numbers up to n:

1. Start

2. input L

3. i = 1

4. if i > L, then go step 8

5. output i

6. Increment value of i by 1

7. Go to step 4

8. Stop

In this algorithm, we try to generate a series from 1 to a given limit (L). step 4 checks whether the value of i (which acts as a counter in this example) is within the bounds (< L). If so, the value of i is printed and incremented by i before the condition at step 4 is again checked. Once the condition at Step 4 fails, control goes to Step 8, which marks the end of the algorithm.

Please note that for values of L greater than 1, Steps 4, 5, 6, and 7 are executed more than once.

Algorithm efficiency

Algorithm efficiency refers to the measure of the computational resources required by an algorithm to solve a given problem. The efficiency of an algorithm is typically measured in terms of its time complexity and space complexity.

Time complexity is a measure of the number of operations performed by an algorithm as a function of the size of its input. The time complexity of an algorithm is usually denoted by the big-O notation, which gives an upper bound on the growth rate of the number of operations as the input size increases.

Space complexity, on the other hand, is a measure of the amount of memory required by an algorithm as a function of the size of its input. Space complexity is also typically expressed using big-O notation.

Efficient algorithms are those that have low time and space complexity, meaning they can solve problems quickly and with minimal use of memory. In practice, the efficiency of an algorithm can have a significant impact on the performance of a software system, particularly for large-scale or real-time applications. Developers use various techniques to optimize algorithms, including analyzing the problem’s structure to identify the most time - and memory-consuming operations, implementing data structures that minimize search and update time, and parallelizing the computation across multiple processors or nodes. It is important to choose an algorithm that is appropriate for the size and nature of the problem at hand and to optimize the implementation of the algorithm to achieve the desired level of efficiency.

Importance of algorithmic efficiency

The number of resources a computer uses to process an algorithm may be used to determine algorithm efficiency. An effective algorithm uses the least number of resources possible to carry out its tasks. An algorithm’s effectiveness is evaluated to make sure it operates without hitches or mistakes. It is doubtful that inefficient algorithms will result in satisfactory outcomes.

Algorithm time complexity cases

The complexity of an algorithm is influenced by several variables, including the comparisons and data processing. For instance, throughout the execution of an algorithm, we may count the number of times data is transported and a key (value) is compared. There are three different time complexities that may be considered when assessing an algorithm’s complexity:

Best case complexity

Average case complexity

Worst-case complexity

The best case means when the algorithm performs the best, the worst case scenario is when the algorithm performs the worst, and the average case lies between the two extremes. For example, an algorithm that searches a given value in a list (array). The best case may be when the value to be searched is found at the beginning of the list, and we do not have to go far to find it. The worst case would be if we found the value at the end of the list or could not find the value (in that case, we also have to traverse the whole list). The average case is when the given value is found somewhere in between the list of values.

Algorithm efficiency

The efficiency of an algorithm is a cumulative end-product of two stages:

Theoretical analysis

Benchmarking

Theoretical analysis

The method of asymptotically (arbitrarily) approaching a value while performing theoretical analysis is to approximate the complexity of an algorithm. Big-O notation, created by Donald Knuth, is the most popular method of expressing how many resources an algorithm needs. Algorithms are evaluated to make sure they are scalable and effective at the same time, regardless of the magnitude of input data.

Benchmarking

Standards are used to assess an algorithm’s performance while evaluating its efficiency. This helps to compare an algorithm’s efficiency to that of its competitors. For instance, we may assess a sorting algorithm’s effectiveness against standards established by a different sorting algorithm. Analysts can compare algorithm speed using benchmarking to see whether there is room for improvement. Performance can also be evaluated in comparison to any earlier or comparable algorithms.

Implementation challenges

Performance can be impacted when assessing space and time complexity by variables such as the processor (parallel processing), instruction set utilized by a processor, cache locality, multi-threading, and multitasking, regardless of the programming language or the way the algorithm is designed. For an algorithm to be processed quickly, it must function flawlessly. Because of this, each new algorithm is reviewed using theoretical analysis and tested to see how successful it is before being compared to other algorithms. Time complexity and space complexity are the two main metrics for evaluating algorithm efficiency, which establishes how many resources are needed for a system to process it. It is possible for implementation issues to arise due to the processor, instruction set, programming language, and other elements, giving programmers a headache.

Flowcharts

Flowchart is a means to visually present the flow of data through an information processing system, the operations performed within the system, and the sequence in which they are performed. We shall concern ourselves with the program flowchart, which describes what operations (and in what sequence) are required to solve a given problem. The program flowchart can be likened to the blueprint of a building. As we know, a designer draws a blueprint before starting construction on a building. Similarly, a programmer prefers to draw a flowchart prior to writing a computer program. As in the case of the drawing of a blueprint, the flowchart is drawn according to defined rules and using standard flowchart symbols prescribed by the American National Standard Institute (ANSI).

History of flowcharts

Flowcharts were introduced by Frank Gilberth in 1921, and they were called Process Flowcharts at the beginning. Allan H. Mogensen is credited with training businesspeople on the use of Flowcharts.

Flowchart symbols

Before drawing flowcharts, we need to understand the different symbols used in flowcharts. Most people are aware of basic symbols like processes and decision blocks. But there are many more symbols to make your flowchart more meaningful. Figure 1.7 shows all the standard flowchart symbols:

Figure 1.7: Flowchart symbols

Most people do not know about some rarely used flowchart symbols like sequential access storage, direct data, manual input, and so on. Check the flowchart symbols (http://creately.com/diagram-type/objects/flowcharts) page for a detailed explanation of different symbols. Although these are the standard symbols available in most some people use different shapes for different meanings. The most common example of this is using circles to denote the start and end.

The examples in this flowchart guide will stick with the standard symbols and how to draw a flowchart.

Flowcharts can be categorized into four main types:

Document flowchart

System flowchart

Data flowchart

Program flowchart

Meaning of a flowchart

Flowchart is a diagrammatic depiction that shows the series of steps that must be taken to solve an issue. Typically, flowcharts are created in the initial stages of developing computer solutions. Flowcharts make it easier for businesspeople and programmers to communicate. These flowcharts are essential for problem programming and are very useful for deciphering the reasoning behind challenging and drawn-out issues. It is simple to develop the program in any high-level language after the flowchart has been created. We frequently see how flowcharts aid in describing the program to others. As a result, it is true to assert that a flowchart is required for improved program documentation.

Flowcharting design guidelines:

In drawing a proper flowchart, all necessary requirements should be listed in logical order.

The flowchart should be unambiguous, clear, neat, and easy to comprehend.

The direction of the flow of a procedure is from top to bottom (or left to right).

Only one flow line must come out of a process symbol.

A single flow line must enter a decision symbol. However, more than one flow line may emanate the decision symbol.

A single flow line is used in concurrence with a terminal symbol.

Briefly express yourself via common flowchart symbols.

In case the flowchart becomes complex, we may use connector symbols to decrease the number of flow lines. We must also avoid the intersection of flow lines for the sake of clarity.

We must ensure that a flowchart has a logical start and finish.

It is useful to validate the flowchart by passing through various test runs.

Flowchart examples

Sequential flow-based flowcharts: The following are some examples for the sequential flow-based flowcharts:

Figure 1.8: Flowchart to display a message

Figure 1.9: Flowchart to add two numbers

Figure 1.10: Flowchart to find the percentage of students in 3 subjects

Figure 1.11: Flowchart to find the simple interest

Figure 1.12: Flowchart to find the average of two numbers

Figure 1.13: Flowchart to find the area of a circle

Figure 1.14: Flowchart to find the area of a rectangle

Condition-based flowchart: A condition-based flowchart, sometimes known as a decision-making flowchart or conditional flowchart, is a graphic representation that shows the order in which steps or actions should be executed in a process depending on particular circumstances or criteria. It is a tool used to show how a process works and how it reacts to different inputs and circumstances.

The following are some instances of condition-based flowcharts:

Figure 1.15: Flowchart to find whether a given number is +ive or -ive

Figure 1.16: Flowchart to find the larger number among two numbers

Loop-based flowcharts: A loop-based flowchart is a visual representation used to show the order of operations in a process, algorithm, or software application that contains repetition. Loops are used to carry out a certain set of instructions continuously until a particular condition is fulfilled in programming and process design. This repetition pattern is graphically represented by a loop-based flowchart.

Figure 1.17: Print a test message five times

Figure 1.18: Print first ten natural numbers (1 to 10)

Figure 1.19: Flowchart to print the sum of the first ten natural numbers

Nested conditions: When decision symbols are nested within other decision symbols in a flowchart, a hierarchical structure is formed that can accommodate more complicated branching logic. This is frequently used to illustrate situations where several conditions must be assessed in a particular sequence.

Figure 1.20: Flowchart to print 1-digit even natural numbers

Figure 1.21: Flowchart to find the greatest of 3 numbers

Finite and infinite loop: In looping statements, a finite loop is one in which a series of statements is run n times (a predetermined number of times). The term infinite loop refers to a situation in which a collection of assertions is repeated indefinitely without reaching a conclusion. For instance, if we do not increase the value of the index, we will have an unending (infinite) loop.

Figure 1.22: Flowchart to describe an infinite loop

An infinite loop is a series of assertions that are repeated endlessly.

In the above flowchart, the value of I is not incremented, so it will create an endless loop. This is also called an infinite loop.

Conclusion

As we come to the end of this initial chapter, we have established the fundamental groundwork for your journey into the realm of C programming. From comprehending the intricacies of computer systems to gaining a firm grasp of programming methodologies, we have provided you with essential building blocks to start your exploration.

By acknowledging the role of computers as effective problem solvers, we have broadened your understanding of their capabilities. We have delved into the systematic approaches employed in programming and harnessed the power of algorithms to streamline tasks for optimal efficiency. Furthermore, we have introduced the concept of flowcharts to visually represent complex processes, enhancing your comprehension.

With these foundational concepts now in your possession, you are well-prepared to dive into the world of C programming. Armed with this knowledge, you will confidently navigate the challenges and opportunities that await you in your coding endeavors. The synthesis of these basic elements will serve to amplify your skills and innovative potential within the realm of C programming.

Exercise

In what language can computers execute instructions?

Explain the block diagram of the computer and Its components.

Describe the steps required to solve a problem on a computer.

Differentiate between assembly language and machine language.

Write down two advantages of a high-level language over an assembly language.

What tasks are performed by a compiler?

Differentiate between compiler, linker, and interpreter.

What are various software types?

What are the categories of application software?

Distinguish between an algorithm and a flowchart.

What are the three main categories programming instructions fall into?

Differentiate between OOP and functional & modular programming.

What do you mean by debugging a program?

What are the major programming paradigms/ styles?

Draw a flowchart/ write an algorithm to convert the input Celsius degree into its equivalent Fahrenheit degree. [formula: F = (9/5) *C+32].

Draw a flowchart/ write an algorithm to compute the volume of a sphere. Use the formula: V = (4/3) *pi*r3 where pi is equal to 3.1416 approximately.

Draw a flowchart to find the sum of the first 100 natural numbers.

Draw a flowchart to find the largest of 3 numbers.

Draw a flowchart to check whether a given number is prime or not.

Write an algorithm to find the percentage of students in 3 subjects. Assume Max. Marks of 100 in each subject.

Join our book’s Discord space

Join the book’s Discord Workspace for Latest updates, Offers, Tech happenings around the world, New Release and Sessions with the Authors:

https://discord.bpbonline.com

CHAPTER 2C Programming Fundamentals

Introduction

Dennis M. Ritchie conceived the high-level, general-purpose language C to create the UNIX operating system at Bell Labs. On the DEC PDP-11 computer, C was first introduced in 1972.

The K and R standard, recognized as the first publicly accessible definition of C, was created in 1978 by Brian Kernighan and Dennis Ritchie. The C programming language was used to build the UNIX operating system, the C compiler, and practically all UNIX application programs. The C is still a popular programming language because of the following reasons:

It is easy to learn.

It is a structured language.

We can design efficient programs using C.

It handles low-level activities.

Programs written in C can be compiled on various computer platforms.

Structure

The chapter covers the following topics:

Facts about C

C program components

Tokens in C

Data types

Variables

Integers data types

Literals

Storage class

Operators

Input-output in C

Write basic programs in C

Objectives

This chapter aims to provide a comprehensive understanding of the C programming language, its components, and its practical applications. We will begin by exploring important facts about C and its relevance in modern programming. The chapter will then focus on Turbo C++, a popular development environment for Windows 10 and beyond, guiding setting up and utilizing this platform. We will dive into the fundamental components of C programs, including tokens, data types, variables, and literals. Additionally, we will examine the concept of storage classes and the wide range of operators available in C. With a strong foundation established, we will learn about input-output operations in C and provide hands-on experience by writing basic programs. By the end of this chapter, readers will have a solid understanding of C programming, enabling them to develop functional and efficient programs using the language.

Facts about C

The widely used programming language C has a long history and a wide range of capabilities. The following are some facts about C programming:

Dennis Ritchie at Bell Labs developed C in the early 1970s as a programming language for the Unix operating system.

Since C is a compiled language, the source code is converted into machine code that a computer can use to run it.

Thanks to its low-level capabilities, programmers may modify hardware and memory directly by writing code in high-level C.

C is easy to learn and read because of its syntax and sparse use of keywords.

Device drivers, operating systems, embedded systems, and other types of low-level software frequently employ C.

A robust standard library for C, including functions for input/output, memory management, string manipulation, and other things.

The object-oriented and structured programming paradigms are both supported in C.

Due to its low-level features and standardized libraries, C programs may be very portable across several systems.

C++, Java, and Python are just a few programming languages impacted by C.

Despite its antiquity, C is still widely used today, and many software engineers believe it to be a crucial ability.

Uses of C programming language

C was first employed for system development, including creating the operating system’s programs. Since C produces code that executes almost as quickly as code written in assembly language, it was adopted as a system development language. Several uses for C could include:

Design of operating systems.

Design of assemblers and compilers.

Development of text editors.

Designing network drivers.

Formulation of databases.

Design of language interpreters.

Design of utilities.

Life cycle of a C program

A C program’s life cycle may be broken down into many phases, each of which has a specific role to play in the creation, use, and maintenance of the program, as depicted in Figure 2.1:

Figure 2.1: Phases of a program

Writing the source code

Computer programs are written using English-like high-level programming languages, like C/C++. But computers cannot understand these programs written in high-level languages. Instead, it can understand only low-level language like machine language. We write our C using a text editor or an Integrated Development Environment (IDE).

Preprocessing

The preprocessor handles and processes directives. In this stage, operations like file inclusion (#include), macro expansion (#define,) and more are performed. The preprocessed source code is the result of this stage.

Compilation

The compiler converts the preprocessed source code into assembly language or another transitional representation. The compiler creates object code while examining the code’s grammar and semantics. The compiler will alert us of any syntactic mistakes.

Linking

The linker creates an executable file by combining the object code, the compiler produced with any required external functions or libraries we may access from other files.

Loading

The operating system loads the executable file into memory, which also allots the required resources. Now that the program is ready, it can be run.

Execution

At this stage, the processor goes through each instruction in the program one at a time and performs actions as specified by these instructions in collaboration with the operating system.

To execute C programs on the Windows operating system (This book portrays examples executed on the Windows platform), we need to install software/ IDE like Turbo C, Dev C++, NetBeans, Code::Blocks, and more.

Integrated Development Environment

We use an IDE to write program code. It aids a programmer in easily designing programs by lending a comprehensive set of services required for program designing. IDE improves the productivity of a programmer with the help of various tools. Using an IDE, we can run, debug, document, execute, and translate a program.

Mainly, an IDE includes three parts:

Source code editor: For writing source code/ instructions.

Automation tool: To compile the source code.

Debugger: To eradicate errors/ bugs from the code.

IDEs come with features like object and data modeling, testing, and source code libraries. Currently, there are IDEs available for several programming languages, including C. Many IDEs even have intelligent code autocompletion options to enrich the programmer’s experience. Let us discuss some IDEs you may use for coding in C.

GCC

The free software foundation created the GCC compiler, often used using a command line interface and based on Linux. If we use a UNIX or Linux variation, it is probably already installed on our machine because it is frequently packed and installed with Linux installations. We can invoke GCC on a source code file by typing gcc filename.

The default executable output of GCC is a.out, which can be executed by typing ./a.out. It is also permissible to specify a name for the executable file on the command line by writing -o outputfile, as shown below:

gcc filename -o outputfile

Again,./outputfile may be used to execute your program. (The ./ ensures the program is executed for the current working directory). You must specifically ask it to connect with that library if we need to utilize functions from the math library (often, math.h functions like sin or sqrt).

Note: If we need to use functions from the math library (generally functions from “math.h” such as sin or sqrt), then you need to explicitly ask it to link with that library with the “ –l ” flag and the library “m”: gcc filename -o outputfile -lm.

Visual Studio Code

Visual Studio Code (VS Code) is a good IDE for C programming overall because of its robust features, sizable extension library, and cross-platform compatibility. A sophisticated C programming environment may be created with the appropriate plugins and configurations in VS Code. Refer to the following Figure 2.2 for VSE Code IDE:

Figure 2.2: VSE Code IDE

VS Code is a popular code editor for various programming languages, including C. The following characteristics of VS Code make it a fantastic option for C programming:

IntelliSense: When writing C code, VS Code’s intelligent code completion system (IntelliSense) saves our time by suggesting code snippets and function definitions.

Debugging: The robust debugging mechanism in VS Code can assist you in locating and resolving errors in your C programs.

Extensions: VS Code has many add-ons that provide additional C programming features, such as syntax highlighting, code formatting, and tool interaction.

Integration of the terminal: VS Code has a built-in terminal that enables us to execute C programs right from the editor, which is particularly useful for debugging.

Git support: VS Code comes with Git support included, simplifying maintaining C projects.

Cross-platform compatibility: You can use VS Code on practically any platform because it is available for Windows, macOS, and Linux.

Turbo C++ for Windows 10+

Turbo C/ C++ is an IDE for writing code in the C programming language. Turbo C++ is the advanced form of Turbo C, which could also be used for C language programming.

Note: C++ compiler supports the C language syntax as C++ language is built on top of the C programming language.

To download Turbo C++ for Windows, simply google Turbo C++ and download it from some authentic website.

Figure 2.3: Turbo C/ C++ setup

Figure 2.3 appears when we start the Turbo C/ C++ setup. Just click Next and follow the instructions on the subsequent screens while installing the software.

Now to complete the setup, click Finish. Refer to the following Figure 2.4:

Figure 2.4: Completing setup

Next, open the program by clicking the Start menu | All Programs. Refer to the Figure 2.5:

Figure 2.5: Turbo C first screen

Now, we can write our C program using the editor. Simply use the File menu to create a new file. The Compile and Run menus help compile and execute the program, respectively, as shown in Figure 2.6:

Figure 2.6: New file

Eclipse

One of the most liked and practical IDEs for C/C++ development is Eclipse. It is open-source software originally used for Java programming and is now used for C and other languages running on Windows, Linux, and so on. Eclipse has several installable plug-ins that make programming easier. For developers, the Eclipse IDE is full of additional capabilities that make creating complex applications on the Eclipse platform easier. Refer to the Figure 2.7:

Figure 2.7: Eclipse IDE

Dev-C++

For Windows systems, Dev-C++ is a fully functional IDE for C and C++. Since the initial version of Dev-C++ was released in 1998, it has been used by millions of programmers, researchers, and students. Numerous C++ and scientific books have mentioned it, and colleges and schools worldwide continue to use it as a preferred teaching tool. Dev-C++ is a free, full-featured integrated development environment distributed under the GNU General Public License for programming in C and C++. Colin Laplace originally developed it. Dev-C++ is not as complex as the Eclipse IDE, but it has many features that the basic Turbo C/C++ IDE lacks. We have executed examples used in this book on this IDE. It is a free software package easily available on the internet to download and install. Refer to the Figure 2.8:

Figure 2.8: Dev-C++ IDE

Creating a source code

The source code file is a file with C programming instructions. To create a new source file, select File | New | Source File. The path is shown in Figure 2.9. This will open the code editor area where we can type our code. Refer to the following figure:

Figure 2.9: Opening a new source code file

Save file

Save your file by going to File | Save or File | Save As. Select Save Location and name your file. Since most modern C/ C++ IDEs (including Dev-C++) are meant for C++ execution, the default file extension is CPP. So, we must explicitly change the file type to a C source file (*.c). Refer the following Figure 2.10:

Figure 2.10: Writing the first C program

Compile code

Compile your code by going to Execute | Compile. This will compile the source code file by reviewing it and ensuring it has a valid C syntax. When the compilation is complete, we can close the compile window. We can see other options there as well which are as discussed as follows and in Figure 2.11:

Figure 2.11: Compile the code

Execute | Run (means we are executing the file without compiling it).

Execute | Compile and Run (compiles and then executes the file if error-free).

Execute/ Run code

To run your code, select Execute | Run, as seen in Figure 2.12. This will start execution of the program and should bring up a new window. Besides the program output, the window would show the time the program took to execute and tells us to hit any key to exit from the output screen. Please refer to the following figure:

Figure 2.12: Executed file with the output

Even if we try to skip the compilation process, the program is still compiled for errors.

C program components

A C program consists of several components that work together to perform a specific task. A C program consists of the following components:

Preprocessor directives: Preprocessor directives are commands that begin with the # symbol and modify the source code before compilation. Examples of preprocessor directives include #include (which includes header files), #define, and #ifdef.

Functions: Functions are blocks of code that perform a specific task. In C, functions must be defined before they can be invoked for execution. C programs typically have a main function, which is the entry point of a program.

Variables: Variables are used to store data that the program can manipulate. In C, variables must be declared before they can be used, and they can have different types, such as integers, floats, and characters.

Statements: Statements are the basic building blocks of a C program. They are used to perform actions, such as assigning values to variables, calling functions, and making decisions based on conditions.

Comments: Comments, denoted by ‘//’, are used to add explanatory text to the program. They are ignored by the compiler and are intended to help other programmers understand the code.

Header files and libraries: Header files contain declarations for functions, variables, and other data types used in the program. They are typically included using the #include directive.

Let us look at a simple program that displays a simple message on the output screen:

#include <stdio.h> // header file

// first program

int main () {

printf ("This is my first C program");

return (0);

}

In the above program:

A preprocessor line (#include <stdio.h>) tells the compiler to include or link stdio.h file (because it contains the definition or code of functions invoked in the C program) to the current C project or program file before executing the program. The header file would be compiled with the C source file(s).

The next line starts with the symbol ‘//,’ which symbolizes a comment that a user writes for documentation purposes and is ignored (neither compiled nor executed) by the compiler. We represent multiline comments by /*...*/. The purpose of comments is to add readability, explanation, or annotation to the program.

The next line starts the definition of function main (): It is the entry point of a C program that kick starts the program execution. A C program may consist of one or more functions, but the execution of a C program always starts with the main () function. The word int preceding main means the function shall return an integer at the conclusion of the execution (will be discussed in detail later).

On the next line, printf (...), a function available in C, displays the message: This is my first C program on the output screen.

The next line returns 0; returns control back to handle where the execution had started, and returns a value 0 (to signify that the program executed successfully).

The last line ’}’ marks the end of the main function, and hence the program stops.

Assignment: Write, compile, and execute the above C program.

You have seen the basic structure of a C program, so it will be easy to understand other basic building blocks of the C programming language.

Tokens in C

In C programming, the token is the smallest unit of a program that is meaningful and can be parsed by the compiler. A C program consists of several tokens, collectively forming a large chuck of a program. The compiler can analyze and understand the program structure and syntax by breaking a C program into its constituent tokens, allowing it to generate executable code. The C language has six types of tokens:

Keywords: These are reserved words with a specific meaning in the C language. Examples of keywords in C include if, else, while, return, and more.

Identifiers: Identifiers are names given to variables, functions, and other program entities. Identifiers in C must begin with a letter or underscore and can contain letters, digits, and underscores.

Constants: Constants are fixed values that do not change during program execution. Constants in C can be of several types, including integer constants, floating-point constants, character constants, and string literals.

Strings: Strings are sequences of characters enclosed in double quotes. In C, strings are treated as arrays of characters and can be manipulated using a variety of string functions.

Operators: Operators are symbols that perform operations on one or more operands. Examples of operators in C include arithmetic operators (+, -, *, /), relational operators (==, !=, <, >), and logical operators (&&, ||, !).

Special symbols: These include punctuation marks and other special characters used to separate program elements or provide additional information to the compiler. Examples of special symbols in C include semicolons (;), commas (,), parentheses (()), and braces ({}).

Example: Consider the statement:

printf ("My first C Program ");

The individual tokens are:

Printf

(

"My first C Program"

)

;

Semicolons

In a C program, the semicolon is a statement terminator. That is, each statement must end with a semicolon. It indicates the end of one logical instruction. Given below are two different statements:

printf ("Hello, World! \n");

return 0;

Comments

Comments are like helping text in your C program; the compiler ignores them. They start with /* and terminate with the characters */ as shown below:

/* my first program in C */

Comments within comments are not allowed, and they do not occur within a string or character literals.

Keywords

The following list shows the reserved words in C. These reserved words may not be used as constants, variables, or other identifier names. Refer to the Figure 2.13:

Figure 2.13: Keywords in C

Whitespace in C

Whitespace in C refers to any sequence of one or more spaces, tabs, and newline characters that are used to separate tokens in a C program. The C compiler ignores whitespace except when it separates adjacent identifiers, keywords, or constants that would otherwise be interpreted as a single token.

Whitespace is used in C programs to improve readability and organization. It can be used to visually separate blocks of code, align variables or function calls, and add comments to the program. Here are some examples of how whitespace is commonly used in C programs:

Indentation: Indentation uses whitespace to align code blocks, making it easier to see the program’s structure.

Spacing: Spacing uses whitespace to separate tokens and improve readability.

Line breaks: It is used to separate statements and improve program readability.

Operators

An operator is a symbol that tells the compiler to perform specific mathematical or logical functions. C language is rich in built-in operators and provides the following types of operators:

Arithmetic operators

Relational operators

Logical operators

Bitwise operators

Assignment operators

Miscellaneous operators

Any programming language must include operators, C is no exception. Their importance can be attributed to the fact that they are used to carry out different operations on variables and values in C, simplifying the manipulation of data and the execution of intricate calculations. We will encounter types of operators in the following sections.

Arithmetic operators

In C, arithmetic operators are used to execute operations on numeric data types:

‘+’ operator is used to denote the addition operation.

‘-‘ operator is used to denote the subtraction operation.

‘*’ operator is used to denote the multiplication operation.

‘/‘ operator is used to denote the division operation and returns the quotient.

‘%‘ operator is used to denote the division operation and returns the remainder.

For the two variables x and y

int x = 10, y = 5;

int s = x + y; // addition

int d = x - y; // subtraction

int p = x * y; // multiplication

int q = x / y; // integer division

int r = x % y; // modulus

Relational operators

In C, relational operators are used to compare items and discover their relation. A relational expression’s outcome is either true or false, which are represented as the integer values 1 and 0, respectively, in C.

Here are the relational operators in C:

`==` (equal to): This operator compares the values of two operands and returns true if they are equal. For example:

if (a == b)

`!=` (not equal to): This operator compares the values of two operands and returns true if they are not equal. For example:

if (a != b)

`<` (less than): This operator compares the values of two operands and returns true if the left operand is less than the right operand. For example:

if (a < b)

`>` (greater than): This operator compares the values of two operands and returns true if the left operand is greater than the right operand. For example:

if (a > b)

`<=` (less than or equal to): This operator compares the values of two operands and returns true if the left operand is less than or equal to the right operand. For example:

if (a <= b)

`>=` (greater than or equal to): This operator compares the values of two operands and returns true if the left operand is greater than or equal to the right operand. For example:

if (a >= b)

Relational operators are often used in control structures like if statements and loops to test for specific conditions and control program flow accordingly.

Logical operators

In C, logical operators combine and manipulate Boolean expressions (which evaluate to True or False). There are three logical operators in C: `&&` (logical AND), `||` (logical OR), and `!` (logical NOT).

The `&&` (logical AND) operator evaluates two Boolean expressions and returns `true` if both are `true`, and `false` otherwise. For example:

if (x > 5 && y < 30)

In this example, the `&&` operator combines two Boolean expressions, `x > 5` and `y < 30`, and evaluates to True if both these statements are true.

The `||` (logical OR) operator evaluates two Boolean expressions and returns `true` if at least one of the expressions is `true` and `false` otherwise. For example:

if (x > 5 || y < 10)

In this example, the `||` operator combines two Boolean expressions, `x > 5` and `y < 10`. The `if` statement will be executed if at least one of the expressions is true.

The `!` (logical NOT) operator negates a Boolean expression, returning `true` if the expression is `false`, and `false` if the expression is `true`. For example:

if (!(x > 5))

In this example, the `!` operator is used to negate the Boolean expression `x > 5’.

Logical operators are typically used in conditional statements and loops to test whether certain conditions are true or false. They can also be used to simplify Boolean expressions and make code more readable.

Bitwise operators

C uses bitwise operators to perform bit-level operations on integers. They include:

Bitwise AND

Bitwise OR

Bitwise XOR

Bitwise NOT

Left shift

Right shift

Assume a and b to be two variables:

int a = 10, b = 20;

Bitwise AND of (two) bits returns 1 if both bits are 1.

int res_AND = a & b; // Bitwise AND

Bit 1

Bit 2

Result Bit 1 AND Bit 2

0

0

0

0

1

0

1

0

0

1

1

1

Table 2.1: Truth table of AND operator

Bitwise OR of (two) bits returns 1 if at least one of the bits is 1.

int res_OR = a & b; // Bitwise OR

Bit 1

Bit 2

Result Bit 1 OR Bit 2

0

0

0

0

1

1

1

0

1

1

1

1

Table 2.2: Truth table of OR operator

Bitwise XOR returns a 1 when the bits are different and otherwise 0.

int res_XOR = a ^ b; // Bitwise XOR

Bit 1

Bit 2

Result Bit 1 XOR Bit 2

0

0

0

0

1

1

1

0

1

1

1

0

Table 2.3: Truth table of XOR operator

Bitwise NOT returns the inverse of the bit:

Int res_NOT = ~a; // bitwise NOT

Left Shift moves bits to the left:

int Lshift = a << 1; // left shift

Right Shift moves bits to the right:

int Rshift = a >> 1; // left shift

Assignment operators

The set of operators that are used in C to assign values to variables are referred to as assignment operators. Some of the commonly used assignment operators in C are:

= (Equal to): This operator assigns a value to a variable.

For example:

x = 5;

+= (Add and assign): This operator adds the value on the right side to the value on the left side and assigns it to the left side variable.

For example:

x += 5; (x = x + 5;)

-= (Subtract and assign): This operator subtracts the value on the right side from the value on the left side and assigns it to the left side variable.

For example:

x -= 5; (x = x – 5;)

*= (Multiply and assign): This operator multiplies the value on the right side with the value on the left side and assigns it to the left side variable.

For example:

x *= 5; (x = x * 5;)

/= (Divide and assign): This operator divides the value on the left side by the value on the right side and assigns it to the left side variable.

For example:

x /= 5; (x = x / 5;)

%= (Modulus and assign): This operator finds the modulus of the value on the left side and the value on the right side and assigns it to the left side variable.

For example:

x %= 5; (x = x % 5;)

<<= (Left shift and assign): This operator shifts the bits of the value on the left side to the left by the number of positions specified by the value on the right side and assigns it to the left side variable.

For example:

x <<= 2; (x = x << 2;)

>>= (Right shift and assign): This operator shifts the bits of the value on the left side to the right by the number of positions specified by the value on the right side and assigns it to the left side variable.

For example:

x >>= 2; (x = x >> 2;)

&= (Bitwise AND and assign): This operator performs a bitwise AND operation between the value on the left side and the value on the right side and assigns it to the left side variable.

For example:

x &= 7; (x = x & 7;)

^= (Bitwise XOR and assign): This operator performs a bitwise XOR operation between the value on the left side and the value on the right side and assigns it to the left side variable.

For example:

x ^= 7; (x = x ^ 7;)

|= (Bitwise OR and assign): This operator performs a bitwise OR operation between the value on the left side and the value on the right side and assigns it to the left side variable.

For example:

x |= 7; (x = x | 7;)

sizeof (): This operator returns the size of a variable.

For example:

float a;

sizeof (a), a being a float, shall return 4 (Bytes).

& (Addressof) operator: Returns the address of a variable.

For example:

&a; returns the actual address of the variable ‘a’.

? conditional operator: Conditional operator (ternary operator) is an alternate way of writing an if-else statement in C.

Syntax:

condition? expression1: expression2

For example:

int max = a > b ? a : b;

If expression a > b is true, a is returned as the expression’s value, else b is returned as the expression’s value.

Operator precedence in C

Operator precedence controls how terms in an expression are grouped and how an expression is evaluated. Certain operators are more important than others; for example, the multiplication operator is more important than the addition operator.

For instance, in the expression, x = 7 + 3 * 2; x is assigned 13, not 20, since operator ‘*’ has a greater precedence than ‘+’.

Hence, multiplication operation takes place first and then the addition operation. Operators having a higher precedence appear at the top of the table, while those with the lower precedence appear at the bottom. In an expression, operators with higher precedence will be evaluated first, followed by the operators with lesser precedence.

In computer programming, the associativity of an operator determines the order in which the operations are performed when multiple operators of the same precedence are used in a single expression. There are three types of associativity. The associativity of an operator is usually specified in the programming language specification, and it is important to understand the associativity of operators to correctly evaluate complex expressions.

Left-to-right

Right-to-left

Non-associative

Left associativity: By left associativity, we mean that operations are performed from left to right. An example of a left-associative operator is the subtraction operator `-`.

Consider the following expression:

x - y - z

The subtraction operator is left-associative, so the operations are performed from left to right. The expression is evaluated as:

(x - y) - z

The subtraction between `x` and `y` is performed first, and then the result of that operation is subtracted from `z`.

Right associativity: By right associativity, we mean that operations are performed from right to left. The assignment operator (`=`) is an operator that is right-associative. Consider the following expression:

x = y = z;

The assignment operator being right-associative, the expression is evaluated from right to left.

Here, the value of `z` is assigned to `y`, and then the value of `y` (which is now equal to `z`) is assigned to `x`. If the assignment operator were left-associative, the expression would be evaluated from left to right, and the result would be different.

Note: The left-associativity only comes into play when operators have the same precedence. If operators have different precedences, the one with the higher precedence is evaluated first, regardless of associativity.

In C, the non-associative operators are those that cannot be used consecutively without the use of parentheses to specify the order of operations. These operators include:

The conditional operator `?:`: This operator is non-associative, meaning that it cannot be used consecutively without the use of parentheses. For example, the expression `a ? b : c ? d : e` is not valid and must be written as `a ? b : (c ? d : e)` to specify the order of operations.

The assignment operators `=`, `+=`, `-=`, `*=`, `/=`, `%=` and so on: These operators are also non-associative, meaning that they cannot be used consecutively without the use of parentheses. For example, the expression `a = b = c` is not valid and must be written as `a = (b = c)` to specify the order of operations.

The comma operator `,`: The comma operator is also non-associative, meaning that it cannot be used consecutively without the use of parentheses. For example, the expression `a = (b = c, d = e)` is valid, but the expression `a = b = c, d = e` is not valid and must be written as `a = (b = c), (d = e)` to specify the order of operations.

It is important to note that non-associativity is not the same as precedence. Precedence determines the order in which operators are evaluated, while associativity determines the order in which operators of the same precedence are evaluated. Non-associativity means that an operator cannot be used consecutively without specifying the order of operations using parentheses.

An operator’s associativity is governed by its precedence level. The associativity of operators with the same precedence level is examined. Here are the precedence levels and associativity of several typical C operators:

The postfix increment (++) and decrement (--) operators have the maximum precedence and are evaluated from left to right.

Prefix increment (++) and decrement (--) operators, as well as unary plus (+) and minus (-) operators, have the same precedence level as postfix increment and decrement operators, but they are evaluated right to the left.

Multiplication (*), division (/), and modulus (%) operators come next in the hierarchy of precedence levels and are evaluated left to right.

The addition (+) and subtraction (-) operators have the next greatest precedence level and are also assessed left to right.

Relational operators (==,!=,, =, >, >=) have the same precedence level as addition and subtraction operators and are evaluated left to right.

Logical AND (&&) and OR (||) operators are at the next level of precedence and are assessed left to right.

The assignment (=) operator has the lowest precedence level and is evaluated from right to left.

Data types

Data types in C define the type and size of data that can be stored in variables or used as function parameters. In C, there are two main categories of data types:

Primitive data types: Primitive data types are the basic building blocks of data in C. These are the fundamental data types in C and are used to represent simple values. The basic data types in C include:

char: Used to represent single characters.

int: Used to represent integers.

float: Used to represent floating-point numbers.

double: Used to represent double-precision floating-point numbers.

void: Used to indicate the absence of a type.

Derived data types: Derived data types in C are built from primitive data types. The derived data types in C include:

Array: Used to represent a collection of homogenous elements (data of the same type).

Pointer: Used to store the memory address of other variables.

Structure: Used to group related data types into a single entity.

Union: Like a structure, all its members share the same memory location.

Enumeration: Used to define a set of named integer constants.

Enumeration data types: These are used to represent a set of named integer constants.

User-defined data types: The programmer defines these using the basic and derived data types.

C also supports type modifiers, which can adjust the size or range of primitive data types. For example, the signed and unsigned modifiers can adjust the range of integer data types. The short and long modifiers can be used to adjust the size of integer data types. The long modifier can also modify the float and double data types to increase precision.

Data types in C are important because they help ensure that data is stored and manipulated correctly, preventing errors and bugs in the program. In addition, the appropriate use of data types can improve program performance by reducing memory usage and improving code efficiency.

When choosing a data type in C, it is important to consider the range and precision of the data being stored and the program’s memory usage and performance requirements. Careful consideration of data types in C can lead to more efficient and reliable programs.

The choice of data type in a C program depends on the type of data being used and the operations being performed on that data. For example, if a program needs to represent whole numbers, an int data type can be used, while if the program needs to represent decimal numbers, a float or double data type can be used. Choosing the appropriate data type ensures the program operates correctly and efficiently.

The types in C can be classified in Table 2.4:

Basic types

Integer

Floating point

Char

Enumerated types

Used to declare variables that can allocate only certain discrete integer values

The type void

void indicates that no value is available

Derived types

Pointer

Array

Structure

Union

Function

Table 2.4: Data types in C

The array and structure are referred to as aggregate types. The (return) type of a function specifies the type of the function’s return value.

Table 2.5 gives an account of standard types with their storage capacities and the range of values they can store:

Serial Number

Type

Size (in bytes)

Range

Unsigned char

1

0 to 255

signed char or char

1

-128 to +127

unsigned int

2

0 to 65535

signed int or int

2

-32,768 to +32767

unsigned short int

2

0 to 65535

signed short int or short int

2

-32,768 to +32767

unsigned long int

4

0 to +4,294,967,295

signed long int or long int

4

-2,147,483,648 to +2,147,483,647

long double

10

3.4E-4932 to 1.1E+4932

double

8

1.7E-308 to 1.7E+308

float

4

3.4E-38 to 3.4E+38

Table 2.5: C data types with storage capacity

We may use the sizeof function to get the size of a type or a variable. Syntax of the sizeof function is:

sizeof (type)

It shows the storage size of the type in bytes. For example:

printf ("Size for int: %d", sizeof (int));

Output:

size for int: 2 Bytes

Variables

Variable is a designated area of memory used by a program where data may be stored and changed. We may store and modify values of various data types, such as floating-point numbers, integers, characters, and so on, using variables. Throughout the course of a program’s execution, variables can be utilized for storing and handling data. The value stored in a variable can be changed during the execution of a program.

Variable declaration

Variable declaration in C programming is the process of defining a variable’s name and data type before it is used to store values. It provides the compiler with information on the kind of data the variable will store, enabling the compiler to allot the proper amount of memory. To ensure correct memory allocation and type handling during program execution, this step is essential.

The basic syntax for a declaration in C is:

type name;

where type specifies the data type of the object being declared, and name is the identifier used to refer to the object.

For example, to declare an integer variable named num, we would use the following declaration statement:

int num;

This statement declares a variable named num of type int.

Guidelines for declaring variables

When defining variables in C, there are certain standards and guidelines to follow. By following these guidelines, we can make our code more organized, legible, and error-free. The main guidelines for declaring variables in C are as follows:

Valid variable names: Variable names must begin with an uppercase or lowercase letter or with an underscore (‘_’). Variable names may include letters, numbers, and underscores after the first character. Variable names are case-sensitive, so 'Var' and 'var' are considered as two separate variables.

Avoid key keywords: The reserved words defined for C, known as keywords, cannot be used as variable names. We cannot declare a variable with the names like ‘if’, ‘for’, ‘break’, and more.

Use descriptive and meaningful names: We should use such names for variables that are evocative of their function. This improves the readability and comprehension of our code.

Do not use special characters: Special characters like “@,” “#,” “$,” “%,” and so on are not permitted in variable names.

Do not use spaces: Spaces are not permitted in variable names.

Use of underscores: We should select a naming pattern for variable names with multiple words like the roll number of a student can be stored in a variable named ‘Roll_No’.

Avoid underscore and uppercase at the beginning: In C, names that begin with an underscore and an uppercase letter are frequently reserved for system and library usage.

Data type declaration: Before being utilized, variables must be defined with their data types. Examples are “int age” and “double salary”.

Initialization: When a variable is declared, it can be initialized (given a starting value). ‘int count = 0;’ is an example.

Declare before use: Before variables are utilized in C code, they must first be defined. This means that a variable must be defined either before or at the start of a function if it is to be used in that function.

Note: We cannot declare variables with the same name more than once within the same scope as shown below:

int main () {

int x = 15;

int x = 20;

printf ("x = %d", x);

}

Output:

ERROR!

Using the typedef keyword to create user-defined data types is also possible in C. The basic syntax for a typedef declaration is:

typedef type new_type;

Where type is an existing data type, and new_type is the new name. For example, to create a new data type called myint that is equivalent to the int data type, we would use the following declaration statement:

typedef int myint;

After this declaration, we can use myint as a synonym for int throughout the program.

Initialization

Initialization in C refers to assigning an initial value to a variable or an array when declared. Initialization can be done using an equal sign (=) or braces ({}) followed by a list of values called an initializer. The syntax for initialization depends on the data type of the variable or array being initialized.

Some examples of initialization in basic data types C:

int x = 10; // x is initialized to 10

float y = 3.14; // y is initialized to 3.14

char c = 'A'; // c is initialized to 'A'

Similarly, as discussed in the upcoming chapters, we can initialize other (derived) data types, like array structures, pointers, and so on.

Initialization ensures that variables and arrays have an appropriate value when used in a program. Uninitialized variables can contain unpredictable values, which can cause bugs and unexpected behavior in the program.

Identifiers

An identifier is a name assigned to an entity in a program. The entities include a variable, function, class, or other programming constructs. Identifiers are used to uniquely identify these entities inside the code. They are composed of letters (uppercase and lowercase), numbers, and underscores (_) and must follow certain rules. Identifiers are used to enhance the program code readability and to identify program elements by meaningful names.

Some of the rules for naming identifiers in C are:

They must begin with a letter or an underscore and can consist of uppercase or lowercase letters, digits, and underscores.

In C, the identifiers are case-sensitive.

Identifiers should be named as descriptive and meaningful in the context in which they are used. For example, ‘salary’ is an appropriate name for an identifier rather than s.

Identifiers should not have special characters or spaces (@ or #).

The name of an identifier cannot be a global identifier, for example, (__TIME__, __LINE__, __TINY__, __SMALL__, __FILE__, __DATE__, and so on.)

The name of an identifier cannot be registered Pseudo variables like _AH.

We must not have more than one identifier with the same name within a block code or function.

Valid identifiers:

Age

Gender

Var

COUNT

_sdr

Roll_No

Invalid identifiers:

1sad (cannot start with a digit)

_AH (cannot use register Pseudo variable)

Roll No (contains a space)

__TIME__ (cannot use global identifier)

scanf (a C library function name)

sal# (contains a special character)

Using meaningful and descriptive identifiers is a good practice to make our code understandable and readable:

Difference between variables and identifiers

In C, a variable and an identifier are related concepts but differ. An identifier in C is a name given to a program’s variable, function, structure, or other entity. It is used to refer to that entity in the program code. The programmer chooses identifiers that can be made up of letters, digits, and underscores. They must start with a letter or underscore but cannot start with a digit. A variable in C is a named memory location that stores a value of a specific data type. Variables are declared using an identifier, which refers to the variable in the program code. The identifier assigns a value to the variable and retrieves the value stored in the variable. In other words, an identifier is a name given to a programming entity.

In contrast, a variable is a named memory location used to store a value of a specific data type. An identifier refers to a variable and other entities in the program, such as functions or structures. For example, in the following C code, count is the identifier that refers to the variable that stores the integer value:

``` int count = 0; ```

In this example, count is the identifier used to refer to the variable, but the variable itself is an integer data type that stores the value of 0. In summary, while an identifier and a variable are related concepts in C, they differ. An identifier is a name given to a programming entity, while a variable is a named memory location used to store a value of a specific data type.

Constants

In C programming, a constant is a value that cannot be changed during program execution. Constants represent fixed values that remain the same throughout the program’s execution. Constants in C can be of several types, including:

Integer constants: Integer constants are whole numbers that can be positive, negative, or zero. We can represent them in decimal, hexadecimal, or octal notation.

For example:

int x = 134; // decimal constant

int y = 02; // octal constant

int z = 0xB; // hexadecimal constant

Floating-point constants: Floating-point constants are numbers that include a fractional part. We can represent them in exponential or decimal notation.

For example:

float d = 3.1423; // decimal constant

float e = 6.02e23; // exponential constant

Character constants: Character constants are single characters enclosed in single quotes.

For example:

char f = 's'; // character constant

char g = '\n'; // character constant (newline character)

String literals: String literals are sequences of characters enclosed in double quotes.

For example:

char* h = "Program"; // string literal

Enumeration constants: User-defined constants represent a set of named integer values.

enum syntax

enum {comma separated enumeration constant list}

For example:

enum {Apple, Banana, Cherry}; // enumeration constant

We can use constants in C in expressions or assignments like variables. they offer a suitable way to store fixed value since their values do not change throughout program execution.

Integer data type

Integer-type variables in C may store zero, positive, or negative values without the decimal point. The integer data type, signed or unsigned, is indicated by the keyword int. Unsigned integer variables are assumed to have positive values by default. The integer data type is divided into short, int, and long.

Long data types need 8 bytes in 64-bit operating systems and 4 bytes in 32-bit operating systems, compared to 2 bytes of storage space for short data types and 2 or 4 bytes of storage space for int data types. If we try to assign a decimal value to an integer variable, the value after the decimal point will be truncated/discarded; only the whole number will be saved in the variable.

Integer expressions

An integer constant is often expressed as a combination of digits occasionally accompanied by a sign and commas to separate combinations of digits such as tens, thousands, hundreds, and more used in day-to-day life mathematics, for example, 504, 239, -6, 30,705 and -4812. When representing integers in C programming, only sign symbols and numbers from 0 to 9 are acceptable. For instance, 82,702 is an incorrect integer constant and must be represented as 82732 since commas cannot separate digits. Integer expressions can be made using arithmetic operations like addition, subtraction, multiplication, division (which produces quotient when dividing numbers), and modulo division (which produces remainder when dividing numbers).

For the declaration:

int x, y, z;

Examples of legal expressions:

x + 1

x + z - y * 2

x % 5

z + (x * 3 + y * 8) / 4

The plus (+), minus (-), and multiply (*) operators produce the anticipated results. However, the division (/) operator carries out integer division; if there is any remainder value, it is castoff, we say integer division truncates. Thus, 10/6 gives the value 1; the remainder 4 is discarded. What about -19/5 where the answer is –3. In the case of C programming, integer division trims toward 0. Though the actual value of -19 ÷ 5 is equal to -3.8, truncating toward 0 gives -3.

The modulo (%) operator produces the remainder when dividing one integer by another. Hence, 12 % 5 evaluates to 2.

Precedence of operators

Multiplication and division occur before addition and subtraction when evaluating expressions in C. In other words, division and multiplication take precedence over addition and subtraction.

Hence, in the case of the expression:

2 + 3 * 4

3 * 4 is evaluated first (=12), and then 2 is added to 12, producing 14 as the value of the expression.

However, to override the default precedence, we may use brackets to force the evaluation of bracketed expressions.

In the case of the expression:

(2 + 3) * 4

Bracketed expression (2 + 3) would be evaluated first, followed by the multiplication 5 and 4, producing 20 as the expression value.

Note: Unless explicitly stated otherwise by brackets, when two operators with the same precedence exist in an expression, they are evaluated from left to right.

Additionally, division (/) and the remainder operator (%) have the same precedence in C. For example, in the expression:

44 / 4 * 2,

44 / 4 is evaluated first, followed by 11 * 2, giving 22 as a result of the expression.

Similarly, in the case of the expression:

12 - 7 + 3

12 – 7 is evaluated first, followed by 5 + 3, giving 8.

However, 24 / (4 * 2) is evaluated as:

24/8

and 12 - (7 + 3) is evaluated as:

12-10

Input-output in C

Input refers to the task of providing data to a program. A file or the command line can be used to provide input. The built-in functions in C programming make it possible to read input and pass it along to the program as required.

The term output refers to the presentation of data in a file, on an output device like a monitor screen, or on a printer. A collection of built-in functions is available in C programming, which we can employ to output data as well as save it to text or binary files.

Output formatting

Controlling how data appears when printed to the console or other output streams is known as output formatting. In C, output formatting is frequently done with the printf function. It enables us to format and present many forms of data, such as strings, integers, and more, in a predetermined way. Utilizing format specifiers—special codes that specify how the data should be displayed—formatting is accomplished.

printf ()

For formatted output in C, we use the printf () function. It is a component of the standard C library (stdio.h), which stands for print format. It is a powerful function for generating well-formatted and attractively designed output since it enables us to output text and values with particular formatting.

The printf() function’s general syntax is as follows:

printf ("format_string", argument_list);

format_string is a string containing the format specifiers and any desired text.

argument_list includes the values that will replace the corresponding format specifiers in the format string.

Here are some common format specifiers and their usage:

`%d` or `%i`: Format as signed decimal integer.

`%f`: Format as floating point number.

`%c`: Format as character.

`%s`: Format as string.

`%x` or `%X`: Format as hexadecimal (lowercase/uppercase).

Input:

printf ("%d%d\n", a, b);

Output:

2083

The numbers are written together in such a way we cannot differentiate between the values of a and b. This is so because the specification %d%d tells the compiler to print the numbers next to each other, without any spaces or lines and so on. If we want them separated by one space, say, we must put a space between %d and %d, like this:

printf ("%d %d\n", a, b);

20 83

We can print the two numbers on different lines or have them on the same line but separated by a set of white spaces (Tab).

Control characters

Control characters in C are unique characters that, when written or shown, carry out certain functions. In the context of a program’s output, these characters have unique meanings and functions, but no apparent representation like conventional characters do. Escape sequences are frequently used to represent control characters. These sequences begin with a backslash (‘’), then a particular character or code.

Some commonly used control characters in C:

`\n` (Newline character): It moves the cursor to the beginning of the next line.

Example:

printf ("Hello\nworld!");

Output:

Hello

world!

`\t` (Tab character): It moves the cursor to the next tab (set of white spaces) and stops on the same line.

Example:

printf ("Hello\tworld!");

Output:

Hello world!

`\b`(Backspace character): It moves the cursor back by one position.

Example:

printf ("Hello\bworld!");

Output:

Hellworld!

`\r` (Carriage return character): It moves the cursor to the beginning of the current line.

Example:

printf ("Programming\rworld!");

Output:

world!mming

`\a` (Alert/Bell character): It produces an audible or visible alert, such as a beep or flashing screen.

Example:

printf ("Listen to the beep\a");

Output:

Listen to the beep (followed by a beep sound)

`\\` (Backslash): Since one backslash is associated with Control characters in C, we have to use a double backslash ‘\\’ to represent a literal backslash character.

Example:

printf ("New line character is represented by \\n");

Output:

New line character is represented by \n

`\’` (Single quote, apostrophe): It represents a literal single quote character.

Example:

printf ("God\'s Children");

Output:

God's Children

`\”` (Double quote): It represents a literal double quote character.

Example:

printf ("\"GodsChildren\"");

Output:

"GodsChildren"

`\0` (Null character): Represents the Null Termination character in strings.

Example:

printf ("Null character: \0\n");

Output:

Null character:

Read data supplied by a user

C programming allows us to declare a variable and initialize it with a value, like:

int main () {

int a = 14;

}

It would be convenient if we could write programs in a way that allows a user to input data/ value at run-time, the program which number to initialize a with when we execute the program (at runtime). In this way, the number would not be tied to the variable from the compile time, and the program would be more flexible. This can be done by getting the program to prompt us for a number. This is where the scanf () function can be used.

The basic input and output functions used in C are:

scanf()

printf()

scanf () function

Scanf ([format specifiers], [variable names preceded with & operator])

Scanf () takes two arguments; the first string is called the format specifier.

Here are some common format specifiers and their usage:

'%d' or '%i': Format as signed decimal integer.

'%f': Format as a floating point number.

'%c': Format as a character.

'%s': Format as a string.

In the above example, the string consists of the format specification %d only. It specifies the type of data to be read. Here, %d is used to indicate that an integer value is to be read.

The & (ampersand) operator specifies the address of the variable to which it is used as a prefix.

Like &a, means address of a, where a is supposedly a variable.

scanf ("%d", &a),

The above statement would scan a value from the input device (like a keyboard) and store it at the address of a.

It is customary to use a printf (which prints a message or value on an output device) before scanf () statement for the sake of presentation. However, it is not mandatory.

#include <stdio.h>

int main () {

int a, b;

printf ("Input 1st number\n");

scanf ("%d", &a);

printf ("Input 2nd number\n");

scanf ("%d", &b);

printf ("%d + %d = %d\n", a, b, a + b);

}

The program shall print the exact output even without the first two printf () statements, but it will not look elegant as the associated prompts for the two numbers would be missing.

Note: When a user presses the “Enter” or “Return key” on keyboard, scanf() reads number and stores it in a variable. The next printf statement then prompts ‘Enter second number’ and the process is repeated.

Since a is an integer variable, scanf expects the input data to be an integer or a value that can be converted into an integer, like a float value 3.14 would be reduced to a non-fractional number, that is, 3. If it is a character, the program will give an error message. If the data is valid, the number will be stored in the variable a. The statement scanf ("%d", &b); works in a similar manner.

We can use scanf to read more than one value simultaneously. For example, let us say we want to read two integer values for variables a and b. To do so, we would need to write %d two times in the format specification thus:

scanf ("%d %d", &a, &b);

When this statement is executed, it looks for two integers. The first one is stored in a; the second one is stored in b.

When entering the data, the numbers must be separated by at least one white space, like:

10 20

We can also use the enter key to separate the input numbers like:

10

20

Read data into a float or double variable

To read a floating-point number into a float variable, we can use:

scanf ("%f", &x);

%f is used when we must read a value into a float variable. When executed, scanf expects to find a valid floating-point constant in the data. To read a floating-point number into a double variable, y, we can use:

Scanf ("%lf", &y);

The specification %lf is used to read a value into a double variable. Data is entered the same way for float and double variables.

Note: We cannot use %f for reading data into a double variable. (However, we can use %f for printing the value of a double variable). Since the value read will be stored in 32 bits rather than 64 bits (used for double). Furthermore, an integer is acceptable when entering data for a float/double variable.

We can read values for int, double, or float variables in the same scanf statement as shown in the following statement:

scanf ("%d %lf %f", &a, &b, &c);

//used to input an integer, a double and a float value

getchar (): This C library function helps input a single character from a user.

#include <stdio.h>

int main () {

char ch;

printf ("Input a character: ");

ch = getchar ();

printf ("You entered: ");

putchar (ch);

return 0;

}

In the above program, the getchar () function is used to input one character from the console. The character is then stored in the variable ch. The putchar () function is used to print the character to the console.

When we execute this program, it will prompt us to input a character. Once we input a character and press the enter key, the program will print the character you entered to the console.

Note: getchar () reads only one character at a time, so if you enter more than one character, the program will only read the first character and ignore the rest of the characters.

getch () and getche () are two input functions in the C programming language that are used to read a character from the console without requiring the user to press the enter key.

getch () function is used to read a character from the console without displaying it on the screen. It does not require the user to press the Enter key. The function does not take any arguments and returns the ASCII value of the read character:

include <stdio.h>

#include <conio.h>

int main () {

char ch;

printf ("Press any key to continue\n");

ch = getch ();

printf ("You pressed the key '%c'", ch);

return 0;

}

In the above example, getch () is used to read a character from the console without displaying it on the screen. The function printf () is then used to display the read character.

getche () function is similar to getch () function, except that it displays the read character on the screen. The function does not take any arguments and returns the ASCII value of the read character.

#include <stdio.h>

#include <conio.h>

int main () {

char ch;

printf ("Press any key");

ch = getche ();

printf ("\nYou pressed the key '%c'", ch);

return 0;

}

Here, using getche (), we read a character from the console, and it was echoed (that is why we added e to getch function) on the screen.

Note that the conio.h header file is required to use both getch () and getche () functions in C programming language.

putchar ()

This function prints a single character on the screen:

#include <stdio.h>

int main () {

char ch = 'A';

putchar (ch);

return 0;

}

Using the putchar () function, character ch is printed to the console.

fgtes () and fputs ()

The C programming language has two input or output functions called fgets() and fputs() that are used to read and write strings to and from files, respectively.

A line of text can be read from a file using the fgets() method. It requires three arguments: the character array containing the read string as the first argument, the maximum number of characters to be read as the second argument, and the file pointer pointing to the file to be read as the third argument. A pointer to the first character of the read string is returned by the function.

fputs () function is used to write a line of text to a file. It takes two arguments: the first argument is the string to be written, and the second argument is the file pointer that points to the file to be written. The function returns a non-negative integer if the operation is successful and EOF if there is an error.

fscanf (): This function is used to read data from a file.

fprintf (): This function is used to write data to a file.

Print an integer using a field width

We print an integer value by including it in a printf command, and C prints the value using as many columns as necessary. For instance, 123 is printed using three print columns because it has three digits. The number -12345 is displayed using six print columns, 5 for the five digits and one for the minus symbol. While most applications can be satisfied with this, there are times when telling C how many print columns to use can be helpful. To display the value of n in 3 print columns, for example, we would specify a field width of 3 as follows:

printf ("%3d", n);

Instead of the specification %d, we now use %3d. The value of n is printed in a field width of 3.

The field’s width is placed between % and d. Suppose n is 12; there are 2 digits to print, so 2 print columns are needed. Since the field width is 3, the number 12 is printed with 1 space before it as ~12 (~ = white space).

Here, 12 was printed right justified (placed towards the right in the field and spaces added in front of it to make up the field width) with 1 leading space or padded on the left with 1 space.

For a number positioned towards the left and spaces added after it to compensate for the field width, the number said to be left justified. Hence, 271~~ is left justified in a field width of 5.

The minus (-) sign is used to specify left justification; %-xd will print a value left justified in a field width of x. For example:

n = 25;

printf ("n = %-5d", n);

would print:

25~~~

For a negative value, say n = -7, and the field width is 5. We require two print columns (one for ‘–‘ sign and 1 for 7); the field width is 5, so it would be printed with 3 leading spaces;

~~~-7.

Note: C ignores the field width if it is too small and just writes the value in as many columns as necessary. For example, if the field width is 2 and the value to be printed is 345, C just displays the value as it is, ignoring the (deficient) field width.

When we want our output (numeric) data lined up, we may take services of field width with text justification. For a declaration:

int a, b, c;

n1 =1276;

n2 = -30;

n3 = 231;

printf ("%d\n", n1);

printf ("%d\n", n2);

printf ("%d\n", n3);

It will print:

1276

-30

231

Each number is printed using just the number of columns required. Since this varies from one number to the next, they do not line up. If we want to, we could get the numbers lined up using a field width of 6, for example, the statements:

printf ("%6d\n", n1);

printf ("%6d\n", n2);

printf ("%6d\n", n3);

It will print (◊ denotes a space):

~~1276

~~~-30

~~~231

Floating point types

A floating point number is a number with a fractional part. A floating point constant can be written in the following ways:

The standard way, with an optional sign and including a decimal point; for example, -23.75, 0.51, 41.0.

Using scientific notation, with an optional sign, including a decimal point and an exponent part, 0.215 can be written as 2.15e-1, that is, 2.15 x 10-1. The exponent can be specified using either e or E.

Note: There are several ways to write the same number. For example, the following represent the same number 27.96: 27.96E00 2.796E1 2.796E+1 2.796E+01 0.2796E+02 279.6E-1.

We can declare a floating-point variable in C programming using either float or double. A float value is normally stored as a 32-bit floating-point number with up to 7 significant digits. The double value, on the other hand, is stored as a 64-bit floating-point number with about 15 significant digits.

A floating-point constant is of type double unless followed by f or F, in which case it is of type float. Thus, 23.5 is type double, but 3.7f or 3.7F is type float. Most calculations are done by means of double precision. The float type is convenient if you need to store many floating-point numbers and wish to use less storage.

To summarize details of standard floating-point types with storage sizes and value ranges and their precision:

Storage capacity of float is 4 bytes, which can store a value ranging from 1.2E-38 to 3.4E+38 with 6 decimal places.

Storage capacity of double is 8 bytes, which can store a value ranging from 2.3E-308 to 1.7E+308 with 15 decimal places and

Storage capacity of long double is 10 bytes, which can store a value ranging from 3.4E-4932 to 1.1E+493238 with 19 decimal places.

To use details about the binary representation of real numbers in our programs, we must include the header file float.h, which defines macros that allow us to traverse various facets of float datatype.

If checking storage capacity, minimum and maximum positive values, and precision of float data type, we may use the following functions or macros:

sizeof (float);

FLT_MIN

FLT_MAX

FLT_DIG

Print double and float variables

If we wish to print the value of a float or double variable, we can use %f. For example, consider the following:

double x = 23.431657;

printf ("%f \n", x);

The value of x will be printed to a predefined number of decimal places, usually six; it varies from one compiler to the other. In this case, the value printed will be 23.431567. However, if x were assigned 23.6543215, the value printed would be rounded to six decimal places, 23.654322.

If we wish to tell the compiler how many decimal places to print or how many columns to use. For, if we want to print x in the above example to 2 decimal places in a field width of 5, we can use:

printf ("%5.2f \n", x);

In-between % and f, we write 5.2, the field width, followed by a point (.), the number of decimal places. The value is rounded to the stated number of decimal places and then printed. Here, the value printed will be 23.43, which occupies exactly 5 print columns. If the field width provided is bigger, the number will be padded on the left with the requisite number of spaces. If the field width provided, however, is smaller, the number is printed using as many columns as necessary. For example, consider:

b = 245.75;

printf ("%6.1f \n", b);

Here, 1 alludes to rounding the number to 1 decimal place, so the new value will be 245.8, which requires 5 columns for printing.

6 suggests printing 245.8 in 6 columns; since only 5 columns are needed for printing the number, one space is added at the beginning to make up 6 columns, so the number is printed as ~245.8 (~ = space).

Floating-point expressions

Floating-point expressions can participate in addition, subtraction, multiplication, and division operations like integer data.

For operands a and b, Table 2.6 shows the type of calculation performed:

a

b

Type of calculation

float

float

float

float

double

double

double

float

double

double

double

double

Table 2.6: Floating point expression calculation

Thus, float is performed only if both operands are float; otherwise, double is performed.

Assigning double/float to int

Consider the value 987 is printed. When we assign a floating-point value to an int, the fractional part, if any, is dropped (not rounded), and the resulting integer value is assigned.

double x = 87.321;

int a = x;

printf ("%d \n", a);

Output:

87

Char data type

In C, the char data type represents a single character, such as a letter, digit, or symbol. It is an integer type with a size of 1 byte (8 bits) on most systems and can hold integer values in the range of -128 to 127 or unsigned values in the range of 0 to 255, depending on whether it is signed or unsigned.

To declare a variable of type char, you can use the following syntax:

char variable_name;

For example, to declare a variable named z of type char, we would use this code:

char z;

We can also initialize a char variable at the time of declaration, just like any other variable in C:

char ch = 'A';

This code declares a variable named ch of type char and initializes it with the character 'A'.

In addition to single characters, char variables can also be used to represent strings (array) of characters, For example:

char nm[] = "Zain"; //discussed in detail in chapter 6

This code declares an array named nm of type char and initializes it with the string "Zain". String values are terminated by the null terminator character `\0’. The array has a size of 5 (including the terminal character), and each element holds a single character.

The char data type is often used in C for text processing and input or output operations, such as reading and writing characters from the console or a file. However, we can also use characters in arithmetic operations, where the integer value of the character is needed instead of the character itself. To print the ASCII value of a character, we simply print the char with the format specifier of integer (%d), instead of the %c format specifier of char.

For example:

#include <stdio.h>

int main () {

char x = 'A';

printf ("The ASCII value of '%c' is %d\n", x, x);

}

It would produce an output of:

The ASCII value of 'A' is 65

The preceding code declares a char variable named x and initializes it with the character 'A'. It then prints the character and its ASCII value to the x variable to the console using the printf function.

void type

In C, the void type is a special type representing a value’s absence. It is used in several contexts, such as function return types, function arguments, and pointer declarations.

Some common uses of the void type in C are:

Function return type: If a function does not return a value, its return type can be specified as void. For example:

void msg() {

printf ("Hello, world!");

}

The function msg() does not return a value (it has a void return type). The function simply prints the string "Hello, world!" to the output.

Function arguments: If a function does not take any arguments, its parameter list can be specified as void. For example:

void msg (void) {

printf ("Hello, world!");

}

This function takes no arguments (its parameter list is specified as void).

Pointer declarations: A pointer to void (a void pointer) is a special pointer that can point to any data type. This is useful when a function or data structure needs to accept or return data of different types. For example:

void *ptr;

int x = 42;

ptr = &x; // ptr now points to x

We declare a void pointer named ptr and initialize it to NULL. It then declares an int variable named x and assigns it 42. Finally, it sets ptr to point to x using the operator’s address.

Note: ptr is a void pointer, it can be used to point to any data type, not just int.

The void type often indicates that a function or pointer can accept or return data of any type. However, the void does not represent a specific data type, so it cannot be used to declare variables or define data structures.

Lvalues and Rvalues in C

In C, an expression can be classified as either an Lvalue or an Rvalue. Understanding the difference between L-values and R-values is important when working with C’s assignment and operator expressions.

An Lvalue refers to an expression that refers to a memory location, such as a variable or an array element. An Lvalue can appear on the left-hand side of an assignment expression because it can be assigned as a value. For example:

int x;

x = 42; // x is a Lvalue because it refers to a memory location

In the preceding code, x is an Lvalue because it refers to a memory location that can be assigned a value.

An Rvalue refers to an expression that represents a value but does not necessarily refer to a memory location. An Rvalue can appear on the right-hand side of an assignment expression because its value can be assigned to an Lvalue. For example:

int y;

y = 10 + 20; // 10 + 20

10 + 20 is an Rvalue because it represents the value 30 but does not refer to a specific memory location.

Not all expressions can be classified as either an Lvalue or an Rvalue. For example, the ternary operator `?:` can return either an Lvalue or an Rvalue, depending on the context in which it is used. Similarly, function calls can also return either an Lvalue or an Rvalue, depending on the function’s return type.

Here are some other examples of Lvalues and Rvalues in C:

int x = 12; // x is an Lvalue, because it refers to a memory location

int y = x; // x is an Rvalue because its value is assigned to y

int z = x + y; // x + y is an Rvalue, because it represents a value

int *p = &x; // & a is an Rvalue because it represents the address. Hence, it is a value to be assigned to p.

In general, understanding the difference between Lvalues and Rvalues for writing correct and efficient C code is important, as it affects how expressions and variables are manipulated or stored in memory.

Literals

Literals are constants used to represent static things that cannot be specified as variables. These constants take up memory without having a specific reference, like variables. The main purpose of C literals is to supplement the code and deal with situations when a variable may not be specified. During its operation, a program cannot change these constants. The fundamental data types, including integer, floating-point, character, and string literals, can be used as constants. Additionally, there are enumeration constants. Except for the fact that their values cannot be changed after their definition, the constants are handled just like the normal variables.

Integer literals

In C, an integer literal is a sequence of digits representing an integer value. Integer literals can be written in decimal, octal, or hexadecimal notation, and they can have different types depending on their format and size:

Decimal notation: `12`, `132`, `0`, `-10`

Octal notation: `056`, `072`, `0100`

Hexadecimal notation: `0x2a`, `0x7b`, `0xff`

In C, the default type of an integer literal is int, which is typically a 32-bit signed integer on most platforms. However, you can specify a different type for an integer literal by appending a suffix to the literal, as shown in Figure 2.14:

Figure 2.14: Suffixes used with literals

For example, 32u is an unsigned integer literal with a value of 32, and 0xffUL is an unsigned long integer literal with a value of 255.

Note that integer literals can overflow if their value exceeds the range of their type. For example, on a platform where int is a 32-bit signed integer, the literal 2147483648 will overflow and have an undefined value. To avoid overflow, use a larger type (such as long or long long) or check for overflow in your code.

In addition, you can use the underscore (_) character as a separator to make large integer literals more readable. For example, 1_000_000 is equivalent to the integer literal 1000000.

Floating-point literals

In C, a floating-point literal is a numeric value representing a floating-point number. It is a number with a decimal point or an exponent that indicates the number’s magnitude.

C provides two types of floating-point literals: Float and Double. The float type represents a single-precision floating-point number, and the double type represents a double-precision floating-point number.

For example:

float f = 3.14f; // a float literal with the value 3.14

double d = 6.022e23; // double literal with the value 6.022 x 10^23

float g = 1.0 / 3.0; // float literal pewith value 0.33333334 (approx).

In the first example, the literal 3.14f is a float literal because it is suffixed with the letter f. If the suffix is omitted, the literal is default interpreted as a double literal.

In the second example, the literal 6.022e23 represents the value 6.022 x 10^23 in scientific notation. The letter e is used to indicate the exponent.

In the third example, the literal 1.0 / 3.0 is a floating-point expression evaluated at runtime. Since floating-point numbers cannot always be represented exactly in binary, the result of this expression is an approximate value.

It is important to be aware of the limitations and quirks of floating-point arithmetic when working with floating-point literals in C. For example, the precision and range of floating-point numbers are limited by the hardware and implementation of the C compiler. In addition, floating-point arithmetic can be affected by rounding errors and other issues that can lead to unexpected results.

Character constants

In C, a char literal is a sequence of characters enclosed in single quotes, such as 'A' or '0'. Char literals represent individual characters and are often used to initialize variables of type char or to compare characters in conditional statements.

For example:

char ch = 'A'; // initialize a char variable with the character 'A'

char chr = '0'; // initialize a char variable with the character '0'

Char literals can also represent special characters, such as newline (`’\n’`) or tab (`’\t’`), which are used to format output or to manipulate text. Here are some examples of char literals that represent the special characters:

char c = '\n'; // initialize a char variable with a newline character

char c = '\t' // initialize a char variable with a tab character

In C, char literals are represented as integer values in memory, with each character corresponding to a specific ASCII code or Unicode code point. For example, the character A corresponds to the integer value 65 in ASCII code, and the character 0 corresponds to the integer value 48.

It is worth noting that C also supports string literals, which are sequences of characters enclosed in double quotes, such as "Hello, world!". String literals are treated as arrays of characters in C and are often used to initialize arrays of type char.

String literals

In C, a string literal is a sequence of characters enclosed in double quotes, such as “Hello, world!”. String literals are used to represent string values in C programs.

String literals are considered constant expressions, which means that they are stored in read-only memory and cannot be modified at runtime. For example, the following code will result in a compile-time error:

char *s = "Zaimi";

s [0] = 'h'; // error: assignment to read-only location

In this code, s is a pointer to the string literal "Zaimi". When we attempt to modify the first character of s, we get a compile-time error because the string literal is stored in read-only memory.

In addition to alphanumeric characters, string literals can also contain escape sequences, which are special characters that are represented by a backslash followed by another character.

For example, the following escape sequences are commonly used in C string literals:

\n: newline

\t: tab

\": double quote

\: single quote

\\: backslash

String literals can be used in several ways in C programs. For example, they can be assigned to char arrays, used as function arguments, or printed to the console using the printf function. Here are some examples:

char str1[] = "Hello Zaimi!"; // assigns string literal to char array

char *s= "Hello, Zaimi!"; // assigns string literal to char pointer

printf ("%s\n", "Hello, Zaimi!"); // prints string literal using printf

In general, string literals are a convenient way to represent string values in C programs. However, it is important to remember that they are stored in read-only memory and cannot be modified at runtime. If you need to modify a string at runtime, use a char array or dynamically allocated memory instead.

Defining constants

In C, a constant is a value that cannot be modified by the program once it has been defined. Defining constants can make programs more readable and maintainable by making the intent of the code clear and by preventing inadvertent changes to values that should not be modified.

We can define constants in C in various ways:

Using the #define preprocessor directive: This method defines a macro that replaces any occurrence of a given identifier with a specified value. For example, the following code defines a constant named PI with a value of 3.14159:

#define PI 3.14159

After this definition, any occurrence of the identifier PI in the program will be replaced with the value 3.14159 by the preprocessor.

Using the const keyword: This method defines a variable with a fixed value that cannot be changed after initialization. For example, the following code defines a constant named MAX with a value of 100:

const int MAX = 100;

After this definition, the variable MAX cannot be modified by the program.

Using an enumeration: This method defines a set of named constants that are assigned to the integer values. For example, the following code defines an enumeration named Color with three constant values:

enum Color {A = 1, B = 2, C = 3};

After this definition, the names A, B, and C can be used as constants in the program.

Note: Defining constants using the `#define` directive can lead to some potential problems, such as unexpected substitution of identifiers and a lack of type checking. Therefore, using `const` variables or enumerations is generally considered to be a safer and more reliable way to define constants in C. It is a good programming practice to define constants in capitals.

Differences between constants and literals

A value that cannot be changed while a program is running is referred to as a constant. Different sorts of constants are possible, including integer, floating-point, character, and string constants. In C, constants are often used to express known fixed values at compile time.

As an illustration, the following code defines a few integer and floating-point constants:

Const float PI = 3.14159, const int MAX = 100

MAX and PI are constants in this code that cannot be modified while the program is running.

In source code, a value is represented using a literal notation. Different forms of literals are possible, including string literals, character literals, floating-point literals, and integer literals. For the purpose of representing fixed values in source code, C uses literals.

The following code, for instance, makes use of some integer and floating-point literals:

int x = 42;

float y = 3.14;

The literals 42 and 3.14 in this code represent integer and floating-point values, respectively.

Though literals can initialize constants, not all literals are constants. For instance, the following code is invalid:

int *p = &42 // Cannot accept the address of an integer literal

&42 is not an acceptable constant expression in this code since it is not a Lvalue.

Storage class

In C programming, storage classes are used to specify the scope, visibility, and lifetime of variables or functions. Four different storage classes are available in C, which are:

Auto

Register

Static

Extern

Auto

Auto keyword specifies that the variable has a local scope and automatic storage duration. Auto is the default storage class for all local variables. A variable is created when the control enters the block, which is defined and destroyed when the control exits from the block.

void func () { // Both declarations below define variable as Auto

auto int x = 10;

int x=10;

}

Register

To define local variables that should be stored in a CPU register rather than in memory, they should be declared with a register storage class. The register keyword is only a hint to the compiler and may or may not be followed. For example:

void func () {

register int x = 10;

}

Extern

Extern storage class is used to declare variables or functions that may be defined in another file. The extern declaration enables a variable or function to be used in multiple files without redefining it in each file. For example:

// file1.c

extern int x;

// file2.c

int x = 10;

Static

Throughout the course of the program’s execution, persistent variables with lifetimes are defined using this storage class. When a variable is declared static, its value is kept between function calls and must be initialized once. A static variable also has a file scope, meaning that it can only be seen in the file in which it is defined. For example:

void func () {

static int x = 10;

}

To sum things up, storage classes in C provide a way to control the scope, visibility, and lifetime of variables and functions in a program. Understanding the different storage classes is important for writing efficient and modular C code.

In C programming, when static is used on a class data member, it causes only one copy of that member to be shared by all the objects:

#include <stdio.h>

void func (void);

static int count = 5; /* global variable */

int main () {

printf ("values of I, 5 times using ordinary declaration\"");

while (count--) {

func ();

}

count=5;

printf ""values of i, 5 times using static declaration\"");

while (count--) {

func_static ();

}

}

/* function definition */

void func (void) {

int i = 0; /* local static variable */

i++;

printf ""i = %d\"", i);

}

void func_static (void) {

static int i = 0; // local static variable

i++;

printf ""i = %d\"", i);

}

The output for the preceding code is:

Values of I, 5 times using ordinary declaration:

i = 1

i = 1

i = 1

i = 1

i = 1

Values of I, 5 times using static declaration:

i = 1

i = 2

i = 3

i = 4

i = 5

Extern storage class

A reference to the global variable that is accessible from all program files is provided using the extern storage class. The variable cannot be initialized when using the extern keyword, but it does direct the variable name to a previously established storage location.

Extern is used in another file to provide the reference of a declared variable or function when you have many files and specify a global variable or function that will also be utilized in other files. Just to be clear: When declaring a global variable or function in another file, extern is utilized.

When two or more files share the same global variables or functions, as detailed below, the extern modifier is most frequently used.

First File: var.c

int x=65;

Second File: main.c

#include <stdio.h>

#include "var.c"

// To include/ link file with external variable declaration.

extern int x; // extern variable

int main () {

// print the variable from another file

printf ("Extern value = %d", x);

return 0;

}

Here, the variable has been declared in the first file var.c. To use the variable x in the second file, the keyword extern is being used in the second file. In the second file, we must give reference to the file where x has been declared, which is done by the statement:

#include "var.c"

Now compile and execute the second file, which should produce the output:

Extern value = 32

Writing basic programs in C

Writing basic programs in C involves writing code that performs a specific task or solves a particular problem. Basic programs typically involve defining variables, using operators and control statements to manipulate those variables, and using functions to perform specific operations.

Sequential logic

Sequential logic refers to the behavior of a program in which instructions are executed one after the other in a sequential order. In C, sequential logic is the default mode of execution of statements, in which statements are executed in the order in which they appear in the program.

Unless control statements like if, while, for, or switch are used to modify the regular flow of control, statements in C are executed sequentially, one after the other. The order in which the statements are written in the source code determines the sequence of execution.

Here we present some basic C programs based on sequential logic:

Printing a greetings message to the console:

#include <stdio.h>

int main () {

printf ("Hello, world");

return (0);

}

Program to compute the sum of two integers:

#include <stdio.h>

int main () {

int n1, n2, sum;

printf ("Enter 2 integers\n");

scanf ("%d %d", &n1, &n2);

sum = a + b;

printf ("sum of the given numbers is %d\n", sum);

return (0);

}

Program to compute the average of three floating-point numbers:

#include <stdio.h>

int main () {

float n1, n2, n3, avg;

printf ("Input 3 numbers\n");

scanf ("%f%f%f", &n1, &n2, &n3);

avg = (n1 + n2 + n3) / 3;

printf ("Average of the given numbers is %f", avg);

return (0);

}

Program to convert temperature from Fahrenheit to Celsius:

#include <stdio.h>

int main () {

float fah = 68;

float cel = (fah - 32) * 5 / 9;

printf ("%.2f Fahrenheit is %.2f Celsius\n", fah, cel);

return (0);

}

Program to find the area of a rectangle:

#include <stdio.h>

int main () {

float l = 5.2;

float w = 3.7;

float area = l * w;

printf ("Area of a rectangle is %.2f\n", area);

return (0);

}

Program to find the average of three integers:

#include <stdio.h>

int main () {

int n1, n2, n3, avg;

printf ("Input 3 integers\n");

scanf ("%d%d%d", &n1, &n2, &n3);

printf ("Average = %d", (n1+n2+n3)/3);

// assuming result to be Integer

return (0);

}

Program to swap two integers:

#include<stdio.h>

int main () {

int a, b, temp;

printf ("Input 2 numbers\n");

scanf ("%d%d", &a, &b);

// value of a is assigned to temp

temp=a;

// value of b is assigned to a

a = b;

// value of temp (initial value of first) is assigned to b

b = temp;

printf ("\nAfter swapping, first number = %.2lf\n", a);

printf ("After swapping, second number = %.2lf", b);

return (0);

}

Program to add two floating point numbers:

#include <stdio.h>

int main () {

float n1, n2;

printf ("Input 2 float numbers\n");

scanf ("%f %f", &n1, &n2);

printf ("Sum = %f", n1 + n2);

return (0);

}

Program to find the product of two floating point numbers:

#include <stdio.h>

int main () {

double a, b, product;

printf ("Input 2 two numbers\n");

scanf ("%lf %lf", &a, &b);

product = a * b;

printf ("Product = %.3lf", product);

// %.2lf displays number up to 3 decimal points

return (0);

}

Program to find the area of a circle:

#include <stdio.h>

#include<conio.h>

int main () {

float radius, ar;

printf ("Input Radius of Circle\n");

scanf ("%f", &radius);

ar = 3.14 * radius * radius;

printf ("The area of the given circle is %f", ar);

return (0);

}

In each of these programs, the code is executed sequentially, line by line. The output of each program is displayed in the console. These programs demonstrate the basic syntax and structure of C programs, as well as the use of variables, operators, and control structures such as printf() and return.

Conclusion

In summary, this chapter has comprehensively covered the fundamental components of C programming. We began by introducing key facts about C, emphasizing its significance in the world of programming. Moving on, we explored the components of a C program, delving into tokens, data types, variables, and integral data types. We also discussed literals, storage classes, operators, and input-output operations.

To provide practical insight, we offered basic C program examples. Armed with this knowledge, you are well-equipped to embark on your programming journey, with a strong foundation to tackle more advanced topics in subsequent chapters. Keep your curiosity alive, practice diligently, and you will soon become a proficient C programmer, ready to take on more complex challenges in the field. This chapter’s content serves as a crucial stepping stone on your path to C programming mastery.

Exercise

What is a token? Give examples.

What is a reserved word? Give examples.

What rules must be followed for making up an identifier?

Give some examples of integer, floating-point, and string constants.

Which operators can be used for writing integer expressions?

How can you print the value of an int variable sum, right justified in a field width of 5?

How can we write 345.72 in 3 different ways using scientific notation?

How can we print the value of a double variable to 3 decimal places, right justified in a field width of 9?

What kind of variable would you use to store a telephone number and why?

How can you print the value of a double variable, rounded to the nearest whole number?

What happens if you try to print a number (int, float, or double) with a field width and the field width is too small? What if the field width is too big?

Name some operators that can be used for writing floating-point expressions.

Describe what happens when we attempt to assign an int value to a float variable.

Describe what happens when we attempt to assign a float value to an int variable.

Write a statement to print the following: Use \n to end a line of output.

Write a statement to increase the value of the int variable quantity by 10.

Write a statement to decrease the value of the int variable quantity by 5.

Write a statement to double the value of the int variable quantity.

Write a statement to set a to 2 times b plus 3 times c.

The double variable price holds the price of an item. Write a statement to increase the price by (a) $12.50 (b) 25%.

What will happen when the computer attempts to execute the following:

p = 7; q = 3 + p; p = p + r;

printf ("%d\n", p);

Join our book’s Discord space

Join the book’s Discord Workspace for Latest updates, Offers, Tech happenings around the world, New Release and Sessions with the Authors:

https://discord.bpbonline.com

CHAPTER 3Control Statements

Introduction

In this chapter, we will dive into the fundamental constructs that allow programmers to control the flow of their programs and make decisions based on specific conditions. We will begin by exploring control statements, which provide mechanisms for altering the sequential execution of code. Next, we will delve into decision-making statements, such as if-else and switch statements, which enable us to choose different paths of execution based on the evaluation of conditions. Additionally, we will learn techniques for handling multiple cases efficiently. Furthermore, we will explore the various looping structures available in C, including the while, do-while, and for loops, which enable repetitive execution of code. By the end of this chapter, we will have a solid understanding of these important programming constructs, equipping us with the skills to control program flow and make informed decisions in our C programs. Let us begin this exciting journey into the world of control and decision-making in C programming.

Structure

The chapter covers the following topics:

Control statements

Decision-making statements

Different loops used in C

Loop control statements

Objectives

The objective of this chapter is to provide a comprehensive understanding of control statements and decision-making statements in C programming. We will explore the concept of control flow and learn how to make decisions based on conditions using if-else and switch statements. Additionally, we will explore handling multiple switch-case statements and implementing looping structures such as the while, do-while, and for loops. By the end of this chapter, readers will have a solid understanding of these fundamental programming constructs, enabling them to effectively control the flow of their C programs and make decisions based on various conditions.

Control statements

Control statements govern a program’s flow, that is, the order in which instructions in a program may get executed. The presence of control statements in our program enables us to make decisions, to do tasks recurrently, or to jump from one section of code to another. The flow of a program is generally sequential, where instructions are executed one after the other, in sequence, like starting from the first instruction and then executing the second, third, fourth and so on.

There are instances when a programmer needs to execute a set of instructions based on certain conditions. For example, executing instructions only if a specific condition is met, such as determining if an input number is even. Similarly, there are cases where a programmer wants to execute a set of instructions continuously until a certain state is reached or a condition is fulfilled. Additionally, there may be situations where it is necessary to skip a set of statements and proceed to a different set of instructions. In such scenarios, the flow of execution may not follow a strict sequential order. For instance, instruction 5 might be followed by instruction 9, or instructions 10, 11, and 12 could be executed multiple times. Furthermore, it may be necessary to skip instructions 21, 22, and 23 and proceed directly to the subsequent instructions.

These scenarios are enabled by the presence of control statements in our program. Let us try to have an idea of them, then try our hand at some associated examples. Refer to the Figure 3.1:

Figure 3.1: Condition based logic

There are four basic control statements in C:

Conditional (decision-making) statements

Switch statements

Loop statements

Jump statements (Go To)

Decision making statements

For decision-making, programmers must define constructs that will be assessed or tested by the program, along with statements to be performed if the condition is true and, optionally, some statements that will be run if the condition is false.

The following is the general form of a typical decision-making structure found in most of the programming languages:

C programming language assumes any non-zero and non-null values as true; if it is either zero or null, it is assumed as false.

It also provides the following types of decision-making statements:

if statement

The if statement executes a block of code if a condition is true. The if construct consists of a Boolean expression followed by a block of statements to be executed if the condition evaluates to TRUE.

Syntax:

if (condition) {

Set of instructions to be executed

}

For example:

a=10; b=20;

if (a>b) {

printf("a is greater");

}

An if statement controls the execution of instructions based on the results of a Boolean expression. if statement starts with an if clause, followed by a condition and a BLOCK of instructions. An if clause may also be followed by (an optional) else clause (or one or more else-if's).

if...else statement

As stated above, an if statement may be followed by an optional else statement, which executes when the Boolean expression is evaluated as FALSE:

Syntax:

if (condition) {

Set of instructions to be executed if the condition is true

}

else {

Set of instructions to be executed if the condition is false

}

For example:

//Greatest of two numbers

a=10; b=20;

if (a>b)

printf("a is greater");

else

printf("b is greater");

Note: Instructions associated with if or else are enclosed within { and }, any statement outside { and } does not belong to the corresponding if or else.

For example:

if (n<0) {

printf("Given number is negative");

}

printf("hi"); // ???????? see afterwards

else {

printf("a is positive");

}

printf("bye");

Here the statements printf("hi"); do not fall under the domain of if and printf("bye"); does not fall under the domain of else, as they lie outside the block { } of if and body of else respectively. However, mentioning the block of if (or else) is not always necessary.

In situations where only one instruction is within the scope of an if or else statement, the use of curly braces { } is not required for that single statement. This is because, by default, C recognizes the subsequent single statement as belonging to the if/else block. However, when dealing with multiple statements, it is necessary to enclose them within a { } block to define the scope of the if or else statement:

printf("hi"); //????????

The above example is invalid as it lies in between if and else; in other words, it belongs to neither if nor else.

Note: The else-clause of an if-clause should start as soon as the block of the latter ends. Hence, the else-clause belongs to the if-clause, which is immediately above it.

Misplaced else

A misplaced else is a usual error in programming, in programming languages that use if-else constructs, like C, the error is generated when the else statement is not paired with the correct if statement (Hence misplaced), resulting in unexpected behavior in program results. To correct a misplaced else, we need to review our code cautiously and make sure the else corresponds to the right if.

In the example:

if (n<0) {

printf("Given number is negative");

}

printf("hi"); // ???????? see afterwards

else {

printf("a is positive");

}

printf("bye");

The highlighted statement is not a part of the block and by syntax rules, ‘}’ of if should have been followed by the else block, hence the else block here is a misplaced one, as it is not present where it ought to be.

Program to check whether a number is perfectly divisible by another number (say 3):

int main () {

int num;

clrscr ();

printf("Input a No.\n");

scanf ("%d", &num);

if (num%3 ==0) {

printf("The given number is perfectly divisible by 3");

}

else {

printf("The given number is not perfectly divisible by 3");

}

return (0);

}

The if-else-if ladder

In C programming, the if-else-if ladder is a means to test several conditions in a series of if and else-if statements. It is a valuable construct when we have several conditions to check and want to execute diverse blocks of code based on the result of those tests.

Syntax:

if (condition1) {

// statements to execute if condition1 is true

} else if (condition2) {

// statements to execute if condition1 is false and condition2 is true

} else if (condition3) {

// statements to execute if condition1 and condition2 are false and condition3 is true

} else {

// statements to execute if none of the conditions are true

}

The first/outermost if statement tests condition1. If condition1 is true, the code inside that if statement will be executed, and the rest of the else-if ladder will be skipped. If condition1 is false, the next else-if statement will be tested. If condition2 is true, the code inside that else-if statement will be executed, and the rest of the else-if ladder will be skipped. This procedure lasts till either one of the conditions is true or till the final else statement is reached.

Note that the final else statement is optional, and we can have as many else-if statements as needed.

Example:

int s = 81;

if (s >= 80) {

printf("Grade A");

} else if (s >= 60) {

printf("Grade B");

} else if (s >= 40) {

printf("Grade C");

} else {

printf("Grade D");

}

In this example, the program will output Grade A because the score s is greater than or equal to 80 but less than 90. If the score was greater than or equal to 90, the program would output Grade A, and so on.

The if-else-if ladder is a useful construct for testing multiple conditions and executing different blocks of code based on the outcome of the tests. When using the if-else-if construct, we must use it cautiously, as it can also make our code harder to read and maintain:

Program to check sign of a number:

#include <stdio.h>

int main () {

int n;

printf("Input a number\n");

scanf ("%d", &n);

if (n > 0)

{

printf("%d is positive\n", n);

}

else if (n < 0)

{

printf("%d is negative\n", n);

}

else

{

printf("%d is zero\n", n);

}

return (0);

}

Program to check largest of three numbers:

#include <stdio.h>

int main () {

int n1, n2, n3;

printf("Input 3 numbers\n");

scanf ("%d %d %d", &n1, &n2, &n3);

if (n1 > n2 && n1 > n3)

{

printf("%d is the largest number\n", n1);

}

else if (n2 > n1 && n2 > n3)

{

printf("%d is the largest number\n", n2);

}

else

{

printf("%d is the largest number\n", n3);

}

return 0;

}

Program to check gender of a user:

int main () {

char c;

printf("Input gender m for male or f for female\n");

scanf ("%c", &c);

if (c == 'm')

{

printf("You are a male");

}

else if (c == 'f')

{

printf("You are a female");

}

else

{

printf("Other Gender");

}

return (0);

}

Nested if statements

We can use one if or else-if statement inside another if or else-if statement(s). Placing a conditional statement inside another conditional statement is called nesting of conditional statements. This is done where we need to check a condition inside another condition. The control reaches the inner (nested) condition only if the outer conditional statement(s) are considered true:

if (condition1) // outer-if

{

instructions to be executed if the condition1 is true

if (condition2) // inner if

{

instructions to be executed if the condition2 (and condition1) is true

} closing Bracket of inner-if

else //else of inner-if

{

instructions to execute if Condition1 is true & condition2 is false

} closing Bracket of outer-if

}

else // else of first/ outer if

{

instructions to execute if Condition1 is false. Here we can test more ifs & elses

}

For example:

Suppose we wish to check if a given number n is divisible by 3 and 7:

if (n%3 ==0)

{

// if true (Divisible by 3) control would reach inner-if

if (n%7 ==0

{

// if true (Divisible by 7) control reaches inner-if

printf("n is divisible by 3 & 7\n");

}

else

{ // This is ELSE of the above IF i-e: (not divisible by 7)

printf("n is divisible by 3 only\n");

}

}

else

{

// This is ELSE of the first IF (Not divisible by 3)

if (n%7 ==0

{ // Check divisibility of 7

printf("n is divisible by 7 only\n");

}

else

{ // This is ELSE of the above IF (not divisible by 7 and 3)

printf("n is divisible by none\n");

}

}

Note: In the first if block ‘n’ is divisible by 3 and in the corresponding else block , ‘n’ is not divisible by 3. Subsequently, we check for the divisibility of 7.

Some programming examples related to conditional statements:

Program to check if given number is a multiple of 5:

#include<stdio.h>

int main () {

int n;

clrscr ();

printf("Input a Number\n");

scanf ("%d", &n);

if (n%5 == 0) // % operator returns remainder

{

printf("Given number is a multiple of 5");

}

else

{

printf("Given number is not a multiple of 5");

}

return (0);

}

Program to check for even/odd:

#include<stdio.h>

int main () {

int n;

clrscr ();

printf("Input a Number\n");

scanf ("%d", &n);

if (n%2 == 0)

{

printf("The given number is Even");

}

else

{

printf("The given number is Odd");

}

return (0);

}

Program to compare two numbers using simple if/else:

#include<stdio.h>

int main () {

int n1, n2;

printf("Input numbers to compare\n");

scanf("%d%d", &n1,&n2);

if (n1 > n2)

{

printf("First number is greater than second number");

}

else

{

printf("Second number is greater than the first number");

}

return (0);

}

The above examples are ideal for scenarios where only two execution paths (handled by if and else) are possible. What if, in the above example, user inputs same value for n1 as well as n2, the output would be Second number is greater than the first number, which is not the expected output (which should be – The numbers are equal). This is so because we have not defined the execution path for a scenario where the user inputs the same values for both the variables n1 and n2. To handle such cases, where merely an if & else combination is not sufficient, we use elseif construct.

The syntax of elseif is:

if (condition1) {

// statements to be executed if condition1 is true

else if (condition2) {

// statements to be executed if condition2 is true

// After having a desired number of else-ifs, we may use an else clause.

Else {

// statements to be executed if all the conditions are false

}

Program to compare two numbers using elseif:

#include<stdio.h>

int main () {

int n1, n2;

printf("Input numbers to compare\n");

scanf("%d%d",&n1,&n2);

if (n1 > n2)

{

printf("First number is greater than second number");

}

else if (n2 > n1)

{

printf("Second number is greater than the first number");

}

else

{

printf("\n The given numbers are equal");

}

return (0);

}

Program to check grade obtained by a user based upon percentage (For simplicity take %age as int)

#include<stdio.h>

int main () {

int p;

printf("Enter Percentage\n");

scanf ("%d", &p);

if (p>=75)

{

printf("Distinction");

}

else if (p>=60)

{

printf("First Division");

}

else if (p>=50) {

printf("2nd Division");

}

else if (p>=40)

{

printf("3rd Division");

}

else

{

printf("Ohhh … You have failed!");

}

return (0);

}

Note: If represents the first, while else represents the last path/possibility in the programs using elseif ladder. elseif’s represent the intermediate possibilities/ paths.

Nested conditions

Conditional statements defined within the domain of other conditional statements are known as nested statements. It is fine to nest if-else statements in C programming, which enables us to use one if/else or if statement inside another if/else/if statement. The syntax for a nested condition statement is as follows:

if (condition1) {

// executes when the condition1 is true

if (condition2) {

// executes when the condition2 is true

}

}

We can nest else-if...else statements as we have nested if statements:

Greatest of three numbers:

#include <stdio.h>

int main () {

int n1, n2, n3;

printf("Input 3 Numbers\n");

scanf ("%d%d%d", &n1,&n2,&n3);

if (n1>n2) //if n1 is greater than n2 control will go to nested-if

{

if (n1>n3)

{ //if n1 is greater than n3, then n1 is greatest

printf("n1 is greatest\n");

}

else

{ // n1<n3

printf("n3 is greatest\n");

}

}

else // n2>n1

{

if (n2>n3)

{

printf("n2 is greatest\n");

}

else

{ // n2<n3

printf("n3 is greatest\n");

}

}

return (0);

}

Bonus system of a company:

#include <stdio.h>

int main () {

int age;

char g;

printf("Input gender - m or f\n");

scanf ("%c", &g);

printf("Input age\n");

scanf ("%d", &age);

if (g == 'm')

{ //if male

if (age>=50)

{ //if age>=50

printf("2000 is your bonus\n");

}

else

{ //male but age<50

printf("1500 is your bonus \n");

}

}

elseif (g == 'f') //if female

{

if (age>=50)

{ //female and age>50

printf("3000 is your bonus\n");

}

else

{ // female and age<50

printf("2500 is your bonus \n");

}

}

else

{

printf("Other gender");

}

return (0);

}

Evaluating year as a leap year or not:

#include<stdio.h>

int main () {

int y;

printf("Input year\n");

scanf ("%d", &y);

if (y % 4 == 0)

{

if (y % 100 == 0)

{

if (y % 400 == 0)

{

printf("%d is a leap year\n", y);

}

else

{

printf("%d is not a leap year\n", y);

}

}

else

{

printf("%d is a leap year\n", y);

}

}

else

{

printf("%d is not a leap year\n", y);

}

return (0);

}

}

Note: When writing conditional statements in C, the ternary operator ‘?:’ offers a convenient alternative to the ‘if-else-elsif’ logic. It enables us to pick between two values depending on a criterion and make decisions.

The ternary operator has the following syntax:

Condition? expression1: expression2

Value of expression1 will be returned if the condition is true, while value of expression2 will be returned if the condition is false.

Program to compare two numbers using if/else combination:

#include<stdio.h>

int main () {

int a=9, b=80;

char g;

if (a>b)

{

g='a';

}

else

{

g='b';

}

printf("Greatest value = %c", g);

return (0);

}

Program to compare two numbers using ternary operator:

int main () {

int a=9, b=80;

char g;

g=(a>b)? 'a':'b';

printf("Greatest value = %c", g);

return (0);

}

Here g would be assigned 'a' if the condition (a>b) evaluates to True, else 'b' would be assigned to it.

Switch statements

The switch statement in C is a control flow statement that enables us to assess an expression’s value and execute various blocks of code in accordance with that evaluation. When we have several conditions to consider, it offers an alternative to a long list of if-else expressions.

The values are the forms that the expression could take, forming the case of a switch construct. The relevant block of code will be run if the expression matches any cases. The default block’s statements will be executed if none of the case values matches the expression.

The evaluation of the phrase and subsequent comparison with the case values are how the switch construct operates. When a match is found, the case’s code is run after which the switch statement should be terminated using the break statement. Please refer to the following figure:

Figure 3.2: Selection/ switch logic

The control will pass to the next case if no break statement is found, and the following code blocks will likewise be run until a break statement is found.

The switch statement is frequently employed for evaluating a discrete set of values. It can be used, for instance, to manage menu selections, process several options, or carry out computations based on various inputs.

Syntax: The syntax for a switch statement in C programming language is as follows:

switch(expression) {

case constant-expression:

statement(s);

break;

case constant-expression:

statement(s);

break;

// We can have any number of case statements */

default:

statement(s);

}

The following rules apply to a switch statement:

The switch statement’s phrase must be of an integral type. Enumerated types and integer types like int, char, short, and long are included.

The switch statement’s case labels must all be constant expressions and cannot be a run-time-evaluable variable or expression.

Within the switch statement, each case label must be different. The use of duplicate case labels is prohibited.

The default case serves as a general-purpose case and is optional. It is executed when none of the case labels match the expression’s value. Although it can occur anywhere in the switch statement, the default case is usually put at the conclusion.

The relevant code block and a colon (:) must come after the case labels. Each case label’s corresponding code block may include numerous statements.

A break statement ends the switch statement once a case is matched and processed. The control flow will move on to the next case without the break statement, maybe executing more than one case.

Since there are no more instances to run, a break statement is not required in the final case of a switch statement.

There is a chance that different case labels will share the same code block. If the expression fits any of the shared case labels, the code block will be run in these situations.

Switch statements like other control structures like if-else clauses and loops can be nested inside of one other.

The following examples demonstrate use of the switch:

Use of switch: output a number (1-5) that the user has inputted:

#include <stdio.h>

int main () {

int n;

printf("Enter a digit from 1 -5\n");

scanf ("%d", &n);

switch (n) {

case 1:

printf("You have entered 1\n");

break;

case 2:

printf("You have entered 2\n");

break;

case 3:

printf("You have entered 2\n");

break;

case 4:

printf("You have entered 4\n");

break;

case 5:

printf("You have entered 5\n");

break;

default:

printf("Invalid No.\n");

}

return (0);

}

Switch characters

In C, we may use a char variable in a switch statement. The switch statement enables us to evaluate the value of a char variable against various cases and run various code blocks depending on which case matches.

Whenever comparing character constants, we use single quotes ('') to convey to the compiler that it is a constant and not a variable:

#include <stdio.h>

int main () {

char s;

int a, b;

printf("Press a for addition s for subtraction m for multiplication d for division x for modulo division \n");

scanf ("%c", &s);

printf("Input 2 Numbers\n");

scanf ("%d%d", &a, &b);

switch (s) {

case "a":

printf("Sum = %d", a+b);

break;

case "s":

printf("Difference = %d", a-b);

break;

case "m":

printf("Product = %d", a*b);

break;

case "d":

printf("Quotient = %d", a/b);

break;

case "x":

printf("Remainder = %d", a%b);

break;

default:

printf("Invalid character\n");

}

return (0);

}

Note: Role of break: Suppose in the above example user enters ‘a’ which would satisfy case ‘a’ and show the sum of a and b. Now, the control should not go to the subsequent cases because no other case would be satisfied, this is where BREAK comes in handy. It prohibits control to go to the other cases and pushes control outside the switch. If no case (was satisfied) was able to break then the default case gets executed, hence the default case can be used for performing a ‘task’ which should be carried out when none of the cases is true. No break is needed in the default case as the control would leave the switch after the default case.

Handling multiple cases

Suppose in the above example’s user enters M for male instead of m. Our program would show invalid gender as C is a case sensitive language, M and m would be two different entities. Similarly, what if user inputs A instead of a for addition, again the case a would not get satisfied. To handle such cases, we use multiple cases as shown in the following examples:

Handling multiple cases:

#include <stdio.h>

int main () {

char s;

int a,b;

printf("press a or + for addition s or - for subtraction m or * for multiplication d or / for division x or % for modulo division \n");

scanf ("%c", &s);

printf("Enter 2 Numbers\n");

scanf ("%d%d", &a, &b);

switch (s) {

case "a":

case "A":

case "+":

printf("Sum = %d", a+b);

break;

case "s":

case "S":

case "-":

printf("Difference = %d", a-b);

break;

case "m":

case "M":

case "*":

printf("Product = %d", a*b);

break;

case "d":

case "D":

case "/":

printf("Quotient = %d", a/b);

break;

case "x":

case "X":

case "%":

printf("Remainder = %d", a%b);

break;

default:

printf("Invalid character\n");

}

return (0);

}

Nested switch statements

One switch statement can be nested within another switch statement in C. An example of this is a layered switch, enabling systematic management of several levels of decision-making. A layered switch statement has the same syntax as a standard switch statement.

Nested switch example:

#include <stdio.h>

int main () {

int category, item;

printf("Enter category(1-3)\n");

scanf ("%d", &category);

printf("Enter item (1-3)\n");

scanf ("%d", &item);

switch (category) {

case 1:

switch (item) {

case 1:

printf("Category 1, Item 1 selected\n");

break;

case 2:

printf("Category 1, Item 2 selected\n");

break;

case 3:

printf("Category 1, Item 3 selected\n");

break;

default:

printf("Invalid item\n");

}

break;

case 2:

switch (item) {

case 1:

printf("Category 2, Item 1 selected\n");

break;

case 2:

printf("Category 2, Item 2 selected\n");

break;

case 3:

printf("Category 2, Item 3 selected\n");

break;

default:

printf("Invalid item\n");

}

break;

case 3:

switch (item) {

case 1:

printf("Category 3, Item 1 selected\n");

break;

case 2:

printf("Category 3, Item 2 selected\n");

break;

case 3:

printf("Category 3, Item 3 selected\n");

break;

default:

printf("Invalid item\n");

}

break;

default:

printf("Invalid category\n");

}

return (0);

}

In this example, the user enters a category number (1-3) and an item number (1-3). Nested switch statements are employed to identify the category and item combination chosen and produce the appropriate message. An appropriate error message is given if the entered category or item is incorrect.

We can handle complicated decision-making scenarios with several selection layers with nested switch statements. Use switch statements sparingly and think about restructuring the code if it gets excessively nested because doing so can make the code more difficult to comprehend and maintain.

Loop statements

Looping logic is a fundamental idea in programming that permits repeating execution of (a block of) code. Statements are typically executed sequentially, and first statement in a function is executed followed by the second, and so on. This is known as sequential execution. However, programming languages offer various control statements, allowing us options for more complex execution paths.

We may run a statement or set of statements repeatedly using a loop statement. In the C programming language, several looping constructs are available, including for, while, and do-while loops. These structures allow programmers to repeat a sequence of instructions until a predetermined condition is satisfied.

Constituents of a loop

In C, a loop is a programming construct that allows us to repeatedly execute a code block if a certain condition is met. The three primary constituents of a loop in C are:

Initialization: It involves initializing the loop control variable(s) before entering the loop. This step is executed only once at the beginning of the loop.

For example:

int count = 0; // Initialization

Condition: The expression that governs whether a loop should continue execution or not. A loop will execute a code block if the condition evaluates to true. The loop terminates as soon as the condition evaluates to false, and control moves to the next set of statements following the loop block.

For example:

if (count < 10) // Condition

{

// execute a set of C statements

}

Update: Update involves updating the loop control variable(s) after each iteration. The update should ideally be performed at the end of each iteration, just before re-evaluating the condition. This paves the way for a loop’s termination and ensures the loop executes a finite number of times, by updating the loop control variable. We can update loop control variable either by incrementing it or decrementing it:

count=count +1

(also written as count++ or count+=1, in C)

// when incrementing by 1

or count=count + 3;

// when incrementing by 3

It is imperative to ensure that the condition eventually evaluates to false (in collaboration with loop control variable, otherwise, the loop may become an infinite loop, which will ultimately cause the program to hang.

The for loop is frequently employed when the number of iterations is known ahead of time. Initialization, a condition, and an increment or decrement comprise this component. When the condition is met, the loop runs the specified block of code; additionally, the increment/decrement operation is carried out after each iteration.

We may encounter situations when a code block needs to be executed several times.

C programming language provides the following types of loops to handle looping requirements.

Different loops used in C

C programming language provides several types of loops to control the repetition of a certain block of code. These loops include the while loop, do-while loop, and for loop. C programming language provides several types of loops to control the repetition of a certain block of code.

While loop

In the C programming language, a while loop is a fundamental control structure that enables us to execute a block of code if certain condition is true. It offers a versatile method for carrying out iterative execution of code and is frequently utilized when the precise number of iterations is not known in advance.

While loop starts with the keyword while followed by a Boolean expression enclosed in parenthesis (), a block of one or more statements is then preferably written on the following line. The expression in the condition defines whether the loop should keep running or come to an end. Before each iteration, it is evaluated, and if it returns true, the code block inside the loop is run. When the condition changes to false, the loop is ended, and control is transferred to the statement that follows the loop. The while loop is frequently used to iterate through arrays or lists, validate input, process data, and process data. It offers a lot of versatility since We may change the loop control variable(s) inside the loop’s body, enabling intricate iteration patterns.

Because it employs a condition to decide how many times the block of code will execute, the while loop is sometimes referred to as a condition-controlled loop as, it tests a condition before starting the execution of the loop statements.

While loop components in C:

Condition: This Boolean expression decides whether the loop should keep running. If the condition evaluates to true, the loop will continue to run. The loop ends and control shifts to the statement after it if the condition evaluates to false. Each time the loop iterates, the condition is verified. It is possible that the loop will not ever run if the condition is originally false.

Loop body: The body of the loop is the section of the code that is run repeatedly while the predicate is true. The words or actions we want to repeat are in the loop body. A single statement or a group of statements might make up the loop’s body enclosed in curly braces `{}`.

Update: It entails changing the variables or conditions inside the loop’s body to guarantee that it ends eventually. The loop control variable or variables are frequently changed during the update stage. Typically, the update is included in the loop body and is carried out after each iteration, right before the condition is reevaluated:

while (condition)

{

// instructions to be executed while the condition evaluates to true

}

Some programming examples:

Print numbers from 1 to 10:

#include <stdio.h>

int main () {

int i=1;

while (i<=10)

{ //True, since i=1 which is <10

printf("%d\t", i);

i++; //take next value of i and check while condition again

}

return (0);

}

Find the sum of series:

#include <stdio.h>

int main () {

int i=1, s=0;

while (i<=10)

{

printf("%d\t", i);

s=s+i;

i++;

}

printf("\nsum of series = %d", s);

//sum is to be printed once that's why its outside loop

return (0);

}

Print even numbers from 1 to 10:

#include <stdio.h>

int main () {

int i=1;

while (i<=10)

{

if (i%2 ==0) // if (i%2! =0) to print odd numbers from 1 to 10

printf("%d\t", i);

i++;

}

return (0);

}

Factorial of a given number:

#include <stdio.h>

int main () {

int n, fct =1;

printf("Input a number\n");

scanf ("%d", &n);

while (n>1)

{

fct = fct * n;

n--;

}

printf("factorial = %d\t", fct);

return (0);

}

Fibonacci series 0 1 1 2 3 5 8

#include <stdio.h>

int main () {

int n1, n2, n3, lmt;

printf("Input Starting Term-1 and Term-2\n");

scanf ("%d%d", &n1, &n2);

printf("Input Limit of the series\n");

scanf ("%d", & lmt);

while (n1<=lmt)

{

printf("%d\t", n1);

n3 = n1 + n2;

n1=n2;

n2=n3;

}

return (0);

}

Program to find the reverse of a number and sum of digits of a number:

For example:

n = 1234. Reversed n is 4321 and sum of digits =10

Hint: When we divide a number by 10, remainder gives us the rightmost digit while quotient returns the remaining digits of the number 1234% 10 would give us 4, while 1234/10 gives us 123:

#include <stdio.h>

int main () {

int num, rem, s=0;

printf("Input a number\n");

scanf ("%d", &num);

while (num>0)

{

rem = num % 10;

printf("%d", rem);

// for num = 123, 3 will be printed, then 2 and finally 1

num = num / 10; // the number should be reduced to 12

s = s + rem; //if you want to print sum of digits

}

printf("\nSum of digits = %d", s);

return (0);

}

do...while loop

A do-while loop is another control flow construct in the C programming language. It enables us to keep executing a code block until certain conditions are met. Do-while loop is a variant of a while loop. The do-while loop executes the code block at least once before checking the condition. If the condition is true, the control keeps executing the block of statement(s), part of the loop. This process repeats until the given condition evaluates to false, in contrast to the while loop where condition is checked at the start of the loop.

Do-while loop’s essential elements:

Code block: The loop’s body is the code between the curly brackets “{}” It includes the statements that must be executed in a loop.

Condition: A Boolean expression known as the loop-condition defines whether the loop should continue. Each iteration ends with an evaluation of the condition. The loop continues if the condition evaluates to true; otherwise, it ends.

The do-while loop comes in handy when we need to run the loop’s body at least once, regardless of the circumstance. The initial iteration is the only time the loop condition is examined:

do {

//set of statements to be executed while the condition is true.

} while (condition);

Some programming examples:

Print numbers from 1 to 10 and find the sum of the generated series:

#include <stdio.h>

int main () {

int i=1, s=0;

do

{

printf("%d\t", i);

s=s +i;

i++;

}

while (i<=10); // in case of do..while, condition is terminated by ;

printf("\nsum of series = %d",s);

//Note: sum is to be printed only once that's why its outside loop

return (0);

}

Assignment: Do all the above while programs using do while.

Note: You can do all the above (and other programs) using do..while loop by simply using a do while loop instead of while loop. Now the question is when should you use the do…while loop. Since the do while loop checks the condition at the end of the body loop, this means that whether the condition is false the loop body would execute at least once, Therefore, use do..while loop when you want loop to get executed once, for sure!

Let us see the following example:

To add two numbers (repeatedly, till user wishes to quit):

#include <stdio.h>

int main () {

int a, b;

char c;

// 2 No's are to be added FOR THE FIRST TIME but for subsequent additions, you can ask user if he wants

do

{

printf("Enter 2 Nos to add \n");

scanf ("%d%d", &a, &b);

printf("sum = %d", a+b);

printf("\nPress y to add 2 more numbers\n");

} while (getch() == 'y'); // getch is used to input a character

return(0);

}

Table 3.1 shows the difference between while and do-while statements:

while

do-while

Condition tested at the beginning before statements get executed.

Condition tested at the exit of the loop after statements are executed (executed at least once).

Block of statements may not be executed even once if the condition is false.

Block of statements gets executed once even if the condition is false.

No semicolon at the end of while. while(condition)

Semicolon at the end of while. while(condition)

If there is a single statement, brackets are not required.

Brackets are always required.

Variable in the condition is initialized before the execution of loop.

Variable may be initialized before or within the loop.

Table 3.1: while vs do-while loop

For loop

A for loop is a fundamental control flow structure in the C programming language enabling a programmer to execute block of code a predefined number of times or iterate over a sequence of values. The for loop consists of three main components: initialization, condition, and update.

The for loop in C in C require the following components to function properly:

Initialization: It entails setting the loop control variable(s) to their initial values before starting the loop. The first time the loop is started, this step is only performed once.

Condition: This expression decides whether the loop should keep running. The for-loop will continue to execute if the given condition evaluates to true. The loop ends and control shifts to the statement after it if the condition evaluates to false.

Update: It entails changing the loop control variable(s) following each loop iteration. After each cycle, the update step is carried out immediately before re-evaluating the condition. It alters the loop control variable, which impacts how the loop functions.

When we have a clear setup, condition, and update stages, the for loop offers a more condensed syntax for looping than the while loop. It is frequently utilized when we know the precise number of iterations needed.

for (initialization; condition; step)

{

//set of statements to be executed while the condition is true

}

For example:

for (i=1; i<=10; i++) {

printf("%d\t", i); //incrementing loop, because step is +ive

i++;

}

Here i is the counter initialized to 1 and counts from 1 to 10. Every time condition (i<=10) evaluates to TRUE the loop body is executed (and value of i is incremented by 1), so in this example value of i would be printed if value of i is less than or equal to 10. Similarly:

for (i=10; i>=1; i--) {

printf("%d\t", i);

}

It would print numbers from 10 to 1 and is an example of decrementing loop (step is negative … i--).

Iterations of a loop refer to how many times body of a loop got executed (10 in the above example).

Some programming examples:

Print numbers from 1 to 10 and find the sum of the generated series:

#include <stdio.h>

int main () {

int i, s=0;

for (i=1; i<=10; i++)

{

printf("%d\t", i);

s = s + i;

}

printf("sum of series = %d", s);

return (0);

}

Check if a given number is prime:

Hint: A number (n) is prime if it is perfectly divisible (Remainder=0) by 1 and itself. Programmatically, we would divide it by 2, 3, 4………….and n/2. If remainder comes out to be 0 every time, then it is a prime number, otherwise it is a composite number:

#include <stdio.h>

int main () {

int i, n;

printf("Input No.\n");

scanf ("%d", &n);

for (i=2; i<=n/2; i++)

{

//n is prime if it is not perfectly divisible by any no. between 2 and n/2.

if (n%i ==0)

{

printf("The given number is composite");

exit (0);

}

}

printf("The given number is Prime");

//control reaches this point only when the number is prime

return (0);

}

Factorial of a given number:

#include <stdio.h>

int main () {

int n, i, f =1;

printf("Input a No.\n");

scanf ("%d", &n);

for (i=n; i>1; i--)

{

f = f* i;

i--;

}

printf("factorial = %d\t", f);

return (0);

}

Loop control statements

In C, loop control statements change how a loop typically executes. Loop control statements enable us to alter execution of a program from its routine sequential execution. They provide us more flexibility and control over loops by letting us stop or skip iterations depending on circumstances. Break, continue, and goto are C’s three primary loop control statements.

Break

The break statement is used to control the flow of control structures like loops, conditional statements, and branching statements. When the break statement is encountered, the program instantly halts the control structure’s execution, in which the break has been used, and execution moves on to the code that follows the control structure. break statement is generally used to terminate loops or switch statements.

Break example 1:

#include <stdio.h>

int main () {

int i;

for (i=1; i<=10; i++)

{

if (i == 2)

{

break;

}

printf("%d\t", i);

}

printf("\nbye");

return (0);

}

Output:

1

Bye

In this program, as soon as condition if(i==2) evaluates to true, control will come out of the for loop and the printf() instruction will get executed, before ending execution of the main() function.

Break example 2:

#include <stdio.h>

int main () {

int n1, n2;

do

{

printf("Input 2 numbers\n");

scanf ("%d%d", &n1,&n2);

if (n2 == 0) {

printf("Cannot Divide by Zero\n");

break;

}

printf("Quotient = %d\n", n1/ n2);

printf("Press y to continue\n");

} while (getche()=='y');

return (0);

}

In this program, if user inputs 0 as the second number, the corresponding if statement evaluates to true, break will force control to come out of the do-while loop.

Continue

The continue statement is used to go to the next iteration of a loop without running the remaining code in the current iteration. The control variable for the loop is updated and the loop condition is re-evaluated when the continue statement is met. This enables us to skip some iterations depending on criteria.

Example:

#include <stdio.h>

int main () {

int i;

for (i=1; i<=10; i++)

{

// if i=2, control skips the following instructions and goes to next loop-iteration instead

if (i ==2)

{

continue;

printf("%d\t", i);

}

return (0);

}

Output:

1 3 4 5 6 7 8 9 10

Goto

The goto statement provides an option of an unconditional hop to a labelled statement declared inside the same function. It enables us to shift control from one location in our code to another location, overriding the usual order of execution. However, it is generally better not to use goto instructions often, because they might make the code difficult to comprehend and maintain.

Loop control statements must be employed judiciously to make sure the code is comprehendible and at the same time it remains maintainable. Overuse of loop control statements may render the code complex and incomprehensible. If used suitably, these statements can provide amicable control and flexibility in the execution of loop controls in C.

Goto example

#include <stdio.h>

int main () {

int a, b;

char c;

Repeat: //Label Repeat

printf("Enter 2 Nos to add \n");

scanf ("%d%d", &a, &b);

printf("sum = %d", a + b);

printf("Press y to add 2 more numbers\n");

scanf ("%c", &c);

}

if (c == 'y')

goto Repeat;

return(0);

}

Infinite loop

If a condition never becomes false in a loop, it executes infinitely. The for loop is typically employed for this. The for loop may be made into an unending loop by leaving the conditional expression empty since none of the three expressions that make up the loop are necessary:

Using for loop:

#include<stdio.h>

int main (){

for (; ; )

{

printf("Example of infinite loop\n");

}

return (0);

}

Infinite loop using while ()

#include <stdio.h>

int main () {

while (1)

{ // 1 here means we made the condition 'explicitly' True

printf("A never ending loop\n");

}

return (0);

}

Note: When the conditional expression is absent or we make it explicitly true (1) in case of while, it is assumed to be true. We may have an initialization and increment expression, but C programmers more commonly use the for (;;) construct to signify an infinite loop.

Infinite loop using goto:

#include <stdio.h>

int main () {

rep:

printf("printing a message in infinite number of times\n");

goto rep:

return (0);

}

In the above example, goto statement sends control to the rep label, which causes the loop to repeat indefinitely.

Note: We can terminate an infinite loop by pressing Ctrl + C keys.

Nested loop

Sometimes we may have to use a loop inside another loop’s body which is known as the nested loop.

The degree of nestedness in programming refers to how deeply one construct (loop in this case) is nested inside another construct. In C, the level of nesting of loops is typically referred to as the degree of nestedness. For instance, a loop inside another loop has a nestedness level of 2, whereas a loop inside another loop inside another loop has a nestedness level of 3:

Syntax:

Outer loop {

Inner loop()

{ //we can have a number of them

//Set of statements of inner loop

Innermost loop()

{

// Set of statements of innermost loop

}

}

}

For example:

for (i=1; i<=10; i++)

{

for (j=1; j<=10; j++)

{

//set of statements to be executed…here executed 100 times

printf("Hello world\t");

}

}

Note: For every outer loop iteration, the inner loop takes all iterations. In the above example:

When i is 1 j takes the values 1,2,3…..10

Then i becomes 2 and j again takes the values 1,2,3…..10

Some programming examples:

Print the tables of 1 to 10 using nested loop:

#include <stdio.h>

int main () {

int i, j;

for (i=1; i<=10; i++)

{

for (j=1; j<=10; j++)

{

//when i is 1, table of 1 would be printed i*1, i*2, i*3

printf("%d\t", i*j);

}

printf("\n"); //change line to print next table on new line

}

return (0);

}

Note: In the programs where we need to print tabular patterns, outer loop manages the rows and the inner one manages the columns. In the above example, there are 10 rows and 10 columns, which is why the outer loop’s limit is 10 and so is the limit for the inner loop.

Print the following pattern:

*

*

* *

* * *

* * * * *

#include <stdio.h>

int main () {

int i, j;

for (i=1; i<=5; i++) // 5 rows

{

for (j=1; j<=I; j++)

{ //No. of columns in each row= i

printf(" * ");

}

printf("\n");

} //line is changed to print stars on different lines

return (0);

}

Print prime series:

#include<stdio.h>

int main () {

int n, i, f;

for (n=10; n<=100; n++)

{ //Generates numbers from 10 – 100

f=0; // for every number f=0

for (i=2; i<=n/2; i++)

{

if (n%i ==0)

{

f=1;

//Make f=1 when n is divisible by any value of i

break;

}

}

if (f==0)

//n was never perfectly divisible i. Hence n is prime, print it.

printf("%d\t", n);

}

return (0);

}

Print 1¹ + 2² + 3³ + 44 + ……….+ nn :

Before doing this program you can try the program to find base to power of a number.

#include <stdio.h>

int main () {

int n, i, j, c, s=0;

printf("Input limit\n");

scanf ("%d", &n);

for (i=1; i<=n; i++)

{ //Generates numbers from 1 to n

c=1;

for (j=1; j<=i; j++)

{

c = c * i;

}

s = s + c;

}

printf("Sum of series = %d", s);

return (0);

}

Print 1! + 2! + 3! + 4! + ………. + n!:

#include <stdio.h>

int main () {

int n, i, f, j, s=0;

printf("Input limit\n");

scanf ("%d", &n);

for (i=1; i<=n; i++)

{ //Generates numbers from 1 to n

f=1; // initialize f to 1

for (j=i; j>1; j--)

{

f = f * j;

}

// use printf function if you wish to print factorial 'f' of every number

s = s + f;

}

printf("Sum of series = %d", s);

return (0);

}

Check for Armstrong number, the sum of cubes of digits of the number = the number itself for example: 153 is an Armstrong number, because 13 +53 + 33 = 153:

#include <stdio.h>

int main () {

int n, i, f, s=0, b;

clrscr ();

printf("Enter n\n);

scanf ("%d", &n);

i=n;

while (i>0)

{

b=i % 10; //get right most digit

f=1; // for every number f=0

for (j=1; j<=3; j++)

{ // find cube of the digit b

f = f * b;

}

s = s + f;

i= i /10;

}

printf("sum of series = %d", s);

return (0);

}

Conclusion

In summary, this chapter has provided a comprehensive exploration of foundational programming constructs that enable precise control over program flow and informed decision-making within the realm of C programming. Beginning with an examination of control statements, we moved on to discuss decision-making statements, including the adaptable if-else and switch constructs. We also delved into strategies for managing diverse scenarios and introduced essential looping structures like while, do-while, and for loops.

Equipped with this knowledge, readers are now well-prepared to orchestrate program execution and navigate intricate decision points in their C programs. As we conclude this chapter, it serves as both a culmination of our study and a launchpad for further mastery in shaping program flow and logical choices within the C programming paradigm.

Exercise

What are the different types of control statements?

What is an if statement in C programming?

Write a program to demonstrate if-else statement work

What is a switch statement in C programming?

How does a switch statement work? Demonstrate by a program.

What is a while loop in C programming?

Show how a while loop works by a simple program.

What is a do-while loop in C programming?

What will be the output of the following C program?

#include <stdio.h>

int main() {

int i;

for (i = 1; i <= 5; i++) {

printf("Iteration %d\n", i);

}

return 0;

}

What is a for loop in C programming?

What is the syntax of a for loop? How does a for loop work?

Write a C Program to check whether a given no. is prime or composite.

What is the difference between a while loop and a do-while loop?

What will be the output of the following C program?

#include <stdio.h>

int main() {

int count = 0;

while (count < 5) {

printf("Count: %d\n", count);

count++;

}

return 0;

}

What is the difference between a for loop and a while loop?

What is a nested loop in C programming?

Write a C program to check greatest of four numbers.

How does a break statement work?

What will be the output of the following C program?

#include <stdio.h>

int main() {

int num;

do

{

printf("Enter a positive number: ");

scanf("%d", &num);

} while (num <= 0);

printf("You entered: %d\n", num);

return 0;

}

Write a program to demonstrate use of break statement in C programming.

What is the syntax of a break statement?

Write a program to demonstrate continue statement.

What is a goto statement in C programming?

Write a C program to show use of goto statement.

Write a program to demonstrate a goto statement.

What is a switch case statement in C programming?

How does a switch case statement work?

What is the syntax of a switch case statement?

What is a default statement in a switch case statement?

How does a default statement work in a switch case statement?

What is the syntax of a default statement in a switch case statement?

What is the purpose of a continue statement in a loop?

What is the purpose of a goto statement in C programming?

Write a program to demonstrate nested loop?

Write a C Program to demonstrate use of a switch statement?

What is the purpose of a default statement in a switch case statement?

Join our book’s Discord space

Join the book’s Discord Workspace for Latest updates, Offers, Tech happenings around the world, New Release and Sessions with the Authors:

https://discord.bpbonline.com

CHAPTER 4Functions

Introduction

Functions in C programming are blocks of code that perform specific tasks. They are also known as modules, routines, procedures, and sub-programs. They offer means of breaking down a program into smaller, more manageable pieces of code. They promote modularity, code reusability, and enhance program organization. Functions can be built-in, user-defined, or part of external libraries. They have a well-defined structure, which includes a function prototype and a function definition, clearly mentioning return type and parameters. Function prototypes are used to declare functions in advance. Recursion is a powerful concept in C, where a function calls itself to solve complex problems. Understanding function variable scope is important, with local variables being limited to a specific block and global variables accessible across functions.

Structure

In this chapter, we will cover the following topics:

Functions

Scope of variables in functions

Recursion

Callback functions

Objectives

The objective of this chapter is to provide an understanding of functions in C programming. It covers function declaration, definition, calling, return statements, parameters, variable scope, and recursion. By the end of this chapter, readers will have a solid foundation in using functions effectively to structure their code, improve code organization, and solve complex problems using recursion.

Functions

A function is a module or a sub-program containing a group of statements that perform a specific task. A C program may consist of one or more functions; however, if there is only one function in a C program, it must be main() because it is the gateway to program execution; that is, program execution in C starts with this very function. The function may be considered the fundamental building element of modular programming. Functions allow us to separate our code into more controllable, reusable units that carry out tasks. Functions encourage modular design and code reusability, which enhances program organization, readability, and maintainability. Please refer to the following figure:

Figure 4.1: Program split in functions

Since C programming epitomizes a structured programming style to solve a problem, C programming divides a problem (program) into smaller modules (sub-programs) called functions, each of which performs a specific task. For example, if we are writing a program to carry out arithmetic operations on two numbers, we may divide the program into individual subprograms (functions), as shown in Figure 4.1; here main() function carries out the controlling or coordination duties. Since execution of a C program starts with main() function, calling other functions is the responsibility of main (or some other calling) function, A function which calls another function, is known as a calling function, while the invoked function (to which control is passed) is known as the called function.

The function definition is when we define a function along with its signature and instruction set starting with {and ending with} character.

Add () {

// instruction set of add function

}

multiply () {

// instruction set of multiply function

}

divide () {

// instruction set of divide function

}

subtract () {

// instruction set of subtract function

}

int main () {

//instruction of main

//instruction of main

multiply (); // function call - invoking multiply () function

//instruction of main

subtract (); // invoking subtract ()

//instruction of main

//instruction of main

divide (); // invoking divide ()

//instruction of main

//instruction of main

Add (); // invoking Add ()

}

The distribution of instructions to various functions is up to the programmer, but logically, the division is such that each function performs a specific task and hence should only contain those instructions that are aimed at solving that particular task. A function may also be referred to as a method, sub-program, sub-routine, procedure, and so on.

Key features of functions in C

The following are some of the key features of C:

Function declaration: A function declaration tells the compiler about a function’s name, return type, and parameters. A function declaration provides the actual body of the function. Before a function is utilized in a program, it must be declared in advance. The function name, return type, and any available parameters are all listed in the declaration:

int add (int a, int b); // Function declaration

Function definition: The function definition provides the actual implementation of the function. It specifies the function body, which contains the statements to be executed when the function is called.

Syntax:

return_type function_name (arguments/ parameters) {

Function_body

}

For example:

int add (int a, int b) { // Function definition

return a + b;

}

Return type: The return type identifies the data type of the value that the function will return after completion. If it is a valid C data type, it may be a built-in or user-defined type. int, float, void, and so on are among them.

Parameters or arguments: We sometimes supply values to a function using variables called parameters. It is generally a comma-delimited list of expressions (parameter list). A function may contain any number (and type of) parameters. They can be used in the function body and are written in the brackets that follow the function name. The function can receive input from parameters and return output because of the computations done on these parameters.

int add (int a, int b) { // Here a and b are the parameters

return a + b;

}

Function call: We need to call or invoke a function to execute its code. The function is invoked (called) by its name (as when calling our friend, we generally call them by their name) followed by parentheses. If the function requires some data, when invoked, we pass the data in parameters in parentheses written after the function name. The function call can be used as an expression or a standalone statement (in this case, the function call statement must end with a semi-colon).

For example:

int sum = add (2, 3); // Function call and assignment

or simply call it:

add (x, y); // Simple Function call

The above examples are when the function call is a standalone statement.

printf ("%d", add (2, 3));

The above example represents a scenario when the function call is an expression.

Void functions: Functions with a return type of void are referred to as void functions, and these functions do not return any values. Void functions are employed when a function is designed to carry out a specified task or process without returning a specific result.

Function prototypes: Forward declarations of a function are referred to as function prototypes. They disclose details such as the name, return type, and function parameters without implementation. Prototypes are frequently included in header files or at the start of programs. They enable the compiler to check the function’s use and guarantee the right argument types.

For a function:

int sum = add (x, y);

the prototype would be:

int sum (int, int);

This implies that we convey to the compiler that it will encounter a function during the compilation or execution of a program whose name is the sum that takes two integer values as arguments or parameters and will return an integer value.

Types of functions in C

Functions can be categorized into different types in C Programming based on their characteristics and behaviors. Here are some common types of functions:

Built-in functions: The standard library of C comes with several built-in functions. The header files stdio.h, stdlib.h, and string.h, among others, include these functions. printf(), scanf(), malloc(), and strcpy() are a few examples of built-in functions. These routines can be used directly without extra software or libraries.

User-defined functions: A programmer can construct user-defined functions to carry out certain tasks. The programmer within the software defines and puts them into action. User-defined functions provide modularity and reuse of the code. To encapsulate certain functionality, you can construct and invoke functions as necessary.

Library functions: External libraries or modules not part of the standard C library provide library functions. By adding new functionality, these functions increase the capability of C. Examples include'sqrt()', sin(), and pow() from the math library ('math.h>'). You might need to include the proper header file and link against the relevant library during compilation to access library functions.

Inline functions: Rather than being called a distinct function, inline functions are functions extended inline by the compiler at the call site. By lowering the cost of function calls, inline functions are meant to increase performance. The inline keyword is often used to define inline functions. However, the compiler finally decides whether to inline a function.

Function pointers: In C programming language, functions can be provided as arguments, handled as variables, or saved as variables. Function pointers are variables that hold a function’s memory address, and they let you use the function pointer to invoke a function indirectly. Implementing callback functions and developing adaptable and extendable code structures benefit from using function pointers.

Recursive functions: Recursive functions either directly or indirectly call themselves by segmenting larger issues into smaller subproblems and resolving each subproblem using the same function; they can solve problems. Recursive functions feature one or more recursive calls that address more specific examples of the issue and a base case that ends the recursion. Examples of recursive functions include calculating factorial, Fibonacci sequence, and traversing tree structures.

Based on the function definition, we have two kinds of functions:

Inbuilt functions: The functions that are part of the C standard library, meaning that these functions are already defined and can mean that they code for them. The C standard library provides numerous built-ins that your program can call. For example, strcat () to concatenate two strings, memcpy () to copy one memory location to another location, and many more functions.

User-defined functions: The functions not part of the C standard library whose code is written by a user. C allows us to define functions according to our requirements. These functions are known as user-defined functions.

For example:

void add () {

// statements

}

Defining a function

The general form of a function definition in C programming language is as follows:

return_type function_name (parameters) {

body of the function

}

A function definition in C programming consists of a function header and body.

Parts of a function:

Return type: A function may return a value. The return_type is the data type of the value the function returns. Some functions perform the desired operations without returning a value; in this case, the return_type is the keyword void.

Function name: This is the actual name of the function. The function name and the parameter list together constitute the function signature.

Parameters: A parameter is like a placeholder.

Function body: It contains a collection of statements defining the function.

Calling a function

A function can be called in C by its name followed by parentheses, containing (optional) arguments / parameters, to be passed to the called function. The function call might be a solitary statement or an expression, in case the called function is expected to return a value to the calling function.

The standard C syntax for invoking a function is as follows:

return_value = function_name (arguments);

Here:

return_value: You can assign the function’s returned value to a variable of the appropriate type if the return type is something other than void. You can skip the assignment if the function has a void return type.

function_name: name of the function you wish to invoke.

arguments: If the function requires arguments, you must enter the parameters’ values within parentheses, separated by commas.

For example:

#include <stdio.h>

int sum (int n1, int n2) {

return (n1 + n2);

}

int main () {

int ret = sum (7, 15);

printf ("The Sum = %d\n", ret);

return (0);

}

In the above example, the sum() function takes two int parameters (n1 and n2) and returns their sum. Inside the main() function, we call the add() function with the arguments 7 and 15. The returned value is assigned to the variable ret. Finally, we print the sum.

Formal arguments

A function must define variables that accept the arguments’ values if it uses them. The formal parameters of the function are what are known as these variables.

Formal arguments are produced at the function entrance and removed at the function exit, behaving similarly to other local variables.

There are two modes for passing arguments to a function when calling it:

Call or pass by value: This technique inserts the real value of an argument into the function’s formal parameter. In this instance, changes made to the parameter inside the function have no impact on the argument.

Most examples discussed in this chapter will use this type of argument passing.

Call by reference: With this technique, the address of an argument is passed to the called function. The function uses the address to retrieve the actual argument used during the call. This implies that modifications to the parameter have an impact on the argument. In other words, changes made to such a variable reflect within the called function and other functions, including the calling function.

C, by default, passes arguments by value, which generally indicates that a function’s code cannot change the parameters passed to the function during callbacks.

Passing arguments by reference will be discussed in the chapter on Pointers.

Return statement

The return statement is used in the C programming language to end the execution of a function by returning a value to the calling function (if the function has a return type other than void). It serves to communicate the computation’s outcome to the calling function and is an essential component of functions. The return statement has the following syntax:

return expression;

Where:

expression represents the value which the called function returns to the calling function. The return type of the function, which is defined in the function definition or definition, should be consistent with this value.

We may terminate a function early by using the return statement, which also lets us return values from functions. When a function encounters a return statement, the function instantly terminates, and control is sent back to the calling function.

A return statement containing a value matching the specified return type must be included in functions that have a non-void return type. Functions having a void return type can be terminated with a return without an expression, even if they do not return a value.

Various ‘return’ scenarios in C

Returning a value: If a function has a return type other than void, it must have a return statement with an expression equivalent to the anticipated return type. Any legal expression on par with the return type may be used instead of a variable, constant, or legal construct.

For example:

int sum (int n1, int n2) {

return n1 + n2;

}

In the preceding example, the sum() function takes two integers as parameters. It returns its sum using the return statement with the expression n1 + n2 (The sum of two integers is supposed to be an integer, which is the function’s return type).

Ending execution: Even when the function is not returning anything (When the return type is void), the return statement can be used to end execution early. It can stop the function from running and give the caller function back control.

For example:

void msg () {

printf ("Hello, world");

return;

// No value is returned, function execution ends here

}

In the preceding example, the msg() function prints a message and terminates without returning a value.

Multiple return statements: Functions can have multiple return statements, which enable various circumstances to return varied results or to leave the function at various times. On finding a return statement, execution of the function promptly stops, and control is returned to the calling function.

int max (int a, int b) {

if (a > b) {

return a;

}

else {

return b;

}

}

In the preceding example, the max() function compares two integers and returns the larger value using two return statements, one for each condition.

Returning from main (): The return statement can be used by the main() function, a C program’s entry point, to end the program. The result of the main() function is frequently used to indicate the program’s exit state. Conventionally, a return value 0 denotes a successful execution, whereas non-zero numbers might denote faults or certain situations.

int main () {

// Program code

return 0; // Successful execution

}

In the preceding example, the main() function returns 0 to indicate the successful execution of the program.

The return statement is essential for managing program flow and returning data from functions in C. It permits early function termination, allowing functions to return results or information to the caller code, and makes it easier to determine the program’s exit state.

Function signature

Combining the function’s name, return type, and parameter types collectively determines a function signature in C. It gives a succinct overview of the function’s interface, enabling you to recognize and classify functions according to their signatures.

The following components are part of the function signature:

Function name: A function is identified within a programme by its unique name. When necessary, it is used to reference or invoke the function.

Return type: The data type of the value that the function returns after it has been executed is specified by the return type. Any legitimate C data type may be used or void if the function returns null.

Parameter types: The function’s input arguments’ data types are determined by the parameter types. Parameters allow you to pass values from the calling code to the function for processing.

The function signature enables you to distinguish between functions based on their names, return types, and argument types and aids the compiler in enforcing type safety. This allows the compiler to carry out type checking, find mistakes, and guarantee proper function usage inside the program.

For example, in the case of the function or declaration:

int sum (int a, int b);

The functions signature consists of:

Function name: sum

Return type: int

Parameter types: int and int

The function sum takes two int parameters (a and b) and returns an int value. The signature allows us to identify the function by its name, return type, and parameter types, making it distinct from other functions with different signatures.

The compiler provides consistency between function calls and definitions by matching function signatures, preventing potential errors caused by mismatched types or invalid arguments. The proper declaration, definition, and use of functions in C depend on understanding function signatures. It aids in maintaining the readability, clarity, and conformance to the proper function interface of the code.

Based on the signature, we have three kinds of functions:

Type-1: Functions with no arguments and no return value.

Type-2: Functions with arguments but no return value.

Type-3: Functions with arguments as well as a return value.

Example: Type 1

#include <stdio.h>

void add () { //User-defined function - add

int a = 10, b = 20;

printf ("sum = %d", a+ b);

//after printing sum control would go back to main

}

// Execution of a C Program starts with main function

int main () {

printf ("Hello from main\n");

add (); // here we send control to add function

// we will resume from here when control returns from add function

printf ("Back in main\n");

return (0);

}

Here:

Function name: add

Arguments: None

Return type: void

Note: The execution of the program always starts with the main (), no matter where it is written in the program. Here execution would start from the main too, and then control would shift to add () and would be back in the main to exit t the program finally.

Example: Type 2

#include <stdio.h>

void add (int x, int y) { // x and y are formal parameters

//after printing sum, control would go back to main

printf ("sum = %d", x+ y);

}

int main () {

int a = 10, b = 20;

printf ("Hello from main\n");

add (a, b); // add function called with 2 arguments (Actual parameters)

printf ("Back in main\n");

return (0);

}

Here:

Function name: add

Arguments a and b (Actual), x and y (Formal)

Return type: void

Example: Type 3

#include <stdio.h>

int add (int x, int y) {

return (x+ y);

//We are returning 'sum' of 2 int's which in turn would be an int

}

int main () {

int a = 10, b = 20, s;

printf ("Hello from main\n");

s = add (a, b); // add () called with 2 arguments, returned value would be stored in s

printf ("Returned value or sum = %d", s);

return (0);

}

Here:

Function name: add

Arguments a and b (Actual), x and y (Formal)

Return type: int

Note: We cannot declare more than one variable with the same name in a function, but you can do that in different functions. As is evident from the above Example 3: we have ‘a’ and ‘b’ variables in the main () function, and we have ‘a’ and ‘b’ variables in add () function.

Mismatch

When the actual and formal arguments’ number and (or) type do not match, it is called a mismatch. For example, if you send two arguments (actual arguments) to a called function, then there must be two formal arguments (comma delimited) defined in that function, which would be a match; otherwise, it would be a mismatch.

For example:

void Add () {

………..

}

int main () {

Add (2, 4);

}

Here two arguments are passed, but the called function does not offer formal arguments to store them.

Similarly, we can have a type mismatch (The above example was of number mismatch) in which type of arguments are mismatched:

void add (int a, char b) {

………..

}

int main () {

add (2,4);

}

Here two arguments are passed, and two formal arguments are declared, but one of them is a char which is a mismatch as we have passed two integers, so both the formal arguments should have been int’s.

Function variable scope

In C, a variable defined within a function has a local scope and can only be accessed. A variable defined outside of any function has a global scope and can be accessed from any function in the program.

A new stack frame is created when a function is called, and all the local variables defined within the function are stored in that stack frame. Once the function returns, the stack frame is deleted, and the local variables are no longer accessible.

Note: A program can have the same name for local and global variables, but the value of the local variable inside a function will take preference. This is known as variable shadowing.

For example:

#include <stdio.h>

int g = 20; // global variable declaration

int main () {

int g = 10; // local variable declaration

printf ("value of g = %d\n", g);

}

Output:

value of g = 10

In C, you can use a static keyword to define a variable with local scope, but it will have a lifetime from when it is defined until the end of the program.

int g_x = 5;

void func () {

int l_x = 10;

static int s_x = 15;

printf ("%d %d %d\n", global_variable, local_variable, static_local_variable);

}

In the preceding example, g_x has a global scope and can be accessed from any function in the program, l_x has local scope and can only be accessed within func(), and s_x has local scope and can only be accessed within my_function. Still, its lifetime is the whole program.

It is important to note that, by convention, global variables in C are named with the prefix g_ to indicate they are global.

Local variables

Local variables are declared within a specific code block, such as within a function or a block enclosed by curly braces `{}`. Local variables are only accessible within the block in which they are declared. They are created when the block is entered and cease to exist when it exits. Each function call creates a new set of local variables.

The following example shows how local variables are used. Here, all the variables a, b, and c are local to the main () function:

#include <stdio.h>

int main () {

int a, b, c; // local variable declaration

// actual initialization

a = 10;

b = 20;

c = a + b;

printf ("value of a = %d, b = %d and c = %d\n", a, b, c);

return (0);

}

Output:

Value of a = 10, b = 20 and c = 30

Function parameters or variables

Function parameters are variables listed in a function declaration’s parameter list. They are assumed as local variables within a function and take preference over global variables. They act as local variables inside a function and are only used within the boundaries of that function. When it is called, the values given to the function serve as the starting values for its arguments.

For example:

#include <stdio.h>

int n1 = 20; // global variable declaration

int main () {

int n1 = 10, n2 = 20, sum;

// local variable declaration in main function

printf ("value of n1 in main () = %d\n", n1);

sum = add (a, b);

printf ("value of sum in main () = %d\n", sum);

return (0);

}

int add (int n1, int n2) { // function to add two integers

printf ("value of n1 in sum () = %d\n", n1);

printf ("value of n2 in sum () = %d\n", n2);

return (n1 + n2);

}

Output:

value of n1 in main () = 10

value of n1 in sum () = 10

value of n2 in sum () = 20

value of sum in main () = 30

Global variables

Global variables are defined outside any functions that make up a program. Traditionally they are defined at the start of the programme. All programme functions have access to and control over global variables. Even after the function that initializes them has finished, global variables have their initial values.

The following program shows how global variables are used in a program:

//global variable declaration

#include <stdio.h>

int g_sum;

int main () {

int n1, n2; // local variable declaration

n1 = 10; // actual initialization

n2 = 20; // actual initialization

g_sum = n1 + n2;

printf ("First No. = %d, 2nd No. = %d and Sum = %d\n", n1, n2, g_sum);

return (0);

}

Output:

First No. = 10, 2nd No. = 20 and Sum = 30

The main () function was able to access the global variable (as would have been other functions had they been part of this program), declared outside the main () function is executed, it produces the output:

value of g = 10

Initializing local variable and global variables

Initializing variables at declaration ensures they have the value you want for them immediately. Uninitialized variables may cause unexpected behavior or issues, which this might help to prevent. Remember that assignment statements can be used to assign values to variables after they have been declared. When a precise starting value is in mind, initialization now of declaration is desirable.

It is crucial to remember that local variables will have garbage values if you do not supply an initial value during initialization, and global variables will be initialized with default values (0 for numeric types, NULL for pointers, and empty text for character arrays) if you do not.

You may directly establish variables’ initial values and guarantee consistency in your C programs by initializing variables during the declaration process.

Callback function

A function supplied as a parameter to another function in C is known as a callbackFunction. A callback function’s main objective is to enable you to alter or expand a function’s behavior without changing the function’s source code. When you wish to provide your code flexibility and extensibility, callback functions are frequently employed in event handling, asynchronous computer programming, and other situations.

#include <stdio.h>

typedef void (*CallbackFunction)(int);

void sum(int n1, int n2, CallbackFunction cbf) {

printf("Sum = %d\n", n1+n2);

cbf(n1+n2);

}

//callback function

void cbf(int sum) {

printf("Average = %d\n", sum/2);

}

int main() {

sum(10,20, callbackFunction);

return (0);

}

Here:

First, we define a callback function of type CallbackFunction. Functions that accept an integer as parameter and returns void. We then invoke sum function, which takes 2 integer values and a callback function of type CallbackFunction as parameters. After computing and printing sum of the 2 given numbers, it invokes the callback function with the sum as argument. The callback function then calculates average of the two given numbers.

Callback function is a helpful technique for detaching and dividing up our code. They let us alter or expand a function’s functionality without changing how it is implemented. In more complicated circumstances, we may define a custom structure or use function pointers with various argument types to convey more data to the callback function.

Function as an argument

In C programming language, we can pass a function as a parameter to a different function by means of function pointers. This is characteristically done to render a function more flexibility and to make it generic. By passing diverse functions as parameters, we can alter the conduct of the higher-level function with not having to modify its code.

The function passed as a parameter can perform a designated task within the higher-level function, with not necessarily involving asynchronous or event-driven behavior.

#include <stdio.h>

// Define a function type using a function pointer

typedef int (*task)(int, int);

int arithmetic(int a, int b, task op) {

return op(a, b);

}

// Example operation functions

int sum(int a, int b) {

return a + b;

}

int difference(int a, int b) {

return a - b;

}

int product(int a, int b) {

return a * b;

}

int main() {

int n1, n2;

printf("Input 2 numbers\n");

scanf("%d%d", & n1, &n2);

// Pass different operation functions to the calculate function

int s = arithmetic(n1, n2, sum);

int d = arithmetic(n1, n2, difference);

int p = arithmetic(n1, n2, product);

printf("Sum: %d\n", s);

printf("Difference: %d\n", d);

printf("Product: %d\n", p);

return (0);

}

Here:

We have defined a function type called task with the help of a function pointer, which characterizes functions that take two integers as arguments and return an integer.

The arithmetic function takes two integers (a and b) and a function pointer of type task as parameters. It then invokes the specified task function with the given numbers and returns the corresponding result.

We defined three example task functions: sum, difference and product. Each of these functions matches the task type.

In the main function, we call the arithmetic function with different task functions (sum, difference, and product) to perform various calculations.

In C, one effective method is to send function pointers as arguments. It enables you to write more adaptable, generic code that lets you swap out various behaviors without changing the fundamental reasoning behind your methods. This is particularly helpful for putting callback systems, unique sorting algorithms, and various other dynamic behavior requirements into practice.

Although they are not precisely the same, a callback function and a function supplied as an argument are similar ideas. Both require sending a function as a parameter to a separate function, but their functions and use circumstances differ.

In conclusion, even though both approaches entail passing arguments as functions, their respective use cases are where they vary most. A general method for making functions more adaptable and reusable is function as an argument. A callback function, on the other hand, is a particular kind of function that is supplied as an input and is run asynchronously according to an incident or condition. Programming that is event-driven and asynchronous frequently uses callbacks.

Recursion

Recursion is the term used in C to describe the concept of a function calling itself directly or indirectly. They divide more complex tasks into simpler or smaller ones, which makes programming simpler. A termination condition is applied to these functions to stop them from executing endlessly. This is also known as the fundamental condition or base case. All tasks that can be solved recursively may also be solved iteratively. It is an effective programming approach that solves challenging issues by splitting them into simpler, easier-to-handle subproblems. The base and recursive cases are the two fundamental parts of recursive functions:

Base case: The base case checks for condition, and the function will continue to call itself if it satisfies.

Recursive case: In the recursive case, the function calls itself with arguments that are constantly revised for subsequent function calls.

For example:

Let us call the disp () function with argument 5:

disp (3);

int disp (int x) {

    if (x == 0) // Base Case

        return 0;

    else {

        printf ("%d", x);

        return disp (x-1); // Recursive case

    }

}

The function will call itself if x is not equal to 0, with arguments: 3, 2, and 1 (and prints each of them), and as soon as x becomes 0, the function returns control to the calling function.

Memory usage in recursion

Each recursive call creates a new instance of that memory method, and the procedure removes the copy from memory after returning some data. Each time a recursive call is made, a distinct stack is kept since all variables and other items defined inside functions are saved on the stack. The stack is deleted after the value has been returned from the associated function. The intricacy of recursion is in tracking and resolving the values at each call. As a result, we must keep track of the stack’s values and the variables it contains.

In the above example, all stacks are preserved, which print the corresponding values of x until they become 0. When the termination condition is satisfied, the stacks are freed, one after another, by returning 0 to its calling stack (Figure 4.2).

Figure 4.2: Memory map for above recursion

Recursions are employed to tackle difficult programming tasks, such as the Tower of Hanoi and other similar ones. Programmers must be cautious and use an exit condition from the function when employing recursion.

Several programming or mathematical problems can be solved with the help of recursive functions; like factorial of a number may be calculated; Fibonacci sequences can be created:

Factorial of a number:

int fct (int i) {

if (i <= 1) {

return (1);

}

return i * fct (i - 1);

}

Figure 4.3: Factorial of a number using recursion

As shown in Figure 4.3, fct (3) would call 3*fct (2), which would, in turn call 2*fct (1). Now fct (1) would return 1, which evaluates to 2* fct (1) to 2*1=2, which would, in turn evaluate 3* fct (2) to 3*2=6, which finally leads to 6 being returned to the calling function (main) and is stored in f which would be printed as output:

#include <stdio.h>

int main () {

int n = 3, f; // let's say n=3 for simplicity

f = fct (n);

printf ("Factorial of %d is %d\n", n, f);

return (0);

}

Tower of Hanoi is a recursive puzzle involving 3 rods some disks of different sizes, which are to be moved from one rod to another. The puzzle follows specific rules:

One disk can be moved at a time.

In each move, we can take the upper disk from one of the stacks and place it on top of other stacks (empty rod)

A smaller disk cannot be placed on top of a larger disk.

Figure 4.4 shows the three rods (currently all disks are on ROD ‘A’)

Figure 4.4: Tower of Hanoi

#include <stdio.h>

void ToH(int n, char A, char C, char B)

{

if (n == 1)

{

printf("\n Move disk 1 from rod %c to rod %c", A ,C );

return;

}

ToH(n-1, A, B, C);

printf("\n Move disk %d from rod %c to rod %c", n, A, C);

ToH(n-1, B, C, A);

}

int main()

{

int n ;

printf("Enter number of disks: ");

scanf("%d", &n);

ToH(n, 'A', 'C', 'B');

return 0;

}

Fibonacci series: The following example generates the Fibonacci series for a given number recursively:

#include <stdio.h>

int n = 30; //limit of Fibonacci series

void fib (int a, int b)

{

int c = a+ b;

if (c < n)

{

printf ("%d\t", c);

fib (b, c);

}

else

{

return;

}

}

int main () {

int a, b;

printf ("Input two initial values\n");

scanf("%d%d", &a, &b);

printf("%d\t%d\t", a, b);

fib(a, b);

}

When the above code is executed, it produces the output (assuming a=0 and b=1):

0 1 1 2 3 5 8 13 21 34

The sum of n numbers:

#include <stdio.h>

int sum (int n);

int main ()

{

int n, result;

printf ("Input a positive integer: ");

scanf ("%d", &n);

result = sum(n);

printf ("sum = %d", result);

return (0);

}

int sum (int n) {

if (n! = 0)

{

return (n + sum(n-1));

}

else

{

return(n);

}

}

Power of a number:

#include <stdio.h>

double power (double bs, int exp) {

if (exp == 0) {

return 1.0;

}

else if (exp > 0) {

return bs * power (bs, exp - 1);

}

else {

return (1.0 / bs) * power (bs, exp + 1);

}

}

int main () {

double bs, exp, res;

printf ("Input 2 positive integers as base and exponent\n");

scanf ("%lf%lf", &bs, &exp);

res = power (bs, exp);

printf ("%.2lf raised to the power of %lf is %.2lf\n", bs, exp, res);

return (0);

}

Count digits of a number:

#include<stdio.h>

int countdig(int);

int main () {

int n,d;

printf ("Input a number\n");

scanf("%d",&n);

d = countdig (n);

printf (" Number of digits in the number is: %d \n\n", d);

return (0);

}

int countdig(int n) {

static int d=0;

if(n!=0) {

d++;

countdig (n/10);

}

return (d);

}

Conclusion

In this chapter, we have learned about the fundamental concept of functions in C programming. We now understand that functions are modular units that perform specific tasks and enhance code organization, readability, and reusability. We have explored the different types of functions, including built-in, user-defined, library, inline, and recursive functions, each serving specific purposes. We have gained knowledge about function signatures, parameter passing, variable scope, and the use of return statements. Overall, we have gained a solid foundation in understanding functions and their role in developing efficient and structured C programs.

Exercise

What is a function in C programming? What is the purpose of a function?

What are the two types of functions in C?

Write a program in C to check if a given number is even or odd using the function.

What is the syntax for declaring a function in C?

What is the syntax for calling a function in C?

What is a function prototype? Why is a function prototype necessary?

What is the return type of a function? Can a function return multiple values in C?

Write a program in C to swap 2 numbers using a function.

What will be the output of the following program?

void swap(int, int);

int main()

{

int n1 = 3, n2 = 14;

swap(n1, n2)

printf("After swapping n1 = %d \n and n2 = %d",n1,n2);

}

void swap(int n1, int n2)

{

int t;

t = n1;

n1 = n2;

n2 = t;

}

Write a program in C to calculate square of a number using function.

How many values can a function return?

What is a void function? Can a void function return a value?

Write a program to check if a given number is positive or negative using the function.

What is the difference between a formal parameter and an actual parameter?

How are parameters passed to a function in C?

What is a default argument?

What is recursion?

Write a program to demonstrate recursion.

What is a static variable in a function? Give a programming example.

What is a global and a local variable?

What is the scope of a variable? What is the lifetime of a variable?

Can a function have the same name as a variable?

What is a function pointer?

How do you declare a function pointer?

What is a callback function?

How do you use a callback function in C?

Join our book’s Discord space

Join the book’s Discord Workspace for Latest updates, Offers, Tech happenings around the world, New Release and Sessions with the Authors:

https://discord.bpbonline.com

CHAPTER 5Arrays

Introduction

In this chapter, we will explore various topics related to arrays, string manipulation, and matrices in the context of programming. Arrays, which are one-dimensional structures, allow us to store and manipulate a collection of elements. We will learn how to perform operations on arrays, such as accessing elements and modifying their values. Additionally, we will delve into string manipulation, understanding how to work with strings effectively. Furthermore, we will delve into matrices and explore how to pass a two-dimensional matrix to a function.

Structure

In this chapter, we will cover the following topics:

Arrays (One-dimensional array)

Array manipulation

String manipulation

Matrices

Passing 2-D matrix to a function

Objectives

The objective of this chapter is to provide a comprehensive understanding of arrays, string manipulation, and matrices. By the end of this chapter, you will be equipped with the knowledge and skills necessary to manipulate arrays effectively, perform string operations efficiently, and work with matrices in programming. Through practical examples and explanations, we aim to enable you to apply these concepts in your coding projects.

Arrays: One-dimensional array

In the C programming language, arrays are a fundamental type of data structure. They offer a practical and effective approach to handling and storing identical data items. Understanding arrays is crucial for creating effective and reliable software, whether you are an expert programmer or a newbie learning C. We may store a fixed-size collection of identical-type objects using the C data structure called an array. Arrays can group related data objects associated with a single name. Arrays allow for the contiguous (one after the other) storage of a fixed-size sequential collection of objects of the same kind. However, it is sometimes more helpful to conceive an array as a group of variables of identical type. Since we assign a name to all the elements of an array, it is imperative to identify the individual elements of an array uniquely. To access individual elements of an array, we use a unique integer called index, which starts from 0. We use index notation to assign values to or access values of individual elements of an array.

Declaring and initializing arrays

Let us talk about declaring and initializing arrays in detail, along with examples.

Declaring arrays

In C programming, array is created by stating the type of each member, followed by the name and size of the array. The size of the array is specified in square brackets:

data_type array_name [array_size]

Where:

array_name is the name given to the array (Elements).

array_size must be an integer value greater than zero, and

data_type can be any valid data type defined in C.

For instance:

int num [10];

It will define an integer array with the name num and 10 members. Arrays can also be initialized at declaration, in which case a specified value is given to each element.

For example, the statement:

int num [10] = {10, 20, 30, 40, 50, 60,70,80,90,100};

It initializes the num array with the integer values 10, 20, 30, 40, 50, 60, 70, 80, 90, and 100.

double b [5] = {10.0, 2.60, 3.45, 7.0, 50.50};

Here b is a one-dimensional array that stores up to 5 double numbers.

Initializing arrays

In C, we may initialize arrays at the time of declaration by either giving the array size and individual values for each element or by using a list of values contained in curly brackets. Here are a few instances:

Initializing an array using values: We may initialize an array using particular values by providing a list of items that are separated by commas and are enclosed by curly braces. The number of elements we specify automatically determines the size of the array.

int numbers[] = {1, 2, 3, 4, 5};

In the above code, the values 1, 2, 3, 4, and 5 are used to initialize the integer array- numbers. The number of given elements (5 in this example) determines the array’s size.

Initializing an array with size and values: We may also initialize an array with its size explicitly specified and with values placed inside square brackets []. This enables us to initialize some items while maintaining the default values (often 0) for others.

int num[5] = {1, 2, 5};

The above code declares an integer array num of size 5. The first three items are given the values 1, 2, and 5, respectively, and the other elements are, by default, initialized to 0 because their values have not been provided.

The size of an array, as stated for it inside square brackets, cannot be more than the number of values between braces. When the array size is left out, a small enough array is generated to hold the array elements.

Initializing an array at the runtime: Array elements can also be provided at runtime, like:

int num[5];

for(i=0;i<5;i++)

scanf("%d", & num[i]);

The following declaration sets up an array type of double with the specified values.

double b [5] = {1000.0, 2.0, 3.4, 7.0, 50.0};

As discussed already, each element of an array is identified by a unique number called an index, which is assigned implicitly to the elements of an array, as shown in Figure 5.1:

Figure 5.1: Memory map of the array

Bounds and array size

An array’s size specifies how many elements it can hold. It must be a variable or a constant with a positive integer value that does not change during the program. Since C does not conduct bound checking on array accesses, accessing elements outside the array’s bounds may result in unpredictable behavior and even crashes. To prevent such mistakes, developers should use caution.

Base address

Whenever a variable is declared in C, the operating system assigns a unique address in RAM. Similarly, each cell of an array is assigned a unique address. The address of the first cell of an array is called the base address of the array (Figure 5.2):

Figure 5.2: Base address

In the above orientation address of a variable, a [0] is 111 (which would be the base address of the array; the next variable, a [1], would be allocated at address 113, as the int data type takes 2 bytes:

Note: Individual variables in the array would be:

2 bytes apart if it is an int array.

4 bytes apart if the array is declared as float.

1 byte apart if the array is declared as char.

Address of an array cell = Base address + (size of the data type of array * index of the cell)

For example:

Address of a [0] = 111 + (2 * 0) = 111

Address of a [1] = 111 + (2 * 1) = 113

Array manipulation

C offers several functions and methods for successfully manipulating arrays. These comprise:

Traversing arrays: We can traverse or navigate array elements using loops.

Sorting arrays: We may organize the items of an array in ascending or descending order by using sorting algorithms like the bubble sort, insertion sort, or quicksort (discussed later).

Searching arrays: Algorithms like linear or binary search can be used to search for elements inside an array.

Modifying arrays: Modifying involves adding, deleting, or changing elements of an array.

Accessing array elements

An array element is accessed by using an index with the array name. This is done by specifying an index of the intended element inside square brackets after mentioning the array’s name.

For example:

double s = data [9];

The above statement will assign to the variable s, the value of the 10th element (which is stored at index 9) from the array data:

int main ()

{

int a [10], I; //a is an array of 10 integers

printf ("Input array elements\n");

for (i = 0; i < 10; i++)

{

scanf ("%d", &a[i]);

}

printf ("Here are the array elements\n");

for (i = 0; i < 10; i++)

{

printf ""%d\"", a[i]); // output each array elemen''s value

}

return (0);

}

Note: All the array programs need to scan and print array elements, so instead of writing the code repeatedly, we will declare a function getarr (), which will input array elements, and a printarr () function, which will print elements of an array.

Modifying array elements

If the need arises, we can modify the values of the array elements, too. We will simply mention the index of the location at which the change is desired, like:

Array_name [index] = new value

For example, in the above array, if we wish to change the value of the third element to 12, we will do it as:

data [3] = 12;

We will use these 2 functions in all the examples discussed in the upcoming sections.

Passing arrays to functions

When calling functions using arrays in C, the array must be declared as a parameter in the function’s declaration. A function may access and work with the original array since just the initial address of an array is copied when it is provided to it. Here are some crucial considerations to keep in mind when sending arrays to functions in C:

Parameter passing: The array argument in the function declaration must be specified with the correct type and then a set of empty square brackets to indicate that it is an array. For instance, to provide a function in an integer array called “a”, the parameter declaration would be int a [].

Array size: The array size is not automatically passed along with it, in C, by default. It is imperative to provide the function with a further parameter specifying the array’s size as a result. The int type argument here denotes the array’s element count and can be of any number of items.

Modifying array elements: Any changes made to the elements of an array inside a function will have an impact on the original array when the array is supplied to the function. This is so that the function may work on the memory location of the initial array. The array’s modifications will, therefore, be visible both inside and outside of the method.

Example:

//Function to input array elements

void getarr (int a [], int n) {

int i;

printf ("Input array elements\n");

for (i = 0; i < n; i++)

{

scanf ("%d", & a [i]);

}

}

int main {

int main () {

int a [10], n;

printf ("How many elements to store\n");

scanf ("%d", &n);

// when passing arrays to a function we just pass the array name

getarr (a, n);

return (0);

}

Similarly, we can use a function to print array elements:

void printarr (int a [], int n) {

int i;

printf ("Here are the array elements\n");

for (i = 0; i < n; i++)

{

printf ("%d\t", a[i]);

}

}

Passing arrays to functions enables us to modularize our code and perform operations on arrays without having to rewrite the same code at multiple places.

Some array-based programs

The following section discusses some array-based programs with the help of examples.

Calculate the sum of an array of elements:

#include <stdio.h>

void sumarr (int a [], int n)

{

int i, s=0;

for (i = 0; i < n; i++)

{

s = s + a[i];

}

printf ("Sum of array elements is %d", s);

}

int main () {

int a [10], n;

printf ("How many elements to store\n");

scanf ("%d", &n);

getarr (a, n);

printarr (a, n);

return (0);

}

Greatest element in an array:

#include <stdio.h>

void greatest (int a [], int n)

{

int i, g;

g = a[0];

// assume a[0] to be the greatest element and

find if there is a greater value across the array

for (i = 1; i < n; i++)

{

if (a[i] > g)

{

g = a[i];

}

}

printf ("Greatest element is %d", g);

}

int main () {

int a [10], n;

printf ("How many elements to store\n");

scanf ("%d", &n);

getarr (a, n);

printarr (a, n);

return (0);

}

Search an element in the array - linear search:

#include <stdio.h>

int search (int a[], int n, int key) {

int i;

for (i = 0; i < 10; i++)

{

if (a[i] == key)

{

return (1); // exit if element found with 1 (TRUE)

}

} //end of for loop

return (0);

}

int main () {

int a [15], i, s, n, r;

printf ("How many elements to store\n");

scanf ("%d", &n);

getarr (a, n);

printarr (a, n);

printf ("Input element to be found\n");

scanf ("%d", &s);

r = search (a, n, s);

if (r==1)

printf ("Element found\n");

else

printf ("Element not found\n");

return (0);

}

Sort an array - selection sort:

To arrange array elements in ascending or descending order. Suppose we have an array:

3

2

1

9

4

7

0

8

5

6

0

1

2

3

4

5

6

7

8

9

When i=0

3 > 2 (True), swapping would take place:

2

3

1

9

4

7

0

8

5

6

2 > 1 (True) swap them:

1

3

2

9

4

7

0

8

5

6

1 > 9 False

1 > 4 False

1 > 7 False

It would result in no swapping taking place, and the array would remain unchanged.

1 > 0 True, swapping would take place:

0

3

2

9

4

7

1

8

5

6

0 > 8 False

0 > 5 False

0 > 6 False

It would result in no swapping taking place, array would remain unchanged:

0

3

2

9

4

7

1

8

5

6

So, at the end of pass 1 (when i=0), we would have the smallest element at a [0].

Now repeat the same process. Next (when i=1), we must compare a [1] with subsequent values, and at the end of the comparison process, the next smallest element, that is, 1, should move to a [1]. Let us implement the method through C code now:

#include <stdio.h>

void Selectionsort (int a[], int n)

{

int i,j,t;

for (i = 0; i < n-1; i++)

{

for (j = i+1; j < n; j++)

{

if (a[i] > a[j])

{

t = a[i]; //save value of a[i] to t

a[i] = a[j]; // swap a[i] with a[j]

a[j] = t; // assign value of t to a[j]

}

}

}

}

int main () {

int a [10], i, j, n, t;

printf ("How many elements to store\n");

scanf ("%d", &n);

getarr (a, n);

printarr (a, n);

Selectionsort (a, n); // calling selection sort function

printarr (a, n);

return (0);

}

Sort an array - bubble sort:

To arrange array elements in ascending or descending order. Suppose we have an array:

13

2

10

9

4

17

0

8

5

6

0

1

2

3

4

5

6

7

8

9

In the case of bubble sort, subsequent elements are compared (and swapped), resulting in relocating the greatest element to the end of the array at each pass, that is, at the end of the pass 1 (When i=0), 17 should move to a [9], in the above example. Similarly, at the end of the next pass (i=1),13 should move to a [8].

When i=0

13 > 2 (True), swapping would take place:

2

13

10

9

4

17

0

8

5

6

13 > 10 (True) swap them:

2

10

13

9

4

17

0

8

5

6

13 > 9 (True), swap them:

2

10

9

13

4

17

0

8

5

6

13 > 4 (True), swap them:

2

10

9

4

13

17

0

8

5

6

13 > 17 (False), no change in the array state

17 > 0 (True), swap them:

2

10

9

4

13

0

17

8

5

6

17 > 8 (True), swap them:

2

10

9

4

13

0

8

17

5

6

17 > 5 (True), swap them:

2

10

9

4

13

0

8

5

17

6

17 > 6 (True), swap them:

2

10

9

4

13

0

8

5

6

17

At the end of the pass 1, (when i=0), we would have the greatest element at a [n-1], where ‘n’ is supposedly the number of elements in an array (n=10) in the above example.

Now repeat the same process. Next (when i=1), we must compare a [0] with subsequent values up to (n-i-1)th element during each pass. At the end of the 2nd pass, the next greatest element, which is 13, should move a [n-i-1]. Let us implement the method through C code now:

#include <stdio.h>

void Bubblesort (int a [], int n)

{

int i, j, t;

for (i = 0; i < n; i++)

{

for (j = 0; j < n - i; j++)

{

if (a[j] > a[j+1])

{

t = a[j];

a[j] = a [j + 1]; // swap a[j] with a[j+1]

a [j + 1] = t;

}

}

}

}

int main () {

int a [10], n;

printf ("How many elements to store\n");

scanf ("%d", &n);

getarr (a, n);

printarr (a, n);

Bubblesort (a, n)

printarr (a, n);

return (0);

}

Merge sort - two arrays:

a1

1

2

5

6

9

11

22

24

30

35

No. of elements (m) = 10th

Counter would be i:

a2

3

4

7

8

10

No. of elements (n) = 5,

The counter would be j

a3

No. of elements (m+n) = 15

The counter would be k

Prerequisite: Both the arrays to be merged should be sorted.

When we compare one element at a time from a1 and a2, whichever element is smaller (in case of ascending sort), we add it to a3. To start with, i = 0, j = 0, and k = 0. So a1[i], that is, 1 would be compared to a2[j], that is, 3. Since 1 is less than 3, a1[i] would be added to a3, and counters I and k would be incremented. Next, a1[1], that is, 2 would be compared with a2[0].

Again, an element of a1 would be added to a3, and counters i and k would be incremented. The process would go on till one of the arrays has no elements remaining for comparison. After the comparison process is over, the rest of the elements of the arrays, which still have elements in them, would be copied to a3:

#include<stdio.h>

void printarr (int a [], int n) {

int i;

printf ("\nHere are the array elements\n");

for (i = 0; i < n; i++)

{

printf ("%d\t", a[i]);

}

}

void getarr (int a [], int n) { //Function to input array elements

int i;

printf ("Input array of sorted elements\n");

for (i = 0; i < n; i++)

{

scanf ("%d", & a [i]);

}

}

void merge (int a1 [], int a2 [], int a3 [], int m, int n)

{

int i, j, k;

i =j = k = 0;

while (i<m && j<n)

{ //while there are elements in both the arrays

if (a1[i] < a2[j])

a3[k++] = a1[i++];

else

a3[k++] = a2[j++];

} // loop will end once elements of either array are exhausted.

while (i<m)

{ //if a1 has elements remaining, add them to a3

a3[k++] = a1[i++];

}

while (j<n)

{ //if a2 has elements remaining, add them to a3

a3[k++] = a2[j++];

}

}

int main () {

int a1[10], a2[10], a3[20], n, m;

printf ("How many elements to store in a1\n");

scanf ("%d", &m);

getarr (a1, m);

printarr (a1, m);

printf ("\nHow many elements to store in a2\n");

scanf ("%d", &n);

getarr (a2, n);

printarr (a2, n);

merge (a1, a2, a3, m, n);

printarr (a3, m+n);

return (0);

}

Merge sort-single array:

Whenever sorting an array using the merge sort algorithm, we divide the array into subarrays until the size of each subarray becomes 1.

An array or a subarray of size 1 is sorted, and we use the merge function to merge these subarrays into a set of sorted subarrays.

And finally, we have a fully sorted array.

#include<stdio.h>

void printarr (int a [], int n) {

int i;

printf ("\nHere are the array elements\n");

for (i = 0; i < n; i++)

{

printf ("%d\t", a[i]);

}

}

void getarr (int a [], int n) { //Function to input array elements

int i;

printf ("Input array elements\n");

for (i = 0; i < n; i++)

{

scanf ("%d", & a [i]);

}

}

void merge (int a [], int mid, int low, int high)

{

int i, j, k, t [100];

i = low; j = mid + 1; k = low;

while (i <= mid && j <= high)

{

if (a [i] < a [j])

{

t[k] = a [i];

i++; k++;

}

else

{

t[k] = a [j];

j++;

k++;

}

}

while (i <= mid)

{

t [k] = a [i];

k++; i++;

}

while (j <= high)

{

t[k] = a [j];

k++; j++;

}

for (i = low; i <= high; i++)

{

a [i] = t [i];

}

}

void mergeSort (int a [], int low, int high)

{

int mid;

if(low<high)

{

mid = (low + high) /2;

mergeSort (a, low, mid);

mergeSort (a, mid+1, high);

merge (a, mid, low, high);

}

}

int main () {

int a[10], n;

printf ("How many elements to store in array\n");

scanf ("%d", &n);

getarr (a, n);

printarr (a, n);

mergeSort (a, 0,n-1);

printarr (a, n);

return (0);

}

Insertion of an element in array:

Figure 5.3: Insertion of a element into array

Suppose we wish to insert 5 where 6 is stored (at index 4). We need to make room for the element by moving elements of the array starting from the last i-e 10 would go to the location with index 9, 9 would go to index 8, and so on till we move 6 to index 5. Now we can insert 5 at index 4, like:

a [4] = 5;

It is pertinent to mention that the value at index 4 would be overwritten, but since we would have already moved the ‘to be’ overwritten element to the higher indexed location, we need not worry:

#include<stdio.h>

void printarr (int a [], int n) {

int i;

printf ("\nHere are the array elements\n");

for (i = 0; i < n; i++)

{

printf ("%d\t", a[i]);

}

}

void getarr (int a [], int n) { //Function to input array elements

int i;

printf ("Input array elements\n");

for (i = 0; i < n; i++)

{

scanf ("%d", & a [i]);

}

}

void ins (int a [], int n, int e, int p) {

int i;

for (i = n; i >p; i--)

{

a[i]=a[i-1]; // Make room for element to be inserted

}

a[i] = e; // store the desired element

}

int main () {

int a [20], i, e, p, n;

printf ("How many elements to store\n");

scanf ("%d", &n);

getarr (a, n);

printarr (a, n);

printf ("\nInput element to be inserted\n");

scanf ("%d", &e);

printf ("\nInput index at which element is to be inserted\n");

scanf ("%d", &p);

ins (a, n, e, p);

printarr (a, n+1);

return (0);

}

Delete an element from array:

Figure 5.4: Deletion of a element from array

For deleting an element that already exists in an array, it is searched, and (if it exists) it is deleted. Deletion is done by shifting values to lower indices one after the other, as shown in the figure. The user inputs the position of the element that is to be deleted (which should be within the size of the array). The process stops once the element to be deleted is overwritten. For example, in the above array, assume we must delete 4 (at index 3). We would move 6 to index 3 (4 is now overwritten), 7 to index 4, 8 to index 5, and so on:

#include<stdio.h>

void printarr (int a [], int n) {

int i;

printf ("\nHere are the array elements\n");

for (i = 0; i < n; i++)

{

printf ("%d\t", a[i]);

}

}

void getarr (int a [], int n) { //Function to input array elements

int i;

printf ("Input array elements\n");

for (i = 0; i < n; i++)

{

scanf ("%d", & a [i]);

}

}

void del (int a [], int n, int p)

{

int i;

for (i = p; i < n-1; i++)

{

a[i]=a[i+1]; // shift elements to the left

}

}

int main () {

int a [20], i, e, p, n;

printf ("How many elements to store\n");

scanf ("%d", &n);

getarr (a, n);

printarr (a, n);

printf ("\nInput index at which element is to be deleted\n");

scanf ("%d", &p);

del(a, n, p);

printarr (a, n-1);

return (0);

}

Character arrays - strings

In C, strings are represented as character arrays terminated by a NULL character (‘0’). In contrast to other programming languages, C lacks a built-in string data type. Instead, with special rules and procedures for manipulating strings, strings are treated as character arrays.

To prevent buffer overflows and other memory-related problems, it is crucial to handle strings properly in C. Always keep a check on array sizes and make sure the strings defined are big enough to store the characters and that strings are appropriately null-terminated.

The following declaration and initialization create a string consisting of the word "ZAIN". To hold the null character at the end of the array, the size of the character array containing the string is one more than the number of characters in the word:

char nm [6] = {'Z', 'A', 'I', 'N', '\0'};

Z

A

I

N

\0

If you follow the rule of array initialization, then you can write the above statement as follows:

char nm [] = "ZAIN";

Following is the memory presentation of the above-defined string in C/C++:

Figure 5.5: Memory presentation of a string

We do not have to worry about putting a NULL character at the end of a string constant. The C compiler automatically places the ‘\0’ at the end of the string when it initializes it (or when the user presses the spacebar or returns after inputting all the characters). Let us try to print the above-mentioned string:

#include <stdio.h>

int main ()

{

char nm [6] = {'Z', 'A', 'I', 'N','\0'};

printf ("My name is %s", nm);

}

Output:

My name is ZAIN

Accessing characters in a string

Zero-based indexing can be used to access specific characters inside a string.

For instance, the expression:

char z = nm [0];

It returns the string’s first character and assigns it to z (which is char-type). Characters in a string can be changed by giving a different value to a particular index.

For instance:

nm [0] = 'A';

It replaces the first character of string nm with "A".

Iterate through characters of a string

In C, we may use a loop to iterate through a string’s characters. Strings are represented as character arrays in C, and their index is used to access them.

#include <stdio.h>

int main ()

{

char nm [] = "ZAIN";

int i = 0;

printf ("What's your name\n");

scanf ("%s", &nm)

// starting from 0 till NULL character is reached

while (nm[i]!= '\0')

{

printf ("%c ", nm [i]); // we are printing one character at a time (%c)

i++;

}

return 0;

}

String input and output

In C programming, printf() and scanf() are generally used for string input and output.

printf () function can be called to print a string and uses the format specifier '%s' to print it. To print the string nm, for instance, we may write:

printf ("%s", nm);

The scanf() function can be used in conjunction with the format specifier %s to read a string from the user.

For instance:

scanf ("%s", nm);

It scans a string from user input and saves it in nm. When reading strings, take care to prevent buffer overflow problems.

Note: Why is the ‘&’ sign not used when scanning strings in C?

The C language uses pointers to an array’s first element to express an array. An array is casted into a pointer to its first element when it is used as a parameter in a function or when scanf () is called on it. Since the array’s name is also a memory location, it is not essential to use the & (address-of) operator.

Scan and print string:

#include <stdio.h>

int main ()

{

char nm [10];

printf ("What's your name\n");

scanf ("%s", nm);

printf ("Hello %s", nm);

return (0);

}

Print string character by character:

#include <stdio.h>

int main ()

{

char nm [10];

int i = 0;

printf("Whats your name\n");

scanf ("%s", &nm);

// starting from 0 till NULL character is reached

printf("Hello ");

while (nm[i]!= '\0')

{

printf ("%c", nm[i]); // %c because we print one character at a time

i++;

}

return 0;

}

Read a line of text

The input function scanf () accepts a string as input from the terminal and uses the %s format specifier. But the issue is that it stops when it comes across a white space. We generally avoid using the gets () function, which can read any number of strings, including white spaces.

The string is a character array that ends with a special character - the NULL character (“0”). To read a line of a string, we use the fgets() method. Additionally, the string may be displayed on the console using puts().

fgets () and puts ():

#include <stdio.h>

int main () {

char nm [30];

printf ("Whats your name\n");

fgets (nm, sizeof(nm), stdin);

printf ("Hello ");

puts(nm); // display string

return (0);

}

The sizeof(nm) results in 30. Hence, we can take a maximum of 30 characters as input, which is the size of the nm string. To print the string, we have used puts (s).

String manipulation

Programming’s core concept of string manipulation encompasses a variety of actions on strings, including concatenation, substring extraction, searching, replacing, and parsing. In this article, we will examine the value of manipulating strings and go over some typical methods and functions that are employed in doing so across a range of programming languages.

Programmers can effectively handle, alter, and analyze textual data thanks to the essential skill of string manipulation. Developers may create powerful systems that can handle challenging string-related tasks by learning how to use the numerous methods and functions for string manipulation that are available in programming languages.

In many applications, including text processing, data analysis, input validation, and file handling, string manipulation is essential. Programmers may efficiently alter data, extract useful information, and carry out intricate tasks by manipulating strings. Let us explore some of the main string manipulation methods.

String concatenation

Concatenation is the process of joining two or more strings together to form a single string. Concatenation operators and functions are typically available for this purpose in most programming languages. For instance, the strcat() function in C is used to concatenate strings, but the + operator or the join() technique might be used in Python:

#include <stdio.h>

#include <string.h>

int main ()

{

char snm [20];

char nm [20];

printf ("What's your name\n");

scanf ("%s", nm);

strcat (nm, snm);

printf ("What's your surname\n");

scanf ("%s", snm);

printf ("Full Name is %s\n", nm);

return (0);

}

Substring extraction

Substring extraction is the process of removing a section of a string. The beginning index and the substring’s length must be specified to do this. Substring extraction tools and techniques are available in many computer languages. For instance, you may extract a substring from a given text in Java using the substring() function and the specified indices. In C, we can write a user-defined function for substring extraction as follows:

#include <stdio.h>

#include <string.h>

int main () {

char str[20], substr [20];

int i, j, start, end;

printf ("Input a string\n");

scanf ("%s", str);

printf ("Input start and end index\n");

scanf ("%d%d", &start, &end);

j=start;

i=0;

if (start>=0 && end<strlen (str))

{

while (i<=end)

{

substr[i] = str[j];

i++;

j++;

}

}

substr[i]=NULL;

// use function, substr(str, substr, start, end), to carry out the task

printf ("Substring is %s", substr);

return (0);

}

Output:

Input a string

saleem

input start and end index

1

4

Substring is aleem

Searching and replacing

String manipulation tasks include looking for a certain pattern or substring inside a string. The pattern or substring can be located and then changed to another value. For finding and replacing strings, programming languages include functions or techniques. The use of regular expressions for more intricate pattern matching and substitution is also very common.

The predefined function strstr() in C is one of the string manipulation functions used to find a string within another string. The function is defined in the header file string.h. This function looks for the first instance of the substring (string to be found) in the parent string. It takes two string-type arguments as arguments:

char *strstr (char *substr, char *str);

The functions return a pointer to the first character of substr found in str, a NULL pointer is returned if substr is not present in str and the string substr points to an empty string.

For example:

#include <string.h>

#include <stdio.h>

int main ()

{

char str [20], substr [20];

char* found;

printf ("Input a string\n");

scanf ("%s", str);

printf ("Input a sub-string\n");

scanf ("%s", substr);

found = strstr (str, substr);

if (found!=NULL)

{

printf ("String found\n");

}

else

{

printf ("String not found\n");

}

return 0;

}

Output:

Input a string

saleem

Input a sub-string

lee

String found

Application

It is used to substitute one string for another. In the example given below, we will use the strstr() method to find the substr in string s, and then we replace that word with another string newstr.

#include <string.h>

#include <stdio.h>

int main () {

char s []="I am a good Boy";

char substr []="Boy";

char newstr[]="Girl";

char* found;

found = strstr(s, substr);

if (found!=NULL)

{

strcpy (found, newstr);

printf ("%s", s);

}

else

{

printf ("String not found\n");

}

return 0;

}

Output:

I am a good Girl

Parsing and tokenization

According to a predetermined delimiter or pattern, parsing entails dividing the text into smaller chunks or tokens. This is beneficial for processing structured data, such as when parsing a CSV file or getting data from a URL. Programming languages offer functions, libraries, or built-in techniques that make string parsing and tokenization easier. Examples include the Python split() method and the Java StringTokenizer class.

Case conversion

Another frequent string manipulation action is to change the case of the characters within a string. A string may be changed to title case, lowercase, or uppercase to accomplish this. The toUpperCase() and toLowerCase() methods in Java or the upper() and lower() procedures in Python are just a few examples of built-in functions or methods that programming languages frequently offer to accomplish case conversion:

#include <string.h>

#include <stdio.h>

int main () {

char s []="SALEEM";

int i=0;

while (s[i]!=NULL)

{

s[i]=s[i] + 32; // -32 when converting uppercase char to lower case char

i++;

}

s[i]=NULL;

printf ("%s", s);

return 0;

}

Output:

Saleem

String formatting

The insertion of variables or values into a predetermined string template is possible with string formatting. For creating intricate strings or producing dynamic output, this is helpful. There are several methods for formatting strings available in programming languages, including the printf () function in C, string interpolation in languages like Python or JavaScript, and the format () method in Python.

String length and manipulation functions

Many computer languages provide built-in functions or techniques for calculating a string’s length, determining if a string begins or ends with a certain substring, and trimming leading and trailing whitespace. Various string manipulations and validations may be done with the help of these functions.

In C programming, there are several built-in string functions available in the standard library `<string.h>` that provide convenient ways to manipulate strings.

Here are some commonly used inbuilt string functions:

strlen (): This function returns the length of a string.

Syntax:

int strlen (char *);

Example:

n = strlen (char *s);

Where:

n: is (an int value) will store the value returned by the strlen() function.

s: string, whose length is to be found.

Program example:

#include<stdio.h>

#include <string.h>

int main ()

{

int l;

char nm [] ="Zain";

l= strlen (nm);

printf ("Length of your name is %d", l);

return (0);

}

strcpy (): This function is used to copy one string to another string. It takes 2 string type arguments, the first string being the destination string (to which string is to be copied), while the second string being the source string (which is to be copied).

Syntax:

strcpy (char *, char *);

Example:

strcpy (char *dest, char *src);

where:

src: a string to be copied.

dest: a string to which str is to be copied.

Program example:

#include<stdio.h>

#include <string.h>

int main ()

{

int l;

char nm [] ="Zain";

char x[]="";

strcpy (x, nm);

printf ("Copied string = %s", x);

return (0);

}

strcat (): String concatenation is accomplished using the C library function strcat. The term means string concatenate. Two null-terminated strings are provided as input, and the second string is appended (concatenated) to the end of the first string.

Syntax:

char *strcat(char *, char *);

Example:

char *strcat(char *dest, char *source);

Where:

dest: This is a reference to the string that will be used to receive the result of the concatenation.

source: The string that will be appended to the end of dest string is represented by this reference to the source string.

The strcat function returns a reference to the changed destination string after making in-place modifications to the source string. It makes sure the string at the end is null-terminated.

Program example:

#include<stdio.h>

#include <string.h>

int main ()

{

int l;

char nm[] = "Zain";

char x[] = "Mir ";

strcat (x, nm);

printf ("Appended string = %s", x);

return (0);

}

Output:

Appended string = Mir Zain

strcmp (): To compare two strings in C, we use the function strcmp () using alphabetical order of the dictionaries (lexicographic order). It returns an integer indicating their relative ordering.

Syntax:

int strcmp(char *, char *);

Example:

int strcmp(char *str1, char *str2);

where:str1 and str2 represent strings to compare.

The function returns:

Negative integer (< 0), if str1 is lexicographically less than str2.

Positive integer (> 0), if str1 is lexicographically greater than str2.

If str1 is equal to str2, it returns 0.

Program example:

#include<stdio.h>

#include <string.h>

int main ()

{

int l;

char s1 [];

char s2 [];

printf ("Input 1st string\n");

scanf ("%s", s1);

printf ("Input 2nd String2\n");

scanf ("%s", s2);

if (strcmp(s1,s2)==0)

{

printf ("The two strings are same");

}

else

{

printf ("The two strings are different");

}

return (0);

}

Output:

Input 1st string

Zain

Input 2nd String2

Zain

The two strings are same

strchr(): To locate a character’s first appearance in a string, we can use C’s strchr function. It looks for a certain character within a string,, and then either returns a reference to the character’s first occurrence or a null pointer if the character cannot be found.

Syntax:

char *strchr(char *, int );

Example:

char *strchr(char *str, char ch);

Where:

str: is the string in which a character is to be found.

ch: is the character to be found in str.

Program example:

#include <stdio.h>

#include <string.h>

int main ()

{

char s [] = "Beautiful";

char *loc = strchr (s, 'u');

if (loc == NULL)

{

printf ("Character not found");

}

else

{

printf ("Character found at position: %d", loc - s);

}

return (0);

}

Output:

Character found at position: 3

strncpy(): A string’s characters can be copied from one string to another using the standard C library function strncpy. This acronym means string copy with length. The function limits the number of characters that may be duplicated, allowing us to transfer only a piece of one string into another.

Syntax:

char *strncpy(char *, char *, size_t n);

Example:

char *strncpy(char *dest, char *source, size_t n);

where:

dest: Pointer to the (destination) string where characters will be copied.

source: Pointer to the (source) string from which characters will be copied.

n: The maximum number of characters to be copied from the source.

size_t: an unsigned integral type.

Program example:

#include <stdio.h>

#include <string.h>

int main ()

{

char s[] = "Information!";

char d []="";

int c = 3; // No. of characters to be copied

strncpy (d, s, c);

d[c] = '\0'; // Put a NULL character to terminate the string

printf ("Destination string: %s", d);

return (0);

}

Output:

Destination string: Inf

strtok(): Using the specified delimiter, the strtok() function tokenizes a given string. The function divides the string into smaller tokens and then returns each token individually:

Syntax:

char *strtok(char *, char *);

Example:

char *strtok(char *str, char *delim);

where:

str: Pointer to the string to tokenize.

delim: delimiter character(s).

Program example:

#include <stdio.h>

#include <string.h>

int main ()

{

char s [] = "http://www.jkims.in/main/index.aspx";

char d [] = "/";

char *token = strtok (s, d);

while (token!= NULL)

{

printf ("%s\n", token);

token = strtok (NULL, d);

}

return(0);

}

Output:

http:

www.jkims.in

main

index.aspx

As programmers, we mostly use just a handful of string functions. Each function offers a practical approach to carrying out typical string manipulation tasks while serving a particular purpose. For more comprehensive details on each function, including its parameters, return values, and use rules, it is imperative to read the documentation or references.

Use inbuilt functions of strings:

#include <stdio.h>

#include <string.h>

int main () {

char s1[10], s2[10];

printf ("Input String 1\n");

scanf ("%s", &s1);

n = strlen (s1);

printf ("Length of s1 is %d\n", n);

strcpy (s2, s1);

printf ("String 2 is %s\n", s2);

n = strcmp (s1, s2);

if (n == 0)

{

printf ("Strings are same\n"):

}

else

{

printf ("Strings are different\n"):

}

printf ("Reverse of string 1 is %s", strrev (s1)); // print reverse of s1

return(0);

}

The following are the user-defined functions for the inbuilt string functions:

User-defined function for strlen ():

#include <stdio.h>

#include <string.h>

int length (char s [10]) {

int i = 0;

while (s[i]! = NULL)

{

i++;

}

return (i);

}

int main () {

char s[10]; int n;

printf ("Input string \n");

scanf ("%s", &s);

n = length (s);

printf ("length of s is %d\n", n);

return (0);

}

User-defined function for strcpy ():

#include <stdio.h>

#include <string.h>

void copy (char s2[10], char s1[10])

{

int i = 0;

while (s1[i]! = NULL)

{

s2[i] = s1[i];

i++;

}

}

int main () {

char s1[10], s2[10];

int n;

printf ("Input string 1 \n");

scanf ("%s", &s1);

copy (s2, s1);

printf ("String 2 is %s\n", s2);

return (0);

}

User defined function for strcmp ():

#include <stdio.h>

#include <string.h>

int compare (char s1[10], char s2[10]) {

int i = 0;

if (length (s1) != length (s2))

{

return (1);

}

else

{

while (s1[i] != NULL)

{

if (s1[i] != s2[i];

return (1);

i++;

}

}

return (0);

}

int main () {

char s1[10], s2[10];

int n;

printf ("Input String 1 And String 2 \n");

scanf ("%s%s", &s1, &s2);

n = compare (s1, s2);

if (n == 0)

printf ("Strings are same\n"):

else

printf ("Strings are different\n"):

return (0);

}

User defined function for strrev ():

void reverse (char s [10]) {

int n, i, j=0;

char temp [10];

n=length (s) -1;

for (i= n; i > 0; i++)

{

temp[j++] = s[i];

} //Temporary string

temp[j] = NULL; //put NULL character at last to complete the string

copy (s, temp); // copy the reversed sing to the original string - s

}

int main () {

char s [10];

int n;

printf ("Input String \n");

scanf ("%s", &s);

reverse (s);

printf ("Reversed strings is %s", s);

return (0);

}

Palindrome is a string, which, when read in both forward and backward ways, is the same. (Like radar, madam, pop, lol, and so on), here is an example for the same:#include <stdio.h>

#include <string.h>

int main () {

char s [20];

int i, l;

int flag = 0;

printf ("Input a string:");

scanf ("%s", s);

l = strlen (s);

for (i=0; i < l; i++)

{

if(s[i] != s [l-i-1])

{

flag = 1;

break;

}

}

if (flag)

{

printf ("%s is not a palindrome", s);

}

else

{

printf ("%s is a palindrome", s);

}

return 0;

}

To find the occurrence of characters in a string:

#include <stdio.h>

#include <string.h>

int main ()

{

char s[100],c;

int i, freq [26] = {0};

printf ("Input a lowercase string\n");

scanf ("%s", s);

for (i = 0; s[i]!= '\0'; i++)

{ // step 2: traversing the string

freq [s[i]-'a'] += 1;

}

printf ("The frequency of characters is -\n");

for (i = 0; i < 26; i++)

{

if (freq[i]!= 0)

{

// obtain characters by adding the ASCII value of 'a'

c = 'a' + i; //for example to obtain c, we use 'a' + 2

printf ("\t%c = %d\n", c, freq[i]);

}

}

return (0);

}

Input a lowercase string

programming is so beautiful

The frequency of characters is -

a = 1

g = 2

i = 1

m = 2

n = 1

o = 1

p = 1

r = 2

2-D strings

A 2D string in C is effectively a collection of character arrays, each of which is a string (null-terminated character array). It is frequently used to represent a group of strings or a data structure that resembles a table and has a string in each row. Use these steps to declare and operate on 2D strings in C:

Declaration and initialization of 2-D strings

You must specify an array of character arrays (strings) in order to declare and initialize a 2D string. Based on the maximum number of strings and the maximum length of each string in the 2D array, the array’s size should be determined. Using string assignment, you may initialize the array either at the moment of declaration or at a later time.

For example:

#include <stdio.h>

int main ()

{

// Declaration and initialization of a 2D string array

char str [10][30] = {

"Welcome,",

"to!",

"C Programming"

};

// Printing the strings

for (int i = 0; i < 10; i++)

{

printf ("%s\n", str [i]);

return (0);

}

Reading input into a 2D string

You may use the scanf or fgets functions to read strings from the user and store them in a 2D string array.

For example:

#include <stdio.h>

#include <string.h>

int main ()

{

char str [10]30];

// Reading strings from the user

for (int i = 0; i < 10; i++)

{

printf ("Input string %d: ", i + 1);

fgets(str[i], 30, stdin);

// Remove trailing newline character

str[i][strcspn(str[i], "\n")] = '\0';

}

for (int i = 0; i < 30; i++)

{

printf ("String %d: %s\n", i + 1, str[i]);

}

return (0);

}

Accessing characters in a 2-D string

We may use the indexing notation to access individual characters in a 2-D string. The first index stands for the row (a string), whereas the second index stands for the column (a particular character inside the string).

For example:

#include <stdio.h>

int main ()

{

char str [3][5] = {

"Apricot",

"Banana",

"Mango"

};

// Accessing individual characters

printf ("Second character of the first Fruit: %c\n", str [0][1]); // P

printf ("3rd character of second string: %c\n", str [1][2]); // n

printf ("Fifth character of the third string: %c\n", str [2][4]); // o

return (0);

}

To prevent buffer overflows and other memory-related problems, keep in mind that the size of the 2D string array should be carefully selected depending on the unique requirements of your software. Additionally, bear in mind that the C language lacks built-in bounds checking for arrays, so it is crucial to make sure you respect the boundaries while accessing and changing items of the 2D string array.

In C, a 2D string (or array of strings) can be represented using a two-dimensional array of characters. For example, the following code creates a 2D array of size 3x4 and assigns some values to it:

char arrstr[3][4] = {

{'a','b','c','d'},

{'e','f','g','h'},

{'i','j','k','l'}

};

Alternatively, we can use a pointer to create a 2D array of strings in the heap, which is more efficient in terms of memory usage. Here is an example of how you can create a 2D array of strings using a pointer to pointer:

#include <stdio.h>

#include <stdlib.h>

int main () {

char **twoD;

int rows = 3, cols = 4; 

// Allocate memory for the rows 

twoD= (char**) malloc (rows * sizeof (char *)); 

// Allocate memory for the columns 

for (int i = 0; i < rows; i++)

twoD[i] = (char *) malloc (cols * sizeof(char));

// Assign values to the array 

twoD[0] = "abcd"; twoD[1] = "efgh"; twoD[2] = "ijkl";

return 0;

}

Note: We should also free the memory allocated by malloc once you are done using it.

Matrices

A multi-dimensional array in C is a set of elements stored in contiguous locations identified by more than one dimension. It permits the storing and handling of data in a tabular or matrix-like structure. Multi-dimensional arrays, such as two-dimensional (2D) arrays, three-dimensional (3D) arrays, and others, can have two or more dimensions.

The two-dimensional array is the most basic type of multidimensional array. In essence, a two-dimensional array is a collection of one-dimensional arrays. As already discussed, arrays are a set of homogenous values stored in contiguous memory locations. Multi-dimensional arrays are not supported by default in C, although arrays of arrays can be used to construct them.

For example:

int mks [2][4];

It would create an array of (2) arrays or a two-dimensional (2-D) array. The array can hold eight elements. You can treat the array as a table with two rows, and each row has four columns. A 2-D array is referred to as a matrix in C.

Column 0

Column 1

Column 2

Column 3

Row 0

Row 1

Table 5.1: Memory map of a 2-D Array

Declaring 2-D array (Matrix)

Dimensional arrays can be declared in C as follows:

datatype arrayName[rows][columns];

So, we can declare the 2-D array as shown below:

int b[2][3];

We declared a 2-D Array:

The array b is of data type int.

Data is represented in the form of 2 rows and 3 columns as can be seen in Table 5.2:

Column 0

Column 1

Column 2

Column 3

Row 0

Row 1

Table 5.2: Memory map of a int type 2-D Array

In a 2-D array depiction, the first square bracket signifies the number of rows, and the second square bracket represents the number of columns in each row. The index of the array elements starts with 0 (usually called the lower bound of the array) and ends with size -1 (called the upper bound of an array). These hold the base address of the memory block where elements of an array will be stored and are known as an internal pointer variable. So ‘b’ is an internal pointer in the above declaration. The row index, as well as the column index, starts with 0, and subsequent indices are calculated by incrementing the previous index. So, if the number of rows is 4, then the row indices will be 0, 1, 2, and 3. Similarly, if there are 3 columns, they would be indexed as 0, 1, and 2. To access a particular element of a matrix, we must provide the row as well as the column index of the value. For example, to get the value of the 2nd row 3rd column in the above matrix, we can write:

b [1][2]

Initializing two-dimensional arrays

Providing initial values for each element of an array is required when initializing a two-dimensional array in C. Depending on your needs, you may initialize a two-dimensional array in a variety of ways. Typical methods include the following:

Inline initialization: A two-dimensional array can be declared with inline values to start it off. The values are separated by commas and surrounded in braces, “.” Row by row, the initialization is carried out:

int mtr [2][3] = {

{61, 23, 80}, // Row 0

{21, 15, 39} //Row 1

{32, 35, 59} //Row 2

};

This example initializes a 3x3 array named mat with the given values. This initialization method is preferred for its clarity, as we can clearly understand the orientation of rows and columns.

The following is another variation of the above declaration:

int b [2][3] = {61, 23, 80, 21, 15, 39};

The following is the pictorial representation of elements and their address for 2-D array b.

The elements of an array are usually stored in contiguous memory locations. The data type of the array determines the number of bytes the values would be apart. For example, in the above declaration, the values (elements of the 2-D array) would be 2 bytes apart as the array has been declared as int, as shown below:

b [0][0]

b [0][1]

b [0][2]

b [1][0]

b [1][1]

b [1][2]

61

23

80

21

15

39

Row 0

Row 1

230

232

234

236

238

240

Table 5.3: Hypothetical addressing in a 2-D Array

Loop-based initialization: Nested loops can be used to dynamically initialize a two-dimensional array. With this method, you may calculate and assign numbers depending on certain criteria or formulae:

int mtr [3][3];

int val = 1;

for (int i = 0; i < 3; i++)

{

for (int j = 0; j < 3; j++)

{

mtr [i][j] = val;

val++;

}

}

This code initializes a 3x3 array named mtr with consecutive values starting from 1.

Partial initialization: We can initialize specific elements of a two-dimensional array while leaving others uninitialized. This is useful when you want to assign values to only certain elements:

int mtr[3][3] = {

{1, 2},

{4},

{7, 8, 9}

};

In this example, the first row of the array is initialized with two values, the second row with one value, and the third row with three values. The remaining elements are automatically initialized to 0.

Initializing with a single value: Use nested loops or the memset() method from the <string.h> header if you wish to initialize the full two-dimensional array with a single value:

int mtr[3][3];

int val = 0;

for (int i = 0; i < 3; i++)

{

for (int j = 0; j < 3; j++)

{

matrix[i][j] = val;

}

}

This code initializes a 3x3 array with all elements set to the value 0. Alternatively, you can use memset() as follows:

#include <string.h>

int mtr [3][3];

int val = 0;

memset (mtr, val, sizeof(mtr));

The memset() function sets the specified value to each byte of the array, effectively initializing all elements.

2-dimensional array elements access

The row index and the column index of a two-dimensional array are used to access an element in the array.

For example:

int val = a[2][3];

The aforementioned sentence will choose the fourth member from the array’s third row. Check out the following program, which handles a two-dimensional array using nested loops:

#include <stdio.h>

int main ()

{

int mtr [3][3] =

{

{0,1,2},

{1,2,3}, // Matrix with 3 rows and 3 columns

{2,4,5}

};

int i, j;

for (i = 0; i < 3; i++)

{ // output each array element's value

for (j = 0; j < 3; j++)

{

printf ("%d\t", mtr[i][j]);

}

printf ("\n");

}

return (0);

}

Output:

0 1 2

1 2 3

2 4 5

The majority of the arrays we generate will probably just have one or two dimensions, but we may have arrays with any number of dimensions.

In C, we may employ any of the following two methods to pass a matrix (two-dimensional array) to a function as a parameter:

Passing 2-D matrix to a function in C

In order to pass a matrix to a function in C, one must specify the parameter using the correct syntax and include the matrix’s dimensions in the function parameter.

Passing array without using pointer: In this technique, when declaring formal parameters, the function skips the first dimension alluding to the fact that it can change, while the 2nd dimension is specified, representing the number of columns:

#include <stdio.h>

void printmat (int mtr [][3])

{

for (int i = 0; i < 3; i++)

{

for (int j = 0; j < 3; j++)

{

printf ("%d ", mtr[i][j]);

}

printf("\n");

}

}

int main () {

int mtr [3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9}};

printmat (mtr);

return(0);

}

In this example, the printmat () function takes the matrix as a parameter. The function prints the elements of the matrix in a tabular format.

In the main () function, we specify both the (row and column) parameters and then pass this matrix to the printmat () function as an argument. When calling the printmat () function, the parameter mtr is automatically converted to a pointer to the first element of the matrix. The function can access and manipulate the matrix elements using the indices provided.

Passing as a pointer to an array: In this approach, you can pass the 2D matrix as a pointer to an array. The function signature will include the dimensions of the matrix, which are necessary to correctly interpret the elements. Here is an example:

#include <stdio.h>

void printmat (int (*mtr)[3], int r, int c)

{

for (int i = 0; i < r; i++)

{

for (int j = 0; j < c; j++)

{

printf ("%d ", mtr[i][j]);

}

printf("\n");

}

}

int main ()

{

int mtr [3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};

printmat (mtr, 3, 3);

return(0);

}

In the above program, the printmat () function takes a pointer to the matrix mtr as its first parameter. The dimensions of the mtr are also passed as parameters. Inside the function, the matrix elements can be accessed and manipulated using the pointer notation `mtr[i][j]`. The r and c parameters are used to define the loop limits for iteration.

The main () function declares mtr and calls the printmat () function, passing the matrix along with the dimensions.

Using a double pointer: We may also use a double pointer to pass a 2-D array to a function. In this case, the function does not require the dimensions of the matrix to be specified in square brackets.

For example:

#include <stdio.h>

void printmat (int** mtr, int r, int c) {

for (int i = 0; i < r; i++)

{

for (int j = 0; j < c; j++)

{

printf ("%d ", mtr[i][j]);

}

printf("\n");

}

}

int main () {

int mtr [3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9}};

int* p [3];

for (int i = 0; i < 3; i++) {

p[i] = mtr[i];

}

printmat (p, 3, 3);

return 0;

}

In the above program, the printmat () function receives a double pointer mtr as its first parameter. The dimensions of the matrix are also passed as parameters.

In the main () function, a double pointer p is declared and initialized with the addresses of the rows of the matrix, enabling the 2D array to be passed as a double pointer to the printmat () function.

The matrix elements can be manipulated inside the function using the double-pointer notation matrix[i][j].

In C, both these methods can be employed to pass a 2D array to a function. Which one to use is subject to requirements and (or) coding style.

As in the case of 1-dimensional arrays, we would use user-defined functions getmat () and printmat () to scan and print matrices, respectively, instead of writing the same code again and again:

#include <stdio.h>

void getmat (int mtr [][5], int r, int c)

{

int i, j;

// r and c are the no. of rows and columns respectively (order: r x c)

printf ("Input Matrix Elements\n");

for (i = 0; i < r; i++)

{

for (j = 0; j < c; j++)

{

scanf ("%d", &mtr[i][j]);

}

}

}

void printmat (int mtr [][5], int r, int c) {

int i, j;

printf ("Given Matrix is\n");

for (i = 0; i < r; i++)

{

for (j = 0; j < c; j++)

{

printf ("%d\t", mtr[i][j]);

}

printf ("\n");

}

}

Program example: Addition of two matrices

#include <stdio.h>

void getmat (int mtr [][5], int r, int c) {

int i, j;

printf ("Input Matrix Elements\n");

// r and c are the no. of rows and columns respectively (order: r x c)

for (i = 0; i < r; i++)

{

for (j = 0; j < c; j++)

{

scanf ("%d", &mtr[i][j]);

}

}

}

void printmat (int mtr [][5], int r, int c) {

int i, j;

printf ("Given Matrix is\n");

for (i = 0; i < r; i++)

{

for (j = 0; j < c; j++)

{

printf ("%d\t", mtr[i][j]);

}

printf ("\n");

}

}

void addmat (int mat1[][3], int mat2[][3], int mat3[][3], int r, int c) {

int i, j;

for (i = 0; i < r; i++)

{

for (j = 0; j < c; j++)

{

mat3[i][j] = mat1[i][j] + mat2[i][j];

// mat3 [0][0] = mat1[0][0] + mat2[0][0] and so on

}

}

}

int main () {

int mat1[3][3], mat2[3][3], mat3[3][3], r, c;

printf ("Input order of matrices - R and C \n");

scanf ("%d%d", &r, &c);

getmat (mat1, r, c);

printmat (mat1, r, c);

getmat (mat2, r, c);

printmat (mat2, r, c);

addmat (mat1, mat2, mat3, r, c);

printmat (mat3, r, c); //to print matrix 3

return (0);

}

Multiplication of two matrices:

#include <stdio.h>

void getmat (int mtr [][3], int r, int c) {

int i, j;

printf ("Input Matrix Elements\n");

// r and c are the no. of rows and columns respectively (order: r x c)

for (i = 0; i < r; i++)

{

for (j = 0; j < c; j++)

{

scanf ("%d", &mtr[i][j]);

}

}

}

void printmat (int mtr [][3], int r, int c) {

int i, j;

printf ("Given Matrix is\n");

for (i = 0; i < r; i++)

{

for (j = 0; j < c; j++)

{

printf ("%d\t", mtr[i][j]);

}

printf ("\n");

}

}

void multiplymat (int mat1[][3], int mat2[][3], int mat3[][3], int r1,int c1, int r2, int c2) {

int i, j, k;

for (i = 0; i < r1; i++)

{

for (j = 0; j < c2; j++)

{

mat3[i][j] = 0;

//initialize mat3[i][j] to 0 for each mat3[i][j]

for (k = 0; k < c1; k++)

{

mat3[i][j] = mat3[i][j] + mat1[i][k] * mat2[k][j];

}

}

}

}

int main () {

int mat1[3][3], mat2[3][3], mat3[3][3], i, j, r1, c1, r2, c2;

printf ("Input order of matrix 1\n");

scanf ("%d%d",&r1, &c1);

printf ("Input order of matrix 2\n");

scanf ("%d%d", &r2, &c2);

if (c1 != r2) {

printf ("Invalid order of matrices - multiplication not possible\n");

exit (0);

}

getmat (mat1, r1, c1);

printmat (mat1, r1, c1);

getmat (mat2, r2, c2);

printmat (mat2, r2, c2);

multiplymat (mat1, mat2,mat3, r1, c1, r2, c2);

printmat (mat3, r1, c2); //to print matrix 3

return (0);

}

Note: Each of the row elements of matrix 1 is multiplied by each of the column elements of matrix 2. So, there should be corresponding elements in rows and columns, which is why the restriction (c1 should be equal to r2).

The sum of diagonal elements:

#include <stdio.h>

void getmat (int mtr[][3], int r, int c) {

int i, j;

printf ("Input Matrix Elements\n");

// r and c are the no. of rows and columns respectively (order: r x c)

for (i = 0; i < r; i++)

{

for (j = 0; j < c; j++)

{

scanf ("%d", &mtr[i][j]);

}

}

}

void printmat (int mtr[][3], int r, int c) {

int i, j;

printf ("Given Matrix is\n");

for (i = 0; i < r; i++)

{

for (j = 0; j < c; j++)

{

printf ("%d\t", mtr[i][j]);

}

printf ("\n");

}

}

int sumDE (int mtr[][10], int r) {

int i, sum=0;

for (i = 0; i < r; i++)

{

sum = sum + mtr[i][i];

}

return (sum);

}

int main () {

int mtr[10][10];

int sum, m, n;

printf ("Input the order of the matrix\n");

scanf ("%d %d", &m, &n);

if (m == n)

{

getmat (mtr, m, n);

printf ("The given matrix is \n");

printmat (mtr, m, n);

sum=sumDE(mtr, m);

printf ("\nThe sum of the main diagonal elements is = %d\n", sum);

}

else

{

printf ("The given order is not square matrix\n");

}

return (0);

}

Check if a matrix is an identity matrix: The identity matrix is a square matrix with 1’s along the diagonal from upper left to lower right and 0’s in all other positions:

#include <stdio.h>

void getmat (int mtr[][3], int r, int c) {

int i, j;

printf ("Input Matrix Elements\n");

// r and c are the no. of rows and columns respectively (order: r x c)

for (i = 0; i < r; i++)

{

for (j = 0; j < c; j++)

{

scanf ("%d", &mtr[i][j]);

}

}

}

void printmat (int mtr[][3], int r, int c) {

int i, j;

printf ("Given Matrix is\n");

for (i = 0; i < r; i++)

{

for (j = 0; j < c; j++)

{

printf ("%d\t", mtr[i][j]);

}

printf ("\n");

}

}

int checkidentity (int a[10][10], int m, int n)

{

int i, j;

for (i = 0; i < m; i++)

{

for (j = 0; j < n; j++)

{

if (i == j && a[i][j] != 1)

{

return (0);

}

else if (i!= j && a[i][j] != 0)

{

return (0);

}

} // End of for j

} // End of for i

// End of function

return(1);

}

int main () {

int mtr [10][10],m, n,flag;

printf ("Input the order of the matrix \n");

scanf ("%d %d", &m, &n);

getmat (mtr, m, n);

printf ("The given matrix is \n");

printmat (mtr, m, n);

flag= checkidentity (mtr, m, n);

if (flag == 1)

{

printf ("It is an IDENTITY MATRIX\n");

}

else

{

printf ("It is NOT an identity matrix\n");

}

return (0);

}

Transpose of a matrix: We get a matrix’s transpose if we flip rows into columns or columns into rows. In the superscript of the provided matrix, the letter “T” designates the transpose of the matrix.

For instance, if “A” is the supplied matrix, then ‘A’ or AT stands in for the matrix’s transposition. Refer to the following figure:

Figure 5.6: Transpose of a matrix

#include <stdio.h>

void getmat (int mtr[][3], int r, int c) {

int i, j;

printf ("Input Matrix Elements\n");

// r and c are the no. of rows and columns respectively (order: r x c)

for (i = 0; i < r; i++)

{

for (j = 0; j < c; j++)

{

scanf ("%d", &mtr[i][j]);

}

}

}

void printmat (int mtr[][3], int r, int c) {

int i, j;

printf ("Given Matrix is\n");

for (i = 0; i < r; i++)

{

for (j = 0; j < c; j++)

{

printf ("%d\t", mtr[i][j]);

}

printf ("\n");

}

}

void transpose (int mtr[][10], int m, int n) {

int i, j,t;

for (i = 0; i< m;i++)

{

for (j = 0; j < n; j++)

{

t=mtr[i][j];

mtr[i][j]=mtr[j][i];

mtr[j][i]=t;

}

}

}

int main () {

int trp[10][10], mtr[10][10],m, n, flag;

printf ("Input the order of the matrix \n");

scanf ("%d %d", &m, &n);

getmat (mtr, m, n);

printmat (mtr, m, n);

transpose (mtr, m, n);

printf ("Transpose of the given matrix is \n");

printmat(mtr,m,n); // order of trp would change to n x m

return (0);

}

Conclusion

In conclusion, this chapter has covered crucial concepts related to arrays, string manipulation, and matrices in programming. We have explored the fundamentals of one-dimensional arrays, including accessing and modifying array elements. Additionally, we have delved into string manipulation techniques, equipping you with the skills to handle strings effectively. Furthermore, we have examined matrices and learned how to pass a two-dimensional matrix to a function. By mastering these topics, you have gained valuable knowledge to enhance your ability to work with data structures in various programming scenarios.

Exercise

What is an array in C programming? How do you declare an array in C programming?

How do you initialize an array in C programming?

What is the syntax for accessing an element of an array in C programming?

What would be the output of the following C program?

#include <stdio.h>

int main()

{

int arr[5] = {1, 2, 3, 4, 5};

printf("%d\n", arr[3]);

return(0);

}

How do you change the value of an element in an array in C programming?

How do you find the length of an array in C programming?

How do you print the elements of an array in C programming?

Can an array have elements of different data types in C programming?

Can an array be resized in C programming?

What would be the output of the following C program?

#include <stdio.h>

int main()

{

int arr[4];

arr[0] = 10;

arr[1] = 20;

arr[2] = arr[0] + arr[1];

printf("%d\n", arr[2]);

return(0);

}

How do you pass an array to a function in C programming?

Can you return an array from a function in C programming?

What is a two-dimensional array in C programming?

How do you declare and initialize a two-dimensional array in C programming?

Write a program in C to find sum of diagonal elements in a two-dimensional array.

What will the output of the following code:

#include <stdio.h>

int main() {

int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};

printf("%d", arr[1][2]);

return(0);

}

How do you print the elements of a two-dimensional array in C programming?

What would be the output of the following C program?

#include <stdio.h>

int main() {

int matrix[2][2] = {{1, 2}, {3, 4}};

int result = matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0];

printf("Determinant: %d\n", result);

return 0;

}

How do you declare a multidimensional array in C programming?

Write a C program to find neighbors of an element in a 2-D array.

How do you print the elements of a multidimensional array in C programming?

Write a C program to find the length of a multidimensional array in C programming.

Write a C program to sort an array in C programming using bubble sort.

How do you search for an element in an array in C programming?

How do you concatenate two arrays in C programming?

Write a C program to copy an array in C programming.

Write a C program to reverse an array in C programming.

Write a C program to find the largest and smallest elements in an array.

How is a string declared in C?

What would be the output of the following C program?

#include <stdio.h>

int main() {

char str[] = "Hello, World!";

printf("%c\n", str[7]);

return 0;

}

How can we input a string in C? How can we output a string in C?

What is the length of a string in C?

How can we compare two strings in C?

Write a C program to concatenate two strings in C?

Write a C program to convert an integer to a string in C?

Join our book’s Discord space

Join the book’s Discord Workspace for Latest updates, Offers, Tech happenings around the world, New Release and Sessions with the Authors:

https://discord.bpbonline.com

CHAPTER 6Pointers

Introduction

In this chapter, we will explore various topics related to arrays, string manipulation, and matrices in the context of programming. Arrays are one-dimensional structures that allow us to store and manipulate a collection of elements. We will learn how to perform operations on arrays, such as accessing elements and modifying their values. Additionally, we will delve into string manipulation, understanding how to work with strings effectively. Furthermore, we will talk about matrices and explore how to pass a two-dimensional matrix to a function.

Structure

In this chapter, we will cover the following topics:

Pointers

String manipulation

Pointer arithmetic

Null pointers

Dangling pointers

Void pointers

Wild pointers

Array of pointers

Objectives

The objective of this chapter is to provide a comprehensive understanding of arrays, string manipulation, and matrices. By the end of this chapter, you will be equipped with the knowledge and skills necessary to manipulate arrays effectively, perform string operations efficiently, and work with matrices in programming. Through practical examples and explanations, we aim to enable you to apply these concepts in your coding projects.

Pointers

The concept of pointers in C programming language is one of the important concepts since it enables us to directly change data and interact with memory locations. For effective memory management, dynamic memory allocation, and sophisticated data structures in C, understanding pointers is essential.

A pointer is a type of variable that essentially points to the place in memory where the real data is kept. The ‘*’ sign, followed by the variable name, is used to declare pointers in the language C.

Pointers allow a program to manipulate the memory directly rather than working with the data stored at that memory address. Pointers can be used to create dynamic data structures, such as linked lists and trees, and to pass large amounts of data to and from functions more efficiently. To declare a pointer, we must mention the data type of the variable along with a special symbol - asterisk (*) to distinguish it from other ordinary variables.

Using pointers

Pointers can be used following the below-mentioned steps:

Declare a pointer variable.

Assign the address of a variable to a pointer.

Access value at the address stored in the pointer.

Declaring a pointer

A pointer is a variable that stores the address of another variable; that is, it stores the address of the memory location. Like other variables, we must declare a pointer before we start using it to store any variable address. In Figure 6.1, the address of a variable, a (which stores 10), is stored by another variable, ptr, which must be declared as a pointer:

Figure 6.1: ptr storing address of a

The general form of a pointer variable declaration is:

type *var;

Where:

type is the pointer’s data type; it must be a valid C data type.

var is the name of the pointer variable.

Asterisk (*) is used to declare a pointer. In other words, the * would tell the compiler that the variable is a special variable that would store a memory address rather than ordinary values.

Some examples of the pointer declaration are:

int *ip; // pointer to an integer

double *dp; // pointer to a double

float *fp; // pointer to a float

char *ch; // pointer to a character

The data type of a pointer variable is established by the kind of data it points to. The value stored by pointers is essentially the same, whether declared as int, float, char, or others, that is, a long hexadecimal number that represents a memory address. The only difference between pointers of different data types is the data type of the variable or constant that the pointer points to. The data type of a pointer variable, however, determines how it is dereferenced and the amount of memory is allocated. As dereferencing a pointer with the incorrect data type might result in undescribed behavior and type-related difficulties, it is crucial to ensure that the pointer’s data type matches the data type of the object it references.

Accessing the value of a pointer

Pointers are variables that hold memory addresses. Working with pointers requires performing the fundamental actions of referencing and dereferencing. Here is a description of both:

Referencing pointers: Referencing a pointer involves discovering the memory address that it refers to. A pointer is referenced using the pointer variable alone, without any extra operators.

For instance, to get the memory location held by a pointer variable named p that points to an integer, we may use the following reference:

Figure 6.2: Memory map of a dummy pointer

int *p; // Declare a pointer to an integer

int x = 100;

p = &x; // Assign the address of x to p - Referencing

The code above uses &x to assign p, the address of x. Hence p will now point to variable x. This is referred to as Referencing.

Dereferencing pointers: Accessing value at a memory address that a pointer points to is known as dereferencing that pointer. We must use the dereference operator ‘*’ and the pointer variable in order to dereference a pointer.

For example:

nt *p; // Declare a pointer to an integer

int x = 100;

p = &x; // Assign the address of x to p

*p = 200; // Set value at the memory address, pointed to by p as 200

In the code example, the value of x is assigned to the variable y as *p (read as value at address p) is used to dereference the pointer p.

Note: In the above example p represents an address, while *p represents the value stored at the address pointed to by p.

We can also modify the value stored at an address pointed to by a pointer by dereferencing and assigning a new value.

For example:

int *p; // Declare a pointer to an integer

int x = 100;

p = &x; // Assign the address of x to p

*p = 200; // Set value at the memory address, pointed to by p as 200

In the above code, after dereferencing p, new value i-e: 200 is assigned to x; hence, the value of x will change to 200.

To access the address of a variable, we use the ‘&’ (ampersand) operator. &x (read as address of x) gives us the address of the variable x.

For example, with reference to Figure 6.2:

printf("Address of x = %p", &x); // will print AEFF

printf("Address of p = %p", &p); // will print E0FA

// if we have used a referencing statement like p = &x; We can print address of x using p as:

printf("Address of x = %p", p); // will print AEFF as p = &x

The following program makes use of these operations:

#include<stdio.h>

int main ()

{

int x = 10;

int *p; // pointer variable declaration */

p = &x; // store address of x in pointer variable*/

printf("Address of a = %p\n", &x); //address stored in pointer variable

printf("Address stored at p = %p\n", p);

printf("Value of x = %d\n", *p); // access the value using pointer

return (0);

}

The test output of the above code is as follows:

Address of x = bffd8b3c

Address stored in p = bffd8b3c

Value of x = 10

Application of pointers

Application of pointers in C spreads across the spectrum of processing operations, as discussed in the upcoming sections:

Direct memory manipulation

Pointers enable direct memory manipulation, which is advantageous for dealing with hardware or optimizing memory utilization in low-level programming activities. We can read and alter data at certain memory locations by fiddling with pointers.

Dynamic memory allocation

Pointers play an essential role in using functions like malloc, calloc, and realloc. These operations return a reference to the RAM that was allocated and do so at runtime. This makes it possible to create adaptable data structures like dynamic arrays, linked lists, and trees.

In C programming, dynamic memory allocation is achieved using three functions:

malloc()

This function is used to allocate a block of memory of a specified size. The syntax for this function is:

ptr = (cast-type*) malloc(byte-size);

Here, ptr is a pointer to the allocated memory, and byte-size is the number of bytes to be allocated.

Example 1:

#include <stdio.h>

#include <stdlib.h>

int main ()

{

int n, *ptr;

printf("Input number of elements:");

scanf("%d", &n);

ptr = (int*) malloc (n * sizeof(int));

if (ptr == NULL) {

printf("Error! memory not allocated");

exit (0);

}

printf("Memory successfully allocated using malloc.\n");

free(ptr);

return 0;

}

Example 2:

int main () {

int *p;

p = malloc (2); //would allocate 2 bytes since p is an int pointer

*p = 10;

Printf("value stored = %d", *p);

return (0);

}

Even though we can use malloc() this way, it is a standard practice to allocate memory in standard sizes (sizes defined for various data types) using the sizeof () function:

malloc (sizeof (int));

//sizeof function returns the number of bytes (memory requirement) of a data type

calloc()

This function is used to allocate a block of memory of a specified size and initialize the memory to zero. The syntax for this function is:

ptr = (cast-type*) calloc (n, element-size);

Where ptr is a pointer to the allocated memory, n is the number of elements, and element-size is the size of each element in bytes.

Example:

#include <stdio.h>

#include <stdlib.h>

int main () {

int n, i, *ptr;

printf("Input number of elements:");

scanf("%d", &n);

ptr = (int*) calloc (n, sizeof(int));

if (ptr == NULL) {

printf("Memory not allocated");

exit (0);

}

printf("Memory successfully allocated using calloc.\n");

free(ptr);

return 0;

}

realloc()

This function is used to resize the previously allocated memory block. The syntax for this function is:

ptr = realloc (ptr, new-size);

Where, ptr is a pointer to the previously allocated memory, and the new size is the new size of the memory block in bytes.

Example:

#include <stdio.h>

#include <stdlib.h>

int main ()

{

int *ptr, n;

ptr = (int*) malloc (10 * sizeof(int));

if (ptr == NULL) {

printf("Error! memory not allocated");

exit (1);

}

printf("Memory successfully allocated using malloc.\n");

printf("Enter new size of memory:\n");

scanf("%", &n);

ptr = (int*) realloc(ptr, n * sizeof(int));

if (ptr == NULL) {

printf("Error! memory not reallocated");

exit (0);

}

printf("Memory successfully reallocated using realloc");

return (0);

}

Knowing the size of an array makes it simple to define it appropriately while programming. If you want to save the name of any individual, for instance, you may store up to 100 characters. Therefore, you might create something like this: char name [100];.

Let us now look at a scenario when you are unsure of the text’s length and need to save it, such as when you need to store a thorough explanation of a subject. Here, we must define a character reference without specifying how much memory is needed; afterward, we can allocate memory based on the need, as shown in the following example:

#include <stdlib.h>

#include <string.h>

int main ()

{

char n [100];

char *d;

strcpy (n,"Umar zain");

// will allocate 200 bytes, remember size of char is 1 byte

d = malloc (200 * sizeof (char));

if (d!= NULL)

{

strcpy (d,"Umar zain is a DPS student in class 6th");

}

else

{

printf("Errorâ€"- unable to allocate required memory\n");

}

printf("Name = %s\n", n);

printf("Details: %s\n", d);

return (0);

}

Resizing and releasing memory

Operating systems automatically release all the memory your program has allocated when it has finished running, but it is best practice to call the function free (when memory is no longer needed). By executing the function realloc (instead), you can modify the size of a memory block that has been allocated. Use the realloc() and free() methods to recheck the program:

realloc() example using char array (string):

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

int main()

{

// Declare a character array/pointer and allocate memory

char *p = (char *)malloc(20 * sizeof(char));

if (p == NULL)

{

printf("Memory allocation unsuccessful.\n");

return(1);

}

// assign the character array some text

strcpy(p, "Hello, zain!");

printf("before using realloc message = \n%s\n", p);

// Resize the character array using realloc

p = (char *)realloc(p, 30 * sizeof(char));

if (p == NULL)

{

printf("Memory reallocation unsuccessful.\n");

return(1);

}

// append text to the end of string

strcat(p, " How are you?");

printf("after using realloc Message =\n %s\n", p);

free(p);

return(0);

}

Output:

before using realloc message =

Hello, zain!

after using realloc Message =

Hello, zain! How are you?

realloc() example using int array:

//realloc() example using int array

#include <stdio.h>

#include <stdlib.h>

int main()

{

int *a;

// memory for an array of 5 integers

a = (int *)malloc(5 * sizeof(int));

if (a == NULL)

{

printf("Memory allocation failed\n");

return(1); // Exit the program with an error code

}

// Initialize the array with some values

for (int i = 0; i < 5; i++)

{

a[i] = 2*i;

}

// Print the initial array

printf("Initial array: ");

for (int i = 0; i < 5; i++)

{

printf("%d ", a[i]);

}

printf("\n");

// Resize array to store 10 integers using realloc

a = (int *)realloc(a, 10 * sizeof(int));

if (a == NULL)

{

printf("Memory re-allocation failed\n");

return(1);

}

// Initialize the additional elements

for (int i = 5; i < 10; i++)

{

a[i] = 2*i;

}

printf("Resized array: ");

for (int i = 0; i < 10; i++)

{

printf("%d ", a[i]);

}

printf("\n");

// Free the memory

free(a);

return 0; // Exit the program successfully

}

String pointers

String pointers are an essential idea for handling strings in C. Character strings are used to express and modify sequences of characters that are ended by the null character (‘0’). An overview of string pointers in C is provided below:

A string pointer stores the address of the first character of a character array. Subsequent characters may be accessed by successively incrementing the address. For example, if the first character of a char array is stored at address 100, the next character of the array may be found at the address which is 1 byte apart (1 byte= sizeof (char))

We can initialize a string pointer in several ways, including:

char *s1 = "Hello all!"; // Initialize with a string literal

char s2[] = "to Programming"; // Initialize an array of characters

char *s3 = str2; // Initialize with the address of an existing array

We can access individual characters of a string using array subscript notation or pointer arithmetic.

For example:

char *s = "Usman";

char fc = s[0]; // Access the first character ('U')

char sc = *(str + 1); // Access the second character ('s')

We must exercise caution when modifying strings which are referenced string pointers. Strings declared as string literals (For example, "Imad") are generally stored in read-only memory; hence, altering them directly may lead to indeterminate behavior. To change a string, we should use an array of characters instead.

If we dynamically allocate memory for strings using malloc or realloc, we must manage memory appropriately and free it (using free) when finished, using the string, to prevent memory leaks.

In C, strings can be represented using two main approaches: fixed-length strings and variable-length strings, which are discussed as follows:

Fixed-length strings

Character arrays: A fixed-length string in C is typically implemented using a character array.

For example:

char fs[30]; // array of characters with a fixed size of 30

Null-terminated: In this method, a string is terminated with a null character to indicate the end of a string. This is essential for string manipulation functions to work properly.

Limited flexibility: We define fixed-length strings with predetermined maximum size, which ensures they only hold a certain number of characters, and if we try to exceed this limit, it can generate buffer overflow issues.

Fixed-length strings can be more memory-efficient if the maximum size is known and constant.

char n[10] = "Zain"; // Fixed-length 10 characters string

Variable-length strings

Dynamic memory allocation: Variable-length strings are implemented using pointers to characters (char*) and dynamic memory allocation functions like malloc, realloc, and free. Variable-length strings can dynamically grow or shrink during runtime, allowing them to accommodate strings of different lengths. Due to dynamic memory allocation, variable-length strings typically have more memory overhead compared to fixed-length strings.

char* vs = (char*)malloc(20 * sizeof(char)); // memory for 20 char's

free(vs); // free the allocated memory when done

Criteria

Fixed-length strings

Variable-length strings

Use case

Use fixed-length strings when the maximum size is known and constant.

Use variable-length strings when the size of the string is not known in advance or may change during runtime.

Memory management

Fixed-length strings do not require explicit memory management (no need to use malloc or free).

Variable-length strings require explicit memory allocation and deallocation.

Flexibility

Fixed-length strings have a fixed size and cannot change.

Variable-length strings can grow or shrink as needed.

Error-prone

Using fixed-length strings can be safer in terms of buffer overflows as long as you ensure you do not exceed the allocated size.

Variable-length strings can be more error-prone if not managed properly, as they can potentially lead to memory leaks or dangling pointers.

Table 6.1: Comparison between variable and fixed-length strings

The approaches come with strengths and weaknesses, and the choice amongst them is dependent on the requirements of the program we are writing.

Passing parameters to functions

Pointers are used to pass parameters to functions by reference. This enables the function to change the initial variables passed to it as arguments. This is especially helpful if we wish to change variables inside of a function and have those changes take effect in the code that calls the function or in other parts of the program.

Generally, values of parameters are passed to a function. This is referred to as call-by value (or pass-by-value) and is one of the methods we can employ for passing parameters to a function in C. When passing arguments to a function, the call-by-value method copies the parameter’s actual value into the formal parameter. Changes made to the parameter inside the called function have no impact on the actual parameter. Call by value is the default method for passing arguments in C programming.

Call by reference method, on the other hand, copies the address of a parameter into the formal parameter. Inside the function, the address is used to access the actual value pointed to by the address (the content of the formal parameter). It means the changes made to the parameter affect the actual argument.

To pass a value by reference, addresses are passed to the called function, just like other parameters. So accordingly, to be in sync with the parameter protocols, we must declare the formal parameters as pointer type as demonstrated in the following programming examples:

Modifying a variable called function:

#include <stdio.h>

#include <stdlib.h>

void inc (int *x)

{

(*x) ++; // value at x being incremented

}

int main () {

int a = 10;

printf("Before calling inc function, a = %d\n", a);

inc (&a); //passing address of a and b variables

printf("After calling inc function, a = %d\n", a);

return (0);

}

Output:

Before calling inc function, a = 10

After calling inc function, a = 11

Swap two variables using pointers:

#include <stdio.h>

#include <stdlib.h>

void swap (int * x, int * y)

{

// Declare x & y as pointer cells (we are receiving addresses of a and b)

int temp;

temp = *x; // save the value at address x to temp

*x = *y; // put value at y into x

*y = temp; // put temp into y

}

int main ()

{

int a = 100, b = 200;

printf("Before swapping, value of a: %d\n", a);

printf("Before swap, value of b: %d\n", b);

swap(&a, &b); //passing address of a and b variables

printf("After swap, value of a: %d\n", a );

printf("After swap, value of b: %d\n", b );

return (0);

}

Output:

Before swapping, value of a: 100

Before swap, value of b: 200

After swap, value of a: 200

After swap, value of b: 100

It shows that the change has reflected outside the function as well, unlike call by value, where the changes do not reflect outside the function.

Note: Arrays are always passed by reference, that is, why any changes performed on an array in one function reflect in other functions (or throughout the program).

Effective array manipulation

Pointers may be effectively utilized to modify arrays. We can go through an array, get at its components, and carry out actions on them by incrementing or decrementing a pointer.

Array manipulation using pointers in C allows us to efficiently access and modify array elements by using pointer arithmetic. By utilizing pointers, you can perform operations such as traversing the array, modifying values, and swapping elements. Let us explore some common array manipulation techniques using pointers in C:

Accessing array elements: Pointers can be used to access array elements directly, eliminating the need for indexing. We can assign a pointer to the first element of the array and then use pointer arithmetic to access subsequent elements:

Simple integer access:

int main ()

{

int a = 10;

int *p;

p = &a;

//store address of a in pointer variable*/

printf("Value of a = %d\n", *p);

// access the value using pointer

return (0);

}

Array access (any particular element):

//Array access:

#include <stdio.h>

int main ()

{

int a [5] = {10, 20, 30, 40, 50};

int *p;

p = a;

// Points to the first element of array

printf("First element of Array = %d\n", *p);

// Prints 10

return (0);

}

Array access (all elements):

#include <stdio.h>

int main ()

{

int i=0,a[5] = {10, 20, 30, 40, 50};

int *p;

p = a;

printf("Displaying array elemets using pointer\n");

while (i<5)

{

printf("%d\n", *p);

p++;

i++;

}

return (0);

}

Changing array elements directly: Pointers can be used to change array elements. Dereferencing the pointer allows you to give array items new values:

#include <stdio.h>

#include <stdlib.h>

int main ()

{

int a [5] = {10, 20, 30, 40, 50};

int i=0, *p;

p = a;

// Points to the first element of array

while (i<5) {

*p = *p + 10;

// changing every array value by +10

printf("%d\n", *p);

p++;

// Moves the pointer to the next elementâ€™s address

i++;

}

return (0);

}

Output:

20

30

40

50

60

Swapping array elements: Pointers can be used to swap the values of two array elements efficiently. Here is an example of swapping the first and last elements of an array:

#include <stdio.h>

int main ()

{

int a[] = {10, 20, 30, 40, 50};

int *p1 = a;

int tmp;

int *p2;

p2 = &a[4];

// Points to the last element of arr

tmp = *p1;

// Store the value of the first element in a temporary variable

*p1 = *p2;

// Swap the values of the first and last elements

*p2 = tmp;

for (int i = 0; i < 5; i++) {

printf("%d ", a[i]);

}

return (0);

}

Output:

50

20

30

40

10

String manipulation

In the C programming language, strings are represented as character arrays. To retrieve individual characters, concatenate strings, compare strings, and carry out other string operations, pointers are frequently utilized.

By using pointer arithmetic and string-related functions, C’s string manipulation with pointers lets you quickly modify and process strings. Without the need for extra memory allocation, pointers offer a straightforward method to navigate, alter, and execute operations on strings.

Some common string manipulation techniques using pointers in C:

Accessing characters in a string

A string’s individual characters can be accessed via pointers. You may use pointer arithmetic to traverse a string and access characters by allocating a pointer to the string’s beginning. Here is an example:

#include <stdio.h>

int main ()

{

char *s = "Think Green";

char *p = s;

while (*p!= '\0')

{

printf("%c", *p);

p++;

}

return (0);

}

Output:

Think Green

Modifying characters in a string

A string’s individual characters can be changed using pointers. Characters in the string can have new values assigned to them by dereferencing the reference. It is crucial to remember, nevertheless, because altering string literals constitutes undefined behavior:

#include <stdio.h>

#include <stdlib.h>

int main ()

{

char s[6] = "Gello";

char *p;

p=s;

*p= 'H'; // change 1st character to G

printf("%s", s);

return (0);

}

Output:

Good

String concatenation:

Pointer arithmetic and string-related operations can concatenate two strings using pointers. Strings can be copied and concatenated with the strcpy and strcat functions, respectively. For example, refer to Chapter 5, Strings.

Pointers may be used to manipulate strings in a flexible and effective manner in C. You may quickly read, edit, and concatenate strings without additional memory allocation by using pointer arithmetic and string-related methods. It is important to make sure that references are handled correctly and that the destination string has enough memory to hold the concatenated result, though.

Just keep in mind that changing string literals might lead to unexpected behavior. For changeable strings, character arrays (char[]) are advised over string literals (char*).

We may swiftly and successfully execute a variety of string manipulation operations in C by utilizing the power of pointers and functions that relate to strings.

Pointer arithmetic

The C programming language has a feature called pointer arithmetic that enables you to perform mathematical operations on pointers. It offers an easy method to move around and work with objects in dynamically allocated memory or an array. You can effectively access, navigate, and work with memory-based data structures if you comprehend pointer arithmetic.

The size of the datatype the pointer points to determines how pointer arithmetic works in C. The size of the data type is considered when performing arithmetic operations on a pointer.

The following arithmetic operations can be performed on pointers:

Increment and decrement

We can increment or decrement a pointer by several elements as follows:

#include <stdio.h>

int main ()

{

int a [5] = {10, 20, 30, 40, 50};

int i=0, *p;

p = a;

// Points to the first element of array

while (i<5)

{

printf("%d\n", *p);

p++;

// Moves the pointer to the next element

i++;

}

return (0);

}

Decrement example:

#include <stdio.h>

int main ()

{

int a [5] = {10, 20, 30, 40, 50};

int i=4, *p;

p = &a[4];

// Points to the last element of array

while (i>=0)

{

printf("%d\n", *p);

p--; // Moves the pointer to the next element

i--;

}

return (0);

}

Addition and subtraction:

Pointers can be added or subtracted by an integer value. The result is calculated based on the size of the data type.

For example:

#include <stdio.h>

#include <stdlib.h>

int main ()

{

int a [5] = {10, 20, 30, 40, 50};

int i=0, *p;

p = &a;

printf("%d\n", *p); // First Element

p=p+3;

// Moves the pointer to the 4Th element

printf("%d\n", *p);

return (0);

}

Pointer difference:

You can subtract two pointers of the same type to determine the number of elements between them. The result is the difference in memory addresses divided by the size of the data type.

For example:

#include <stdio.h>

#include <stdlib.h>

int main ()

{

int a[5] = {10, 20, 30, 40, 50};

int *p1 = &a[0];

int *p2 = &a[3];

int pdiff = p2-p1;

printf("Address are %d Size-Units apart", pdiff);

return (0);

}

Output:

Address are 3 Size-Units apart

Pointer comparisons

The addresses stored by two pointers are compared in C via pointer comparisons. Comparing pointers can be helpful in a variety of situations, such as detecting if two pointers lead to the same location in memory or the relative order of two memory locations. Comparing pointers is possible using relational operators like ==, < and >.

The following are some key ideas in C’s pointer comparisons:

Equality comparison

The equality operator can be used to determine if two pointers are referring to the same address in memory.

For example:

#include <stdio.h>

int main()

{

int a=10,b=20;

int* p1;

int* p2;

// make p1 and p2 to some addresses

// p1=&a;

//p2=&b;

// Compare the 2 pointers

if (p1 == p2)

{

printf("p1 and p2 point to the same memory address");

}

else

{

printf("p1 and p2 point to the different memory address");

}

return (0);

}

Inequality comparison

To determine if two pointers are referring to separate memory locations, use the inequality operator (‘!=’).

For example:

//Inequality comparison

#include <stdio.h>

int main()

{

int a=10,b=20;

int* p1;

int* p2;

// make p1 and p2 to some addresses like

p1=&a;

p2=&b;

if (p1 != p2) { // Compare the 2 pointers

printf("p1 and p2 point to the different memory addresses");

}

else {

printf("p1 and p2 point to the same memory address");

}

return (0);

}

Relational comparison

The relative order of memory addresses can also be determined via pointer comparisons. When two pointers are compared, their respective locations in memory are verified using the relational operators '', ‘>’, ‘=’, and ‘>=’. It is crucial to remember that comparing pointers that do not point to components of the same array or associated memory results in undefinable behavior.

For example, comparing two pointers from the same array can help determine their order:

#include <stdio.h>

#include <stdlib.h>

int main () {

int a [5] = {10, 20, 30, 40, 50};

int* p1 = &a [1];

int* p2 = &a [3];

if (p1 < p2) {

printf("p1 points to a lower memory location than p2");

}

else {

printf("p2 points to a lower memory location than p1");

}

return (0);

}

Output:

p1 points to a lower memory location than p2

The memory locations that the pointers hold are used as the basis for pointer comparisons in C. To prevent undefined behavior, it is crucial to exercise caution and ensure that the pointers being compared are properly initialized, valid, and associated with the same memory area.

When working with arrays, iterating over their elements, and modifying data structures that demand sequential access, pointer arithmetic is helpful. Without the requirement for explicit indexing, it offers an effective technique to navigate through memory regions.

To prevent accessing erroneous memory addresses or resulting in undefinable behavior, it is crucial to use pointer arithmetic with prudence. Make sure that the arithmetic operations are carried out within the restrictions of the RAM that has been allotted. By executing arithmetic operations on pointers, the arithmetic pointer in C enables you to browse and modify data structures. It makes efficient data access, dynamic memory management, and array traversal easier. We may build more effective and expressive C code by comprehending and applying pointer arithmetic.

Pointers can be incremented or decremented and can be subjected to arithmetic operations. Pointer arithmetic is made possible by this feature, which is helpful when working with arrays and quickly iterating across memory locations.

Return pointer from functions

A pointer can be returned from a function in C. You can give the calling function a memory location that can be used to read or modify data beyond the function’s domain by returning a pointer.

Let us look at some examples now.

Declare a function with a pointer as the return type:

int* func (int n) {

// Function implementation

}

The function func() returns a pointer to an integer (int*).

Perform operations on the allocated memory:

int* func (int n) {

int* a = (int*)malloc(size * sizeof(int));

// Memory allocation and other operations

for (int i = 0; i < n; i++)

{

a[i] = i + 1;

}

return a;

}

In the above example, the allocated memory is filled with values (in this case, the array is initialized with consecutive integers).

Return the pointer from the function:

int* func (int n) {

int* a = (int*)malloc(size * sizeof(int));

// Memory allocation and other operations

for (int i = 0; i < n; i++)

{

a[i] = i + 1;

}

return a;

}

Subsequently, the pointer a is returned from the function after performing the requisite actions.

Use the returned pointer in the calling function:

#include <stdio.h>

#include <stdlib.h>

int main ()

{

int* a = func (5);

for (int i = 0; i < 5; i++)

{

printf("%d ", a[i]);

}

return (0);

}

In the code above, the pointer a is assigned the returned pointer from func (). The main function then accesses and prints elements of the array.

The facility of returning a pointer from a function allows us to allocate memory dynamically and return a reference to the allocated memory to the calling function.

Pointer to pointer

A double pointer in C is a variable that stores the address of another pointer variable. It is sometimes referred to as a pointer to a pointer. It gives you two layers of indirection to use when accessing and changing a pointer’s value. When you need to allocate memory dynamically or change pointers themselves, you frequently utilize pointers. Here is how you can declare and use a pointer to a pointer in C:

Declaration: When declaring pointer to pointer, we use two asterisks (**).

Data type** ptr2ptr;

For example:

int ** pv;

Initialization: After declaring a pointer to a pointer, you can initialize it by assigning the address of a pointer variable to it. Here is an example:

#include <stdio.h>

#include <stdlib.h>

int main()

{

int* p; // Pointer variable

int** p2p; // Pointer to a pointer

p = (int*)malloc(sizeof(int));

*p = 10;

p2p = &p; // pointer to pointer

printf("value = %d", **p2p); // use double pointer ** to access stored value

return (0);

}

Output:

value stored = 10

In this example, p is a pointer variable that has been allocated memory using malloc. The address of ptr is then assigned to p2p using the address-of operator (`&`).

Indirection and access: We must employ two layers of indirection to access the value of the pointer using the double pointer. You can obtain the value at the second level of indirection after obtaining the pointer at the first level.

For example:

#include <stdio.h>

#include <stdlib.h>

int main ()

{

int a [5] = {13, 21, 43, 20, 34};

int* p=a;

int** p2p;

p2p = &p;

printf("First Element of the array is %d", **p2p);

return (0);

}

Output:

First Element of the array is 13

Dynamic memory allocation: Double pointers are often used in scenarios where you need to dynamically allocate memory for a pointer variable. Here is an example:

// Dynamic memory allocation

#include <stdio.h>

int main ()

{

int** mat, r=3, c=1, i;

mat = (int**)malloc(r * sizeof(int*));

for (i = 0; i < r; i++)

{

mat[i] = (int*) malloc (c * sizeof(int));

*mat[i] = i+1;

printf("%d\t", *mat[i]);

}

}

Output:

1 2 3

In the code snippet above, mat is declared as a double pointer representing a dynamically allocated 2D array. The first malloc() allocates memory for rows of the matrix, and the second malloc() allocates memory for each column inside the loop.

In C, double pointers are an effective tool that lets you read and alter pointers without directly accessing them. In situations requiring dynamic memory allocation, multi-dimensional arrays, and changing pointer values, they are frequently employed. To prevent memory leaks, it is essential to correctly manage memory, which includes deallocating memory using free when it is no longer required.

Types of pointers

The different types of pointers are discussed below:

NULL pointers

If we do not know an exact address to assign to the pointer, it is advised to assign a NULL value to the pointer variable when declaring it. A NULL pointer is a pointer that has been assigned a value of NULL. According to several standard libraries, a NULL pointer is given a value of zero.

For example:

#include <stdio.h>

int main ()

{

int *ptr = NULL; // NULL Pointer

printf("The value of ptr is: %p\n", ptr);

}

Output:

The value of ptr is 00000000

Because the operating system has reserved memory at address 0, most operating systems do not allow programs to access that memory. The memory address 0 has a specific meaning, however, since it indicates that the pointer is not meant to point to a memory region that is accessible. But by convention, it is presumed that a pointer points to nothing if it has the null (zero) value. To check for a null pointer, use the following commands:

if (ptr) //True if p is not null

if (! ptr) // True if p is null

Dangling pointers

Dangling (which means to dangle freely) pointers point to a deallocated or freed memory location. Dangling Pointer Problem (DPP) refers to the mistake caused by dangling pointers. Dangling pointers can also occur when a pointer that was pointing to a variable has lost its scope or when the memory that the pointer was pointing to is deallocated. When creating programs, attempt to minimize the likelihood of dangling pointers because they might cause unanticipated errors during program execution.

To avoid dangling pointer problems, we may:

Assign NULL to the pointer when the memory gets deallocated.

Use static variables:

#include <stdio.h>

int main () {

int *ptr;

int a=560;

ptr=&a;

printf("%p\n", ptr); // ptr prints address of â€˜aâ€™

free(ptr); // you deallocated/ freed the memory

printf("%p", *ptr);

//will show a dangling (misleading/ false) address

return 0;

}

Void pointers

The term void pointer refers to a type-less pointer, which means it has no attached data type. Any type of address may be stored in a void pointer, which can also be type-casted to any data type.

For example:

//Void Pointer

#include <stdio.h>

int main () {

int a = 10;

float b = 3.5;

void* p = &a;

printf("Address of a = %p\n", p); // holds address of int 'a'

p = &b; // can also store address of float 'b'

printf("Address of b =%p\n", p);

}

Dynamic memory allocation functions like malloc () and calloc () return void * type, which lets these functions allocate memory of any data type. Void pointers are used to implement generic functions in C.

Note: The above code compiles in C but shows error in C++; this is because, in C++, we have to explicitly typecast the return value of malloc.

Wild pointers

Wild pointers are the un-initialized pointers. These pointers can generate errors; such programs in which they are declared are executed.

Wild pointer example:

//Wild pointer

#include <stdio.h>

int main () {

// wild pointer

int* ptr; // un-initialized pointer

printf("%d", *ptr); // No Value because |ptr= &a;| like statement missing

return 0;

}

It is important to mention that using pointers comes with some drawbacks, like dangling pointers, memory leaks, and accessing erroneous memory locations. To ensure proper utilization and prevent any problems, diligent management is necessary. In the C programming language, pointers are a potent tool. Direct memory manipulation, dynamic memory allocation, effective array manipulation, and other sophisticated programming methods are made possible by them. For C programming to be mastered and programs to be developed that are effective, reliable, and memory-optimized, pointers must be understood. To maintain safe and dependable code, however, adequate management and comprehension of pointers are required since with tremendous power comes great responsibility.

Array of pointers

We can declare an array of pointers just like we do in the case of an integer array or a float array like:

int * ptr [5];

Example:

//Array of pointers

#include <stdio.h>

int main ()

{

int a =10, b = 20, c = 30;

int * ptr [3];

ptr [0] = &a; //ptr [0] stores address of a â€¦ and so on

ptr [1] = &b;

ptr [2] = &c;

printf("Values of a, b and c are %d\t%d\t%d", *ptr [0], *ptr[1], *ptr[2]);

return (0);

}

Program example:

#include <stdio.h>

#include <stdlib.h>

int main () {

int a [5], i, *p[5];

printf("Input 5 elements\n");

for (i =0; i<5; i++) {

scanf("%d", &a[i]);

p[i] = & a[i];

}

printf("5 elements entered are\n");

for (i =0; i<5; i++) //print array elements using pointer array

printf("%d", *p[i]);

return (0);

}

We can also use an array of pointers to characters to store a list of strings as:

//Array of pointers - example 3

#include <stdio.h>

int main () {

char *n [] = {

"Zain",

"Zaid",

"Usman",

"Imad",

};

int i = 0;

for (i = 0; i < 4; i++)

{

printf("Good Boy [%d] = %s\n", i+1, n[i]);

}

return (0);

}

Output:

Good Boy [1] = Zain

Good Boy [2] = Zaid

Good Boy [3] = Usman

Good Boy [4] = Imad

Conclusion

In conclusion, this chapter has covered crucial concepts related to arrays, string manipulation, and matrices in programming. We have explored the fundamentals of one-dimensional arrays, including accessing and modifying array elements. Additionally, we have delved into string manipulation techniques, equipping you with the skills to handle strings effectively. Furthermore, we have examined matrices and learned how to pass a two-dimensional matrix to a function. By mastering these topics, you have gained valuable knowledge to enhance your ability to work with data structures in various programming scenarios.

Exercise

What is a pointer in C programming?

How do you declare a pointer in C?

What is the size of a pointer in C?

What will be the output of the following code?

#include <stdio.h>

int main()

{

int x = 5;

int *ptr = &x;

printf("%d\n", *ptr);

return(0);

}

What is the purpose of a pointer?

What is the difference between a pointer and a variable in C?

How do you assign a value to a pointer in C?

What will be the output of the following code?

#include <stdio.h>

int main()

{

int arr[5] = {1, 2, 3, 4, 5};

int *ptr = arr;

printf("%d\n", *ptr);

return(0);

}

What is a null pointer in C? How do you check if a pointer is null in C?

What will be the output of the following code?

#include <stdio.h>

int main() {

char ch = 'A';

char *ptr = &ch;

printf("%c\n", *ptr);

return(0);

}

What is a void pointer in C?

How do you typecast a pointer in C?

What is pointer arithmetic in C?

What is a function pointer in C?

What will be the output of the following code?

#include <stdio.h>

int main()

{

int x = 5;

int *ptr = &x;

*ptr = 10;

printf("%d\n", x);

return(0);

}

Write a program in C to declare a function pointer in C?

How do you call a function using a function pointer in C?

What is a pointer to a pointer in C?

What will be the output of the following code?

#include <stdio.h>

int main()

{

int arr[3] = {1, 2, 3};

int *ptr = arr;

printf("%d\n", *(ptr + 2));

return(0);

}

What is dynamic memory allocation in C?

Write a program in C to allocate memory dynamically using malloc() in C?

How do you free the memory allocated using malloc() in C?

What is a memory leak in C?

How do you avoid memory leaks in C?

Join our book’s Discord space

Join the book’s Discord Workspace for Latest updates, Offers, Tech happenings around the world, New Release and Sessions with the Authors:

https://discord.bpbonline.com

CHAPTER 7Structures and Unions

Introduction

In this chapter, we will explore structures and unions in the C programming language. Structures allow us to define custom data types that can hold multiple variables of different types. Unions, on the other hand, enable us to allocate memory that can be interpreted in multiple ways. We will learn how to create and access members of structures and unions effectively.

Structure

In this chapter, we will cover the following topics:

Structures

Unions

User-defined data types

Enumerations

Members of structure/unions

Accessing structure/union members

Objectives

The objective of this chapter is to provide a comprehensive understanding of structures and unions in C. By the end of this chapter, we aim to equip you with the knowledge and skills necessary to create and manipulate structures and unions. You will learn how to define structures to hold related data and access their members. Additionally, you will understand the concept of unions and how to access their different interpretations of memory.

Structures and unions

In C programming, structures, and unions are used to group multiple variables of different data types.

A structure, usually referred to as a struct, is a grouping of variables of various data kinds that are given a single name. A struct’s members are all its variables, and they may all be accessed using the dot notation, as in structName, memberName.

Complex data types, such as a date or a point in three dimensions, can be represented via structures.

For instance, the code below creates a structure called student with three members named Roll_No, nm, and marks:

struct student {

int Roll_No;

char nm[20];

float marks;

};

A union is similar to a struct, except for the fact that all members of a union share the common memory location. This means that the value of one member will overwrite the value of another member if the union is not set to the correct member. Unions are typically used when you want to conserve the memory space by using a single block of memory to store multiple variables of different types, but only one of them is used at a time.

For example, the following code defines a union called number that can hold an int, a float, or a double:

union number

{

int i;

float f;

double d;

};

Both structures and unions can be used in a variety of ways in C programming, such as passing them as function arguments or using them as members of other structures or unions, and it is important to use them correctly to make the most of their capabilities.

User defined data types

With user-defined data types in C, programmers may combine different data types or build new custom data types on pre-existing data types. These user-defined data types offer a mechanism to group similar information and actions into a single unit, improving the organization, reuse, and upkeep of the code. Structures and enumerations are the two main approaches used in C to build user-defined data types.

Structures

You may develop a composite data type in C that unifies linked variables under a single name using structures (struct). With the struct keyword and the member variables enclosed in curly brackets, a structure may be defined. Here is an example:

//Define a structure representing a point in 2D space

struct student

{

int Roll_No;

char nm[20];

float marks;

};

// Create variables of the structure type

struct student s;

// Access and assign values to structure members

s.Roll_No = 25;

s.nm = "Zain"; //or strcpy (nm,"Zain");

s.marks=39;

Enumerations

Enumerations (enum) can be used to define a collection of named constants. Enumerations provide a way to express a collection of related variables. You may define an enumeration by using the enum keyword together with the constant names surrounded in curly braces. Here is an example:

#include <stdio.h>

int main ()

{ // Define an enumeration

enum Fruits

{

Apple,

Banana,

Cherry,

Dates

};

enum Fruits fav; // Declare enumeration type

fav = Dates; // Assign a value from the enumeration

switch (fav)

{ // Use the enumeration constants in switch statements

case Apple:

printf ("Not my Favorite");

break;

case Banana:

printf ("Not my Favorite");

break;

case Cherry:

printf ("Not my Favorite");

break;

case Dates:

printf ("My Favorite");

break;

}

return(0);

}

Output:

My Favorite

We may design unique data structures and named constants that meet the precise requirements of your program using user-defined data types in C, such as structures and enumerations. With the higher degree of organization and abstraction that these data types offer, we can create more modular and enduring programs. User-defined data types improve the readability and effectiveness of the C program by enclosing relevant data and activities.

In C programming, user-defined data types can be created using typedef and struct.

The typedef keyword assigns an alternate label for an existent data type. This can be useful for making code more readable or creating a new data type with a more meaningful name.

For example, the following code creates a new data type called myint that is equivalent to int:

typedef int myint;

The struct keyword is used to create a new user-defined data type called a structure that can contain multiple variables of different data types. Each variable in a struct is called a member and is accessed using the dot notation.

For example, the following code defines a struct called person that contains three members, name, age, and address:

struct person

{

char name [20];

int age;

char address [50];

};

We can also create a new type of name for struct using typedef.

For example:

typedef struct person Person;

In this way, we can create an instance of the struct person using the new type name Person:

Person p1;

User-defined data types can be used in a variety of ways in C programming, such as passing them as function arguments or using them as members of other structures or unions, and it is important to use them correctly to make the most of their capabilities.

Let us examine Structure and Union data types in detail.

Structures

Arrays allow the defining of different types of variables that can hold several data items of the same data types (homogenous). Similarly, structure is another user-defined data type we can use in C to combine data of different kinds.

Structures are used to represent a record. Say if we want to keep track of employees in a company. We might want to keep up with the following attributes about each employee:

Emp_ID

Name

Parentage

Address

Phone_No

Salary

It is convenient to organize related data by defining a new data type with various members by declaring a structure in C. In a program, things or items like a person, a student, or an employee are frequently represented by structures. They give you a means to collect related data and work with it as a single entity.

Declaring a structure

A structure can be declared in C using the struct keyword, followed by the structure’s name and members. The following is how to declare a structure in C:

struct Employee

{

char name[20];

int age;

float salary;

};

In this code, we declare a structure called Employee with three members: name (character array), age (integer), and salary (float).

Once the structure is declared, you can create variables of that structure type:

struct Employee e1;

struct Employee e2;

In the code above, the variables e1 and e2 are of the Employee structure type. To hold the values of the structure members, each variable will have its own memory area.

Each member definition is a characteristic variable declaration, such as int xyz, float mno, or any other satisfactory variable declaration, and the structure tag is optional. We can optionally specify one or more structure variables before the last semicolon at the conclusion of the structure declaration, as shown below:

struct Emp

{

char nm [20];

int age;

float sal;

} e1;

Here, we have created a variable e1 just before terminating the definition of the structure.

Accessing structure members

We make use of the member access (dot) operator to gain access to any member of a structure. The structure variable name and the structure member that we want to access are separated by a dot, which is the member access operator. For example, in the above declared structure, Employee, we may access the members as:

=================== e1 =================

e1.age = 25;

e1. sal = 1.75;

strcpy (e1.nm, "Omar Zain");

=================== e2 =================

e2.age = 30;

e2. sal = 1.80;

strcpy (e2.nm, "Ali Imad");

In the above code, we access the members of e1 and e2 using the dot operator (`.`). We assign values to the nm, age, and sal members of each structure variable using the assignment operator (`=`) and the strcpy function for the name member (because we have to initialize a string):

#include <stdio.h>

struct Emp

{

char nm [20];

int age;

float sal;

} ;

int main ()

{

struct Emp e1, e2; // Declare e1 and e2 of type Emp

printf ("Input Name, Age and Salary of First Employee\n");

scanf ("%s%d%f", e1.nm, &e1.age, &e1.sal);

printf ("Input Name, Age and Salary of 2nd Employee\n");

scanf ("%s%d%f", e2.nm, &e2.age, &e2.sal);

printf ("Name\t\tAge\t\tSalary\n"); // Heading

printf ("%s\t\t%d\t\t%f\n", e1.nm, e1.age, e1.sal);

printf ("%s\t\t%d\t\t%f\n", e2.nm, e2.age, e2.sal);

return (0);

}

Output:

Input Name, Age and Salary of First Employee

Zain

30

90000

Input Name, Age and Salary of 2nd Employee

Imad

28

85000

Name Age Salary

Zain 30 90000.000000

Imad 28 85000.000000

Access structure members through a pointer

Using the arrow operator (‘->’) in C, we can access structure members through a pointer. You may dereference a pointer to a structure and access its members with the arrow operator. The following describes how to use a pointer to access structural members:

Define a structure:

struct Emp

{

char nm [20];

int age;

};

Declare a pointer to the structure and allocate memory:

struct Emp *p;

p = (struct Emp *) malloc (sizeof (struct Emp));

Access structure members through the pointer:

strcpy(p->nm, "Zain");

p->age = 25;

Using the arrow operator (->), we can access the members of the emp structure through the p pointer.

Retrieve and use the structure members:

printf ("Name: %s\n", p->nm);

printf ("Age: %d\n", p->age);

We may extract the values of the structure members and utilize them as needed by dereferencing the pointer and using the arrow operator. Here, we use printf to print the members of nm and age.

After you use the structure and its members, remember to free the allocated memory using free to release the memory resources.

Accessing structure members through a pointer using the arrow (->) operator is a common practice in C, especially when working with dynamically allocated structures or passing structures to functions. It allows you to conveniently access and modify the members of a structure without needing to dereference the pointer explicitly:

#include <stdio.h>

struct Employee

{

char name [20];

int age;

float salary;

};

int main ()

{

struct Employee *p;

p = (struct Employee *)malloc(sizeof(struct Employee));

printf ("Input Name, Age and Salary of First Employee\n");

scanf ("%s%d%f", p->name, &p->age, &p->salary);

printf ("Name\t\tAge\t\tSalary\n"); // Heading

printf ("%s\t\t%d\t\t%f\n", p->name, p->age, p->salary);

return (0);

}

Output:

Input Name, Age and Salary of First Employee

Usmaan

30

95000

Name Age Salary

Usman 30 95000.000000

Using arrow (->) operator to access structure members

To access the members of a structure using a pointer to that structure, you must use the operator as follows:

#include <stdio.h>

struct std

{

int rn[50];

char nm[50];

char std[100];

};

void print (struct std *); // function prototype

int main ()

{

struct std s, *p;

p =&s;

printf("Input Roll No., Name, Class of student\n");

scanf("%d%s%s", &p->rn, &p->nm, & p->std);

print(p);

}

void print (struct student *p)

{

printf ("%d\t%s\t%s\n", p->rn, p->nm, p->std);

}

Array of structures

In C programming, an array of structures is a collection of structures that are grouped together under a single name. Each element in the array is a structure and can be accessed using an index, just like an array of basic data types.

To define an array of structures, we first need to define the structure and then create an array of that structure type.

For example, the following code defines a struct called person that contains three members, name, age, and address:

struct person

{

char name[20];

int age;

char address[50];

};

Then, we can define an array of the struct person:

struct person people[10];

This will create an array called people that can hold up to 10 elements, each of which is a person structure.

You can also define and initialize an array of structures in one step by using an initializer list. For example:

struct person people [] =

{

{"Zain", 25, "New York"},

{"Imad", 30, "Los Angeles"},

{"Usman", 35, "Chicago"}

};

Individual elements of the array can be accessed using the array subscript operator ([]). For example, the following code will print the name of the first person in the array:

printf ("Name: %s\n", people[0].name);

An array of structures can be useful when you need to work with multiple instances of a complex data type and perform operations on them as a group, such as sorting or searching:

#include <stdio.h>

struct emp

{

char id [50];

char nm [50];

char par [50];

char adr [100];

};

int main ()

{

struct emp e[10];

int i, n;

printf ("Input No. of employees to be recorded\n");

scanf ("%d", &n);

for (i=0; i<n; i++)

{

printf ("Input Id, Name, Parentage and address of Employee\n");

scanf ("%s%s%s%s", e[i].id, e[i].nm, e[i].par, e[i].adr);

}

for (i=0; i<n; i++)

{ // print records

printf ("%s\t%s\t%s\t%d\n", e[i].id, e[i].nm, e[i].par, e[i].adr);

}

return (0);

}

Sort array of structures

We may utilize the traditional sorting methods discussed in the chapter on Arrays to sort the array of objects/ structures in C. The program below uses the Selection Sort method to sort a set of records/ structures:

#include <stdio.h>

#include <stdlib.h>

struct emp

{

int id;

char nm [50];

float mks;

};

int main ()

{

struct emp tmp, e[]=

{

{1,"Zain", 22},

{2,"Usman", 21},

{3,"Imad", 28},

{4,"Saad", 24},

{5,"saleem", 27}

};

int i,j, n=5;

printf("Unsorted records\n");

for (i=0; i<n; i++)

{ // print records

printf ("%d\t%s\t%f\n", e[i].id, e[i].nm, e[i].mks);

}

// selection sort

for (i=0;i<n-1;i++)

{

for(j=i+1;j<n;j++)

{

if (e[i].mks<e[j].mks) // descending Order - Merit

{

tmp=e[i];

e[i]=e[j];

e[j]=tmp;

}

}

}

printf("Sorted records\n");

for (i=0; i<n; i++)

{ // print records

printf ("%d\t%s\t%f\n", e[i].id, e[i].nm, e[i].mks);

}

return (0);

}

Output:

Unsorted records

1 Zain 22.000000

2 Usman 21.000000

3 Imad 28.000000

4 Saad 24.000000

5 saleem 27.000000

Sorted records

3 Imad 28.000000

5 saleem 27.000000

4 Saad 24.000000

1 Zain 22.000000

2 Usman 21.000000

Structures as function arguments

Structures can be provided as function arguments in C, enabling you to access and modify the data they contain. You may either pass a structure by value or by reference when giving it as a parameter to a function, as follows:

Passing a structure by value: When you pass a structure by value, the function receives a copy of the full structure. The structure in the calling code will not be impacted by any changes made to the structure within the function, according to this.

Here is an illustration:

#include <stdio.h>

struct room

{

int len;

int bred;

};

void printarea (struct area r)

{

printf ("Length= %d and Breadth = %d\n", r.len, r.bred);

printf ("Area= %d", r.len*r.bred)

}

int main()

{

struct room r = {30, 12};

printarea (r);

return 0;

}

struct student {

int rollno [50];

char name [50];

char class [100];

};

void print (struct student); //function prototype

int main () {

struct student s;

printf ("Input Roll No., Name, Class and Section of student\n");

scanf ("%d%s%s", &s.rollno, &s.name, & s.class);

print (s);

}

void print (struct student s) {

printf ("%d\t%s\t%s\n", s.rollno, s.name, s.class);

}

Passing a structure by reference: You can transmit a structure by reference using pointers to change the original structure or avoid duplicating a sizable structure. As a result, the function has direct access to the original structure and may alter it.

Here is an illustration:

#include <stdio.h>

struct room

{

int len;

int bred;

};

void printarea (struct room *r)

{ // pointer variable receives address

printf("Length= %d and Breadth = %d\n", r->len, r->bred);

printf ("Area= %d", r->len*r->bred);

}

int main ()

{

struct room r = {30, 12};

printarea (&r); // passing address of r

return(0);

}

It is possible to deal with intricate data structures and change their values within functions by passing structures as function parameters. You can choose to send structures by value or by reference (using pointers), according to your needs, to improve efficiency and effectively manage memory.

Nested structures

In C, we can use a structure as a member of another structure – a structure within a structure is referred to as a nested structure. Nesting structures in C relate to the idea of enabling the construction of intricate data structures with hierarchical connections. A structure member or field is another name for the nested structure. Nested structures offer a mechanism to arrange and aggregate similar data into a bigger data structure, improving the administration and organization of complicated data.

We may design complicated data types by layering one structure inside another utilizing the functionality that C provides. For instance, we could need to keep an employee’s entity’s address in a structure. Street, state, city, and pin code are additional possible subparts of the attribute address. Therefore, to save the employee’s address, we must place it in a different structure and nest the structure address within the structure employee.

Consider the following example:

struct Adr

{

char str [50];

char city [50];

char state [50];

};

struct Emp {

char nm [50];

int age;

struct Adr ad;

};

Accessing nested structure

We can access members of the nested structure by using the dot operator, like in the case of the non-nested members.

//Outer_Structure.Nested_Structure.member as given below:

//emp.adr.street

#include <stdio.h>

int main ()

{

struct Emp emp = {"Omar Zain", 30, {"Smartstreet", "Srinagar", "JK"}};

printf ("Name: %s\n", emp.nm);

printf ("Age: %d\n", emp.age);

printf ("Address: %s, %s, %s\n", emp.ad.str, emp.adr.city, emp.adr.state);

}

Here, instead of having street, city, and state as members of the employee structure, we made a composite structure called adr and placed it inside the Employee structure as a member of the structure:

#include <stdio.h>

struct dt

{

int dd;

int mm;

int yy;

};

struct Emp {

int id;

char nm [50];

struct dt dob;

};

int main()

{

struct Emp emp = {"Omar Zain", 30, {"Smartstreet", "Srinagar", "JK"}};

printf ("ID: %d\n", emp.id);

printf ("Name: %s\n", emp.name);

printf ("Birth date: %d/%d/%d\n", emp.dob.dd, emp.dob.mm, emp.dob.yy);

return (0);

}

In the above example, we have defined two structures: dt and employee. The dt structure represents a date, with dd, mm, and yy (representing Day, Month, and Year respectively) as its members. The Emp structure represents an employee, with id, nm, and dob as its members. The dob member variable is itself a structure of type dt.

In the main function, we create an employee structure variable and initialize its members. We then print the values of the member variables using the printf function.

Embedded structure

Another way to use a nested structure is to use an embedded structure, which allows us to declare a structure inside another structure, requiring fewer lines of code. The restriction with the embedded version of nested structures is that they cannot be used in multiple data structures.

Consider the following example in which we have declared the date structure within the body of the Employee structure, and it therefore becomes a member of the Employee structure, which would not be accessible to the structures other than the Employee structure:

Struct Emp

{

int id;

char nm [20];

struct Dt

{

int dd;

int mm;

int yy;

} dob;

} emp;

Note: The method of accessing members of an embedded structure would be same as the method discussed above, that is, we can use the ‘.’ Operator to access the nested structure members.

Unions in C

A union is an aggregate/ composite data type in the C language that enables us to combine variables of various data types under a single name. A union may only keep a single of its member variables in memory at any given moment, in contrast to structures, which simultaneously store all of its member variables in memory. A union is given a memory equal to the size of its biggest member. All of a union’s members share the same memory location; therefore, modifying one member can affect the value of other members. Unions are frequently used when you need to save memory or when you need to share a memory location across two different data kinds. When gaining access to a union’s members, you must exercise caution since gaining access to the incorrect member may have unanticipated consequences. Unions are often employed in low-level computer programming for operations like dealing with hardware registers and serializing data.

Syntax of unions

Similar to how a structure is defined in C, a union is also defined using the union keyword rather than struct. The general syntax for declaring a union is as follows:

union UnionName {

member1;

member2;

...

};

In this syntax, UnionName is the name of the union, and member1, member2, and so on represent the members or fields of the union. Each member can have a different data type.

Usage of unions

Unions are a data structure in programming that allows different data types to be stored in the same memory space. They are useful when you must represent a single value that can be interpreted in multiple ways, providing memory efficiency and flexibility in handling different data types. The following are the uses of unions:

Memory optimization

When several members of a structure are not required at once, unions are frequently employed to conserve memory. When opposed to utilizing distinct variables for each member, a union can save memory since the memory allocated for it is equal to the size of its largest member. When working with constrained memory resources or embedded devices, this can be quite helpful.

Type conversion and interpretation

Unions offer a means of interpreting a single memory region as several kinds. When working with data of various sorts that are housed in the same memory region, this is extremely helpful. You may see or alter the underlying data in several different ways by gaining access to various union members. However, to prevent unexpected effects or type-related problems, it is crucial to treat unions carefully.

Tagged union - discriminated union

A union that has an additional member known as a tag or discriminator is referred to as a tagged union, also known as a discriminated union. This tag makes it easier to determine whether a union member is legitimate or active at the moment. The representation of data structures having various options, such as variant types or data that may take on different representations, is frequently done using tagged unions.

Considerations and limitations

The following are the limitations of Unions in C:

Overwriting data

Due to the fact that they all occupy the same memory region, changing one member of a union might change the value of the other members. To avoid unexpected behavior or data damage, it is essential to make sure that data is handled and synchronized properly.

Limited simultaneous access

Only one union member can ever be accessed or utilized at once because of the shared memory space. The values of other members become ambiguous when the value of one member is altered. It is critical to keep track of which member is active at any one time to prevent unauthorized access to or modification of other members.

Padding and alignment

Based on the architecture and compiler settings, unions may be subject to padding and alignment concerns, much like structures. The union’s size and memory configuration may change if padding is introduced to align the union’s members.

Type safety

Unions provide flexibility in data access, but they also raise the possibility of type-related mistakes. To prevent unexpected effects or violations of type safety, it is essential to guarantee correct type handling and casting.

Examples of union usage

Following are the examples of Unions usages:

Type conversion

Unions can be used to reinterpret data or change its type. For instance, you may interpret a series of bytes as various kinds, such as characters, floating-point values, or integers, using a union.

Memory sharing

Unions can be used to exchange memory amongst several sorts of variables.

Defining a union

A union is a unique data type that may be used in C programming to hold several variables in the same memory region. A union allocates enough memory to contain the biggest member variable instead of allocating memory for each member variable separately, as in a structure.

union [tag]

{

members of union

} [Union variables];

Each member definition is a typical variable definition, such as int i, float f, or any other acceptable variable definition, and the union tag is optional. You can optionally specify one or more union variables before the last semicolon after the union specification. Here is the way you would define a union type named UData, having 3 members int type x, float type y, and string type z:

union UData

{

int x;

float y;

char z [20];

} d;

Now, a variable of UData type can store an integer, a floating-point number, or a string of characters. It indicates that several types of data may be stored in a single variable, that is, the same memory address. Depending on your needs, you may utilize any built-in or user-defined data types inside a union.

A union will have enough space in its memory to accommodate its largest member. For instance, since 20 bytes is the maximum amount of memory a character string may occupy, the data type will take up 20 bytes in the example above. The following illustration shows how much memory the union used overall:

#include <stdio.h>

union UData

{

int x;

float y;

char z [20];

};

int main()

{

union UData d;

printf ("Memory size occupied by data: %d\n", sizeof (d));

return (0);

}

Output:

Memory size occupied by data: 20

The Table 7.1 marks differences between Structure and Union in C, taking various criteria like memory, accessing members, and initialization of membersand more into consideration:

Structure

Union

The keyword union is used to define a union.

A keyword struct is defined as a structure.

The size of the union is equal to the size of the largest member.

Size equates to greater than or equal to the sum of the sizes of its members.

Memory allocated to the union is shared by members of the union.

Every member is assigned a unique storage area of location.

Altering the value of any of the members will alter other members’ values. Only one member can be accessed at a time.

Altering the value of a member will not affect other members of the structure.

Only the first member of the union can be initialized.

Several members of a structure can be initialized at once.

Table 7.1: Structure versus Union

Accessing union members

Using the member access operator (dot), we are able to access any union member. The union variable name and the union member that we want to access are separated by a period (.), which is the member access operator. When defining variables of the union type, the term union is used. The example below demonstrates how to include unions in a program:

#include <stdio.h>

union std {

int rn;

float mks;

char nm [20];

};

int main () {

union std s;

s.rn = 10;

s.mks = 220.5;

strcpy (s.nm, "Saleem");

printf ("Id: %d\n", s.rn);

printf ("Marks: %f\n", s.mks);

printf ("Name: %s\n", s.nm);

return (0);

}

Output:

Id: 1701601619

Marks: 69767112807043203000000.000000

Name: Saleem

The final value allocated to the variable has taken up the memory address, as can be seen, the values of the i and f members of the union have been changed. This is why the value of the nm member is being printed so clearly. Let us take a second look at the same example, this time using one variable at a time, which is the major benefit of unions:

#include <stdio.h>

union std {

int rn;

float mks;

char nm [20];

};

int main () {

union std s;

s.rn = 10;

printf ("Id: %d\n", s.rn);

s.mks = 220.5;

printf ("Marks: %f\n", s.mks);

strcpy (s.nm, "Saleem");

printf ("Name: %s\n", s.nm);

return (0);

}

Output:

Id: 10

Marks: 220.500000

Name: Saleem

Now, all the members are getting printed the way they were initialized because one member is being used at a time.

Error handling and validation in structures/unions

The accurate and safe operation of your code depends heavily on error handling and validation when utilizing structures and unions in C. In the context of structures and unions, the following factors should be considered for error handling and validation:

Handling memory allocation errors

When dynamically allocating memory for unions or structures (using ‘malloc’, 'calloc’, and so on), always verify that the allocation was successful:

struct str* ptr = malloc(sizeof(struct str));

if (ptr == NULL)

{

printf("Memory allocation failed\n");

exit(1);

}

Member initialization

Make sure that a structure’s or union’s members are all correctly initialized, especially if you are utilizing dynamic memory allocation:

#include <stdio.h>

struct Person

{

char nm[30];

int age;

};

int main()

{

struct Person* p = malloc(sizeof(struct Person));

if (p == NULL) {

printf(, "Memory allocation failed\n");

exit(1);

}

strcpy(p->name, "Zain");

p->age = 30;

printf ("Name: %s\t Age = %d\n", p->name, p->age);

return (0);

}

Input validation

If our program relies on user input to provide data to structures /unions, we must validate the input to avoid errors or unexpected conduct:

struct Date

{

int day;

int month;

int year;

};

struct Date d;

printf("Enter day (1-31): ");

scanf("%d", &d.day);

if (d.day < 1 || d.day > 31) {

printf("Invalid day.\n");

exit(1);

}

Union discriminant handling

Prior to gaining access to members, confirm that any discriminant members that a union may have, are properly established and identify what kind of data the union holds:

union Data

{

int intVal;

double doubleVal;

char stringVal[20];

char type; // Discriminant

};

union Data d;

d.type = 'i'; // Indicates that intVal is valid

if (d.type == 'i')

{

d.intVal = 42;

printf("Value: %d\n", d.intVal);

}

else

{

printf( "Invalid type.\n");

exit(1);

}

Error reporting and exiting

When a mistake occurs, it is crucial to acknowledge it and respond accordingly. This might entail ending the application, issuing an error notice, and releasing memory that has been allotted.

if (errorCondition)

{

printf("Error occurred.\n");

exit(1);

}

Graceful decline

We could be interested in developing handling of errors or graceful degradation techniques as a means of managing exceptions or mistakes in a C program in a gentle manner:

Error codes: Use codes for errors to show if a function succeeded or failed. An error code is returned by a function that meets a problem; the calling function can examine this code and take the necessary action.

Descriptive error messages: These messages should be incorporated into your code. To assist in locating and resolving the problem, print or record an informative error message whenever one occurs.

Graceful recovery

Our software/ application should be able to bounce back from mistakes as gently as possible. For instance, in the event that a file action is unsuccessful, the software may try utilizing a different approach or default values.

Resource management: Make sure that resources, such as file handles or RAM, are released and handled appropriately. When managing files, use mechanisms like fclose() and free() for dynamic memory.

Implementing appropriate debugging and logging procedures will aid in the diagnosis of problems as they arise.

For example:

#include <stdio.h>

struct Student {

char nm[50];

int age;

};

int main() {

struct Student s;

// Error-checking

if (s.age > 0) {

printf("Student's age: %d\n", s.age);

} else {

printf("Age is invalid.\n");

}

return 0;

}

Conclusion

In conclusion, this chapter has covered important concepts related to structures and unions in C programming. We have explored how structures allow us to define custom data types that can hold multiple variables of different types, facilitating organized data storage. Moreover, we have learned about unions, which provide the ability to allocate memory that can be interpreted in various ways. By understanding how to create and access structure and union members, you now possess the skills necessary to effectively utilize these data structures in your C programs.

Exercises

What are structures in C programming?

What is the purpose of using structures in C programming?

How do you access members of a structure in C programming?

Write a program to declare and initialize a structure in C programming.

Can you have a structure inside another structure in C programming?

What is the size of a structure in C programming?

What is the difference between a structure and a union in C programming?

What is the size of a union in C programming?

Write a program to access members of a union in C programming.

What is the purpose of using unions in C programming?

What will be the output of the following code?

#include <stdio.h>

struct Point {

int x;

int y;

};

int main()

{

struct Point p1 = {2, 3};

printf("%d %d\n", p1.x, p1.y);

return 0;

}

Can a structure have a member that is a union in C programming?

What will be the output of the following code?

#include <stdio.h>

struct Time {

int hours;

int minutes;

};

int main() {

struct Time t1 = {4, 30};

printf("%d:%d\n", t1.hours, t1.minutes);

return 0;

}

What is the difference between a structure and a typedef structure in C programming?

Write a program to pass a structure to a function in C programming.

Can you return a structure from a function in C programming?

Write a program to compare two structures in C programming.

How do you copy one structure to another in C programming?

How do you compare two unions in C programming?

Can you have an array of structures or unions in C programming?

Join our book’s Discord space

Join the book’s Discord Workspace for Latest updates, Offers, Tech happenings around the world, New Release and Sessions with the Authors:

https://discord.bpbonline.com

CHAPTER 8File Handling

Introduction

In this chapter on file handling in C, we will delve into the essential concepts and techniques for managing data in files. We will begin by exploring the data hierarchy, and understanding the building blocks of data organization from bits to data marts and data warehouses.

In the subsequent sections, we will focus on files, understanding their significance, and exploring different types. Then, we will dive into binary file handling and uncover the intricacies of working with text files in C. You will also gain insights into file modes, file operations, and various file access methods, including sequential, random, direct, and stream file access.

Error handling is a critical aspect of file handling, and we will discuss how to manage errors efficiently, utilizing the global variable errno. You will also find a practical example of errno usage.

Let us embark on this journey to master the art of file handling in C!

Structure

The chapter will discuss the following content:

Introducing file handling

Data hierarchy

Types of files

Binary and text file in handling in C

File modes

File operations

Random access files

Direct file access

Stream file access

Error handling in files

File descriptors

Objectives

In this chapter, we will explore file handling in C, an essential skill for working with data storage and retrieval. You will learn about the data hierarchy, file types, and their significance in programming. We will cover binary and text file handling, including file modes, operations, and access methods. Additionally, we will focus on error-handling techniques using the errno global variable. By the end of this chapter, you will be proficient in managing data through files and equipped to build robust file-based applications in C.

Introducing file handling

Programming tasks that include reading data from or writing data to files fall under the category of file handling (Figure 8.1). It enables better orderly and permanent information storage and retrieval for applications. For many operations, including data storage, data processing, configuration management, and others, file handling is a must. Here are a few typical programming file-handling operations, as shown in Figure 8.1:

Figure 8.1: File read and write operations

Programming languages like Python, C/C++, Java, Ruby, and so on provide the service of file management. Language differences may affect the mechanics of file processing, but the fundamental ideas are the same. You prevent data loss and security risks and make sure you handle files appropriately and responsibly at all times.

Data hierarchy - layers of data

A conceptual framework called data hierarchy divides data into many levels/ layers based on how complicated they are and how they relate. It offers an organized method for managing and analyzing data, enabling companies and organizations to manage and use their data resources efficiently. The data level hierarchy is explored below, from the lowest to the highest, while stressing their unique qualities and importance. The data structure is explained in Figure 8.2:

Figure 8.2: Bit, Nibble, and Byte representation

Bit

The bit, which stands for the fundamental computing unit of information, is located at the lowest level of the data structure. Binary data, represented by the values 0 or 1, may be stored in a bit. It forms the framework for data representation and processing and acts as the foundation for all other data kinds.

Byte

The smallest addressable storage unit in most computer systems, a byte, is made up of 8 bits. Bytes are frequently employed when representing characters like letters, numbers, and symbols using different character encoding methods like ASCII or Unicode.

Field

In a database or dataset, field is a group of bytes that denotes a particular property or feature of an item. Data items of a same sort, such as names, addresses, or dates, are fields and records stored and organized using fields. They are the fundamental parts of records and lay the groundwork for storing, organizing, and retrieving data, as shown in Figure 8.3:

Figure 8.3: Data fields in (Employee) table

Record

A record is a group of connected fields that together describe all of the information about a certain item or object. For instance, a record in a client database can include fields like name, address, phone number, and email. Structured data may be stored and retrieved from records, allowing for effective data processing and analysis.

File

A file is a logical container for a group of associated records. It reflects a broader collection of data and is frequently linked to a particular data object or application. Within a computer system, files are used to organize and manage data, enabling effective data storage, retrieval, and modification of data in an efficient way, as shown in Figure 8.4:

Figure 8.4: Files and records

Database

A database is a structured group of linked and arranged files that enables effective data management. Large amounts of data may be stored, retrieved, and managed using this single repository. Data integrity, security, and concurrency control are built into databases to support them, assuring the correctness and dependability of the information stored.

Data warehouses

Data warehouses are specialized databases that combine data from multiple sources located throughout a business. This system offers a consolidated picture of the organization’s data and is geared for analytical processing. Businesses may obtain insights and make wise strategic decisions using data warehouses for reporting, mining, and decision support.

Data mart

A data mart is a data warehouse division specializing in one particular field, such as sales, marketing, or finance. It contains a subset of information pertinent to a certain division or user group, enabling more specialized analysis and reporting. Comparatively speaking, data marts offer a more user-friendly and effective means of accessing and analyzing data than the larger data warehouse.

From the smallest unit of data, the bit, to the most complete data warehouses and data marts, the data hierarchy offers a systematic framework for organizing and managing data. Organizations may get useful insights and make wise decisions thanks to the effective storage, retrieval, and analysis of data made possible by each level of the hierarchy. Anyone involved in data management must understand the data hierarchy because it lays the groundwork for effective data handling and usage in today’s data-driven world.

Files

A key component of C programming is file access, which enables the manipulation of data contained in files. Reading from and writing to files, modifying file data, and controlling file operations are all part of C file programming. An understanding of file programming is necessary for activities like reading configuration files, analyzing sizable datasets, and persistently storing program data. This chapter on file programming in C covers in-depth information on file handling, opening and closing files, reading and writing data, and error handling.

Importance of files

File Input and Output (I/O) is needed in C programming because it allows a program to store and retrieve data on a permanent storage device, such as a hard drive or a USB drive. This allows the data to persist even after the program has terminated and can be used to save the state of the program or to load data that the program needs to operate on.

File I/O also allows a program to read and write data from/to external sources such as text files, images, and other multimedia files. This makes it possible to work with a wide range of data types and to share data with other programs or systems.

Additionally, file I/O is also useful for logging and debugging, as it allows a program to write information about its execution to a file for later analysis.

In C programming, the entire data is lost when a program is ended. It takes a lot of time to enter all the data if we want to keep a lot of it. However, if a file is generated, a few commands can be used to retrieve this data. In C programming, file I/O is performed using a set of functions from the standard library.

The file access mechanism, which controls how files are saved, arranged, and accessed by applications and users, is a crucial part of an operating system.

The following examples illustrate the significance of the file access mechanism in operating systems:

Efficiency: A computer system’s efficiency may be greatly impacted by the file access technique since it affects how quickly files can be accessed and how rapidly data can be written to or retrieved from storage.

Data integrity: By ensuring that data is appropriately saved and retrieved, the file access technique protects the integrity of the data kept in files.

Security: By restricting who may read, change, or delete files, the file access technique can help protect the security of files.

Resource management: The file access mechanism is crucial to resource management since it aids the operating system in allocating resources and managing disc space.

File modes

In C programming, file modes determine the intended operation on a file when opening it. The file mode is specified as a string parameter in the fopen() function, indicating whether the file will be opened for reading, writing, or appending, among other options. Here are the commonly used file modes in C:

Read mode (r): Read-only mode opens the file. The fopen() function must be used; otherwise, it will fail and return NULL. You can read data from the file in this mode but not change its contents.

Write mode (w): Opens the file for writing in the write mode. The file’s contents are truncated (cleared) if it already exists. If the file does not already exist, one is created. This mode is used to write data to a file, overwriting any previously stored information.

Append mode (a): Starts the document in writing mode with the cursor at the end of the document. New information is added to the existing file’s content if it already exists. If the file does not already exist, one is created. Use this mode to add data to a file’s end without replacing any already present information.

Read-only and read-write (r+): Opens the file in read-only and read-write modes. The fopen() method will not work if the file does not already exist. You can read and edit the file’s contents using this mode. The very first file location is at the start.

Read and write mode (w+ or truncate file mode): Allows both reading and writing to be done on the same file. The file’s contents are truncated (cleared) if it already exists. If the file does not already exist, one is created. Start with an empty file and use this mode to read from and write to it.

Append mode (a+) read and append mode: It opens the file for both reading and writing with the cursor at the end of the file. New information is added to the existing file’s content if it already exists. If the file does not already exist, one is created. Use this mode to add data at the end without replacing the current data while reading from the file.

It should be noted that each mode behaves differently while opening a file, and the choice of mode relies on the program’s needs and intended use. Additionally, you may specify binary mode, which is used to handle binary files, by adding the letter b to any mode (for example, rb or wb+).

File operations

File operations in C programming include a variety of operations on files, including opening, closing, reading, writing, and placement inside the file. The functions offered by the common C library are used to perform these actions. The typical file operations in C are listed below:

Opening a file

The fopen() function, which accepts the file path and the file mode as arguments, is used to open a file. The file stream is represented by the FILE* pointer that the function returns. The letters r, w, and a represent mode, in which the file is supposed to be opened and stand for reading, writing, and adding, respectively. Needless to say, that before performing any operations on a file, it must be opened first; hence, the primary step in file handling is opening a file.

fopen (char * filename, char * mode)

where:

filename is the name of our file

mode can have one of the above-mentioned modes.

Note: If you are going to handle binary files, then you will use the following access modes instead of the above-mentioned ones − rb, wb, ab, rb+, r+b, wb+, w+b, ab+, a+b.

#include <stdio.h>

int main ()

{

FILE* fp = fopen ("data.txt", "w");

if (fp == NULL)

{

printf ("File cannot be opened\n");

return (1);

}

// Do file operations and close the file

return (0);

}

Closing a file

It is important to close a file using the fclose() function when you are done working with it. A file is closed to guarantee that any outstanding data is written to it and to free up system resources related to the file stream. Data loss or resource leakage may occur if a file is not properly closed.

To close a file, use the fclose() function. The prototype of this function is:

int fclose (FILE *fp)

where:

fp: is pointer to the FILE structure representing the file (file pointer).

For example:

fclose (fp)

When a file is successfully closed, the fclose(-) function returns zero, or End-of-File (EOF) if there is a problem. This function shuts the file, releases any memory needed for the file, and flushes any data that is still pending in the buffer to the file. The header file stdio.h contains a definition for the constant EOF.

Writing to a file

Functions like fputc(), fputs(), fwrite(), or fprintf() are used to write data to files. You can write characters, strings, or data blocks to the file using these functions. The format and nature of the data to be written determine the function to be used.

fputc(): The function fputc() writes one character to the output stream referenced by the file pointer. It returns the written character written on success; otherwise, EOF if there is an error.

Syntax:

int fputc(char , FILE *);

Example:

int fputc(int c, FILE *fp);

where:

c: This is the character we want to write to the file stream. It should be of type int to accommodate all possible character values, including EOF.

fp: This pointer to the FILE structure representing the file (file pointer).

fputs() returns the character written as an unsigned char cast to an int.

fputw(): The fputw() function is used to print numbers onto a file. The fputw() function is not among standard C library functions. It does not exist in the standard C library functions provided by ANSI C. We may instead use the fprintf() function to format and write the value to the file stream.

Syntax:

int putw(int , FILE *);

Example:

int putw(int w, FILE *fp);

where:

w: This is the integer we want to write to the file stream.

fp: This is a pointer to a FILE, which represents the file stream we want to write to.

fputs(): We can use the fputs() function to write a null-terminated string to a stream.

Syntax:

int fputs (char *, FILE *);

Example:

int fputs (char *s, FILE *fp);

where:

s: This is a pointer to the null-terminated string that you want to write to the file.

fp: This is a pointer to a FILE object, which represents the file we want to write to. It could be a file opened in write mode, such as “w”, “a”, or “w+”.

fprintf(): fprintf() function is used to output formatted data to a file. It allows you to apply formatting and write several types of data (such as text and numbers) to a designated file.

Syntax:

int fprintf(FILE *, char *, argument list);

Example:

int fprintf(FILE *fp, char *format, argument list);

where:

fp: A pointer to the FILE object that represents the file you want to write to. It can be a file pointer returned by functions like fopen() or standard streams like stdout (for console output) or stderr (for error messages).

format: A format string that specifies how the data should be formatted and written to the file. It can contain format specifiers like (%d, %f, %s, and so on) and are replaced by the corresponding values provided in the argument list.

argument list: This represents the list of arguments we want to write to the file. They should correspond to the format specifiers in the format string.

fwrite(): The fwrite() function in C is used to write binary data to a file. It allows you to write a specified number of elements, each with a specific size, to the file in a single operation.

Syntax:

size_t fwrite(void *, size_t, size_t count, FILE *fp);

Example:

size_t fwrite(void *ptr, size_t size, size_t count, FILE *fp);

Where:

ptr: Pointer to the data to be written.

size: Size (in bytes) of each element to be written.

count: Number of elements to be written.

fp: Pointer to the file stream to write the data to return value.

The fwrite() function returns the number of elements successfully written to the file. If the return value is less than count, an error occurs during the write operation.

Reading from a file

Functions like fgetc(), fgets(), fread(), or fscanf() can be used to read data from a file. You can read characters, lines, or blocks of data from the file using these methods. The required format and kind of data to be read determine the function used.

fgetc(): This function reads a single character from the file.

Syntax:

int fgetc(FILE *);

Example:

int fgetc(FILE *fp);

Where, Fp is pointer to a FILE object representing the file from which you want to read a character.

The function returns an integer value, the character read from the file as an unsigned char cast to an int or EOF.

fgets(): In C, a line of text can be read either a file or standard input (often the keyboard) and stored in a string, using the fgets() function.

Syntax:

char *fgets(char *, int , FILE *);

Example:

char *fgets(char *s, int n, FILE *fp);

Where:

s: A reference to the string wherein the read line will be placed.

n: The maximum character count to read, including the null-terminator. It defines the str buffer’s size.

fp: A reference to the FILE, the input stream to be read. This might be a file opened with fopen() or a file pointer, like stdin, for standard input.

fscanf(): The function in C is used for reading formatted input from a specified file stream and store it in variables based on the specified format.

Syntax:

int fscanf(FILE *, char *, argument list);

Example:

int fscanf(FILE *fp, const char *format, argument list);

Where:

fp: A pointer to the FILE structure representing the file you want to read from.

format: A format string specifies how the data should be interpreted and extracted from the file. It consists of format specifiers that define the expected data types and how they are formatted in the file.

argument list: This represents a list of arguments we want to write to the file. They should correspond to the format specifiers in the format string.

The function returns the number of successfully read items (matching format specifiers) or `EOF` if an error occurs or the end of the file is reached.

fread(): The function in C is used for reading binary data from a file. It is part of the C Standard Library’s file input/output functions and is typically used with binary files to read a specified number of binary objects (usually bytes) from a file into a memory buffer. Here is the syntax of the fread() function:

Syntax:

size_t fread(void *, size_t, size_t count, FILE *);

Example:

size_t fread(void *ptr, size_t size, size_t count, FILE *fp);

Where:

ptr: A pointer to the memory buffer where the data will be stored after reading.

size: The size (in bytes) of each object to be read from the file.

count: The number of objects to be read from the file.

fp: A pointer to the FILE structure representing the file to be read.

The fread() function reads size * count bytes of data into the memory buffer referred to by ptr from the supplied file. If the end of the file is reached before reading count items, it gives the total number of successfully read objects, which might be fewer.

File positioning

The file pointer can be moved within a file to a precise location using file positioning functions. Each byte in a file is given a location or offset, and files are viewed as collections of bytes. To maintain track of the current location within a file, C uses a file pointer. The byte that will be read or updated next is indicated by the file pointer.

In C, file positioning functions are used to traverse to certain locations of a file. The basic file positioning functions in C are:

fseek(): The fseek() function enables direct access to various file sections by allowing programmers to relocate the file pointer to a specific location inside the file.

Syntax:

int fseek (FILE *, long, int)

Example:

int fseek (FILE *fp, long offset, int origin)

Where:

fp is the file pointer.

offset is the number of bytes to move,

origin governs the reference position.

The reference position can be the beginning of the file (SEEK_SET), the current position (SEEK_CUR), or the end of the file (SEEK_END).

ftell(): The present file position indicator’s location inside the file is returned by the ftell() function. It takes one argument that is the file pointer associated with the file concerned and returns a {long} integer, which is the current file pointer’s byte offset from the beginning of the file.

Syntax:

long ftell(FILE * );

Example:

long pos = ftell(FILE *fp );

Where:

fp is the file pointer.

Pos is (long integer value) - the current position in the file.

rewind(): The rewind() function is used to reset the file position indicator to the beginning of the file. It takes one argument that is the file pointer associated with the file concerned and effectively moves the file position indicator to the start of the file.

Example:

rewind(fp);

Here, fp is the file pointer

fgetpos and fsetpos()

The functions are used to Get and Set File Position using fpos_t object.

The fgetpos function retrieves the present file position to stores it in an fpos_t object.

The fsetpos function sets the file position from an fpos_t object.

These functions are used for more advanced file positioning tasks.

Syntax:

fgetpos(FILE *, fpos_t);

fsetpos(FILE *, fpos_t);

Example:

fgetpos(fp, &p);

fsetpos(fp, &p);

Where:

fp is the file pointer

p is an `fpos_t` object.

For example:

fpos_t p;

fgetpos(fp, &p); // Get the current file position.

// ... Perform some operations ...

fsetpos(fp, &p); // Set the file position back to the saved position.

Error handling

It is crucial to appropriately manage errors while working with files. I/O functions, positioning functions, and functions that return error indicators include fopen() and errno. These functions also set the global errno variable to indicate problems. Use perror() or fprintf() to show error messages and deal with special situations.

File truncation

We can use the ftruncate() or truncate() methods to truncate or resize a file. You can change the file size using these routines to a desired length. When you need to eliminate previous data or condense the file size, truncating a file might be helpful.

File deletion

The remove() method can be used to remove a file from the file system. This function permanently deletes the file that was given. When utilizing this feature, caution should be used because file deletion is irrevocable. It is crucial to handle file operations with care, checking for mistakes and making sure each action is successful. When interacting with files, your program’s stability and dependability are enhanced by proper error handling.

Please be aware of the following factors:

Verify that the file you wish to remove is present at the place you have chosen.

We must make sure that our program has the permissions it needs to remove the file. To remove certain files on some systems, you might need elevated rights (such as being logged in as the administrator).

Use caution when using the remove function because it permanently deletes files. This function should only be used with the intention of deleting the file.

When employing file-related methods, error handling is crucial since they might fail for a variety of reasons (for example, file not found, inadequate permissions). We can identify and manage issues with the use of the perror function.

File locking

A file locking mechanism is used to stop several processes or threads from accessing or changing a shared file simultaneously. As a result, problems like race conditions and data corruption are avoided since it ensures that only one process may have exclusive access to the file at once.

The fcntl system call, in conjunction with the F_SETLK command, can be used to provide file locking in C. This enables software to lock or unlock a specific section of a file.

Type of files

There are numerous different file types that may be used in C programming for a variety of reasons. The following file formats are used the most frequently in C file programming:

Text files: Each character in a text file is represented by its corresponding ASCII value, allowing for the storage of data in a human-readable manner. Standard text editors make it simple to generate, modify, and read these files. Text files are frequently used in C programming to store and read textual data, including plain text data sets, log files, configuration files, and log files.

Binary files: Binary files hold information in a binary format, which exhibits the information’s unprocessed state. These files cannot be read by humans and must be properly interpreted using specialized software or tools. Complex data structures like photos, audio files, video files, or serialized objects are frequently stored in binary files. Low-level file operations are used to manage binary files in C programming.

Input/Output files: The standard input (stdin), standard output (stdout), and standard error (stderr) streams are connected to I/O files. These files offer a way to communicate with the system and the user. The standard output file (stdout) is used to show program output, whereas the standard input file (stdin) is used to take human input. The standard error file (stderr) is employed to show error messages and diagnostic data.

Temporary files: In order to hold intermediate or temporary data, temporary files are generated during program execution. These files are usually employed when processing vast volumes of data or for caching purposes. Temporary files are frequently automatically created and then removed after use. The tmpfile() and tmpnam() methods are often used in C programming to create and maintain temporary files.

Random access files: Random access files enable reading and writing data at any desired place by allowing direct access to any portion of the file. When you regularly need to alter or access data from particular points inside the file, this sort of file is appropriate. For databases or applications that demand effective data retrieval and change, random access files are frequently utilized. Random access files may be managed in C code by utilizing methods like fseek() and fwrite().

These file types are appropriate for various applications since they have varied functions and unique properties. For effective file management and data manipulation in C programming, it is crucial to comprehend the various file formats and how to use them properly.

Working with text files in C

Text files are commonly used for storing and manipulating human-readable data. In the C programming language, working with text files involves various operations such as creating, reading, writing, and manipulating text data. This note provides a comprehensive overview of working with text files in C, covering the essential file operations, techniques, and considerations.

Opening a text file

The fopen() function is used to open a text file. The function returns a file pointer. We specify the file mode as r when reading a file and w for writing data onto a file. If the file we specify for writing does not exist, it creates a new file with the specified name/path. We may also use a for appending data to an existing file.

Syntax:

FILE *fopen (char *filename, char *mode)

Reading text from a file

The fgets() function is used to read a line of text from a file.

Syntax:

char *fgets (char *, int, FILE *)

Example:

char *fgets (char *str, int num, FILE *fp)

Where:

str is the character array to store the read line,

num is the number of characters to read

fp is the file pointer.

The function returns NULL when the end of the file is reached.

Writing text to a file

We use the fprintf() function to write formatted text to a file.

Syntax:

int fprintf (FILE *, char *format, ...)

Example:

int fprintf (FILE *fp, char *format, ...)

Where:

fp is the file pointer,

format specifies the format of the text to be written.

... represents the data to be formatted and written.

The function returns the number of characters written or a negative value on failure.

Appending text to an existing file

We use this mode to open a file for appending text. This mode guarantees that new text is added at the end of the file, preserving existing content, if any.

Closing a text file

The fclose () function is used to close a text file.

Syntax:

int fclose (FILE *)

Example:

int fclose (FILE *fp)

Where, fp is the file pointer.

We must close a file that guarantees the release system resources and ensures data integrity.

Error handling

The process of error handling checks for any errors after performing file operations to handle exceptional situations. Functions like fopen(), fgets(), and fprintf() return NULL or a negative value on failure, which signifies an error. We may also use feof() and ferror() to check for end-of-file and error conditions, respectively.

Reading and writing line-by-line

We may use a loop in association with fgets() to read a file line-by-line until the end of the file is reached. In the meantime, process each line as needed.

We use fprintf() to write lines of text to a file, ensuring appropriate formatting.

Text file manipulation

We use string manipulation functions (strcpy(), strcat(), and more) to modify and manipulate text data read from a file. We also apply data processing techniques like tokenization and regular expressions to extract meaningful information from the text.

Working with CSV files

Comma Separated Values (CSV) files are commonly used for tabular data storage. We can read CSV files using fgets() and then parse the read lines using string manipulation or specialized CSV parsing libraries. We can also use fprintf() to write data in CSV format, ensuring proper comma separation.

Text and binary files are the two primary file kinds that may be handled in C programming. How data is expressed and stored inside these different file formats varies. The description of each file type is as follows:

Text files: Text files are readable by humans and used to store plain text data. There is a distinct character or symbol for each character in the file. Data is saved in text files as a series of characters, including alphabetic characters, numeric characters, symbols, and control characters. A text editor may generate, edit, and display text files.

Text-oriented C programming routines like fscanf() and fprintf() are commonly used to read and write text files. By treating the data as text, these methods let you read and write formatted data. Data intended to be understood as characters or strings can be stored and exchanged using text files.

Binary files: Binary files are files that hold data in a binary format, which translates information into individual bytes. Binary files can include non-textual data like numbers, structures, or graphics but are not meant to be understandable by humans. To maintain the precise representation of data, including the underlying structure and data types, binary files are utilized.

Binary-oriented methods like fread() and fwrite() are usually used in C programming to read and write binary files. By treating the file content as a series of bytes, these functions enable direct reading and writing of binary data.

When correct data representation is crucial for storing complicated data structures, serialization, and data durability, binary files might be helpful.

It is crucial to keep in mind that whereas text files may be opened and examined in a text editor, binary files call for certain procedures to accurately comprehend their contents. The kind of data and required functionality determine whether text or binary files should be used.

Binary file handling in C

Binary file handling in the C programming language allows programmers to read from and write to binary files, which contain non-textual data. Unlike text files, binary files store data in a raw and compact format, making them suitable for complex data structures and high-performance applications. The following section provides a comprehensive overview of binary file handling in C, covering file operations, reading and writing binary data, and considerations for working with binary files.

Opening and closing binary files

The fopen() function opens a binary file for reading or writing. We use the file mode as rb when reading a file or wb for writing in binary mode. If the file does not exist, it can be created using wb mode. The fclose() function closes a binary file after reading or writing operations are complete.

Reading binary data

The function is used to read a specified number of bytes from a binary file.

Syntax:

size_t fread(void *, size_t, size_t, FILE *)

Example:

size_t fread(void *ptr, size_t size, size_t count, FILE *fp)

Where:

ptr signifies the buffer to store the read data.

size represents the size of each element to be read.

count represents the number of elements to read.

fp is the file pointer.

After reading, the file pointer advances automatically to the subsequent position.

Writing binary data

The fwrite() function is used to write a specified number of bytes to a binary file.

Syntax:

size_t fwrite(void *, size_t, size_t, FILE *)

Example:

size_t fwrite(const void *ptr, size_t size, size_t count, FILE *fp)

Where:

ptr represents the data to be written.

size represents the size of each element to be written.

count represents the number of elements to write, and

fp is the file pointer.

The file pointer automatically propagates to the subsequent position after writing.

Moving the file pointer

The fseek() function is used to move file pointer to a stated position within the binary file.

Syntax:

int fseek (FILE *, long, int)

Example:

int fseek (FILE *fp, long offset, int origin)

Where:

fp is the file pointer

offset is the number of bytes to move,

origin governs the reference position.

The reference position can be the beginning of the file (SEEK_SET), the current position (SEEK_CUR), or the end of the file (SEEK_END).

Binary file size

The ftell() function is used when we have to return the current position of the file pointer within the binary file.

Syntax:

long int ftell (FILE *fp)

Example:

long int ftell (FILE *fp)

Where:

fp is the file pointer.

By finding the difference between the starting position (usually 0) and the current position, we can determine the size of the binary file.

Binary file error handling

We can check binary file error handling for errors during file operations by examining the return values of functions fread() and fwrite().

The feof() function also checks if the end-of-file indicator is set after a read operation.

The ferror() function is used to check if an error occurred during a file operation.

We may also use error handling techniques like conditional statements or error codes to handle errors, which may occur during file related operations, effectively.

Working with complex data structures

Binary file handling is particularly useful for working with complex data structures like structs or arrays. Serialize the data structures by writing their contents directly to a binary file using fwrite(). Deserialize the data structures by reading binary data from a file using fread().

Considerations for binary file handling

Binary files are not human-readable, so proper data organization and documentation are crucial for handling binary files. We must ensure platform compatibility by considering issues like endianness (byte order) when working with binary data. We should also avoid directly manipulating the binary file without proper precautions to prevent data corruption.

Binary file handling provides a strong mechanism in C for reading and writing non-textual data in a raw format. Binary file handling allows programmers to work with complex data structures by understanding the operations and functions associated with these files, which in turn help in achieving high-performance data processing. By following the considerations associated with handling binary files, developers can harness the capabilities of binary file handling and build robust applications that manipulate binary data effectively.

File access

File access, as used in computer programming, describes the procedures and tricks used to read from or write to files. There are several file access techniques that may be used, each with unique benefits and specialized functions. The sequential, direct, and stream kinds of file access are covered in general detail in this note.

The three main file access methods offered by C are:

Sequential access

Random or direct access

Stream access

Each kind offers special features that are suited to a particular scenario. We will be going over their features, principles, and actual implementations. It also explores important modes, functionalities, and factors to consider while using each sort of file access; the purpose is to provide readers with a full grasp of file handling in C and give them the skills necessary to effectively interact with various file formats.

Programming cannot function without files because they allow us to persistently store and retrieve data. Using file streams - represented by the FILE data type—in C programming simplifies file processing. Here, we will discuss various file-related topics, such as file modes, opening and closing files, reading from and writing to files, and error handling when it comes to working with files in C programming.

Sequential file access

The process of reading or writing data from or to a file in a consecutive way, progressing through the file from start to finish, is referred to as sequential file access in computer programming. In the context of the C programming language, this note gives a quick explanation of sequential file access, covering file opening, reading, writing, and closing operations.

When using a sequential file access method, data is read or written throughout a file, beginning at the start and terminating at the end. According to a linear progression, data is processed in the order that it occurs in the file. Important aspects of sequential file access are as follows:

Reading: Reading in the case of a sequential file begins at the file’s beginning and moves through it step by step until it reaches its finish.

Writing: Sequential file writing enlarges the file’s size when new data is written by appending it at the end of the file.

Benefits: Massive volumes of data can be read or written quickly and easily with sequential access. For processing massive datasets, log files, and file backups, it is commonly utilized.

Opening a file for sequential access

The fopen() function is used to open a file and returns a file pointer for subsequent file operations.

Example:

FILE *fopen (const char *filename, char *mode)

Where:

Filename is the name of the file to be opened.

mode specifies the access mode (‘r’ for reading, ‘w’ for writing, ‘a’ for appending, and combinations like ‘r+’, ‘w+’, ‘a+’).

In case the file does not exist when opening in write mode, a new file of the same name will be created.

Reading from a file

The function fgets() reads a line of text from the specified file. The function returns s if successful, otherwise NULL. We may also use the function feof() to check the end of the input stream. The end-of-file flag for the specified stream is shown by the feof() function.

Writing to a file

To write data to a sequential file, we use functions like fprintf() for formatted output, fputs() for writing strings and fwrite() for writing binary data.

// fprintf to write formatted data

fprintf(file, "The value is: %d\n", number);

// fputs to write a string

fputs("This is a line of text.\n", file);

// fwrite to write binary data

fwrite(data, sizeof(double), 5, file);

Moving the file pointer

By default, the file pointer advances automatically after each read or write operation, allowing sequential access.

If required, the file pointer can be moved explicitly using the fseek() function. Refer to the direct access section for more details.

Closing a file

The fclose() function closes the file and releases associated resources. Closing the file is important to ensure data integrity and free system resources.

Advantages

The advantages of sequential file access are discussed below:

Simplicity: Sequential file access is straightforward to implement and understand.

Efficient for large files: Sequential access can be efficient for reading or writing large files without the need to store the entire file in memory.

Suitable for certain data structures: Sequential access is well-suited for line-based text files, where data is processed line by line.

Limitations

The limitations of sequential file access are discussed below:

Random access limitations: Sequential access does not allow direct access to specific portions of a file without reading or skipping preceding data.

Inefficiency for specific operations: If frequent random access or modifications to different parts of a file are required, sequential access may not be the most efficient approach.

Sequential file access in C provides a simple and efficient way to read and write data from and to files in a sequential manner. It is particularly useful for handling large files or working with line-based text data. However, it has limitations in terms of random access and may not be suitable for certain scenarios that require frequent modifications or direct access to specific file positions. Understanding the characteristics and appropriate use cases of sequential file access can help developers effectively utilize file operations in their C programs.

Program examples

Below discussed are some examples of the basic programs for sequential files:

Creating a sequential file and writing data to it:

#include<stdio.h>

int main()

{

FILE *fp;

char chr;

fp = fopen ("s.txt","w");

fputc ('H', fp);

fputc ('E', fp);

fputc ('L', fp); // will print HELLO onto the file s.txt

fputc ('L', fp);

fputc ('O', fp);

fclose (fp);

fp = fopen ("s.txt","r");

while (!feof(fp))

{

chr = fgetc(fp);

printf ("%c", chr);

}

return (0);

fclose (fp);

}

Write a paragraph onto a file:

#include<stdio.h>

int main ()

{

FILE *fp;

char chr;

fp = fopen ("s.txt", "w");

while ((chr=getche ())!= "~") {

// write to file the characters till user presses '~' character

fputc (chr, fp);

}

fclose (fp);

fp = fopen ("s.txt","r");

while (!feof(fp)) {

chr = fgetc(fp);

printf ("%c", chr);

}

fclose (fp);

return (0);

}

Write numbers to a file:

#include<stdio.h>

int main ()

{

FILE *fpe, *fpo;

int nm;

fpe = fopen ("even.txt","w"); //open even. txt and odd.txt files for writing

fpo = fopen ("odd.txt", "w");

for (nm=1; nm<=20; nm++) {

if (nm%2 ==0)

putw ((char)nm, fpe);

//if even number, write to even file else write to odd file

else

putw ((char)nm, fpo);

}

fclose (fpe);

fclose (fpo);

printf("\nOdd Numbers:\n");

fpo = fopen ("odd.txt", "r");

while ( (nm = getw(fpo)) != EOF )

{

printf ("%d\t", nm);

}

fclose (fpo);

fpe = fopen ("even.txt","r");

printf("\nEven Numbers:\n");

while ( (nm = getw(fpe)) != EOF)

{

printf ("%d\t", nm);

}

fclose (fpe);

return (0);

}

Write a string onto a file using fputs():

#include <stdio.h>

int main ()

{

FILE* fp = fopen ("data.txt", "w");

char* s;

if (fp == NULL)

{

printf ("File cannot be opened\n");

return (1);

}

s = "Good to see you Programming";

if(fputs (s, fp) == EOF)

{

printf ("Cannot write onto the File\n");

fclose(fp);

return (1);

}

fclose(fp);

printf ("Data successfully written to the file\n");

return (0);

}

Using fprintf():

#include <stdio.h>

int main () {

FILE* fp = fopen ("data.txt", "w");

int x;

if (fp == NULL)

{

printf ("File cannot be opened\n");

return (1);

}

x = 10;

fprintf (fp, "The inputted value is: %d\n", x);

printf ("File write successful\n");

fclose(fp);

return(0);

}

Deleting a file using remove():

#include <stdio.h>

int main()

{

char file[] = "D:\\s.txt";

// Attempt to remove the file

if (remove(file) == 0)

{

printf("File '%s' removed successfully.\n", file);

}

else

{

perror("Error removing the file");

}

return(0);

}

Program to find the number of lines in a text file:

#include <stdio.h>

int main ()

{

FILE *fp;

int lines = 0;

char file[40]="s.txt", chr;

fp = fopen (file, "r"); //s.txt is the file to be read

chr = getc (fp);

while (chr!= EOF)

{

if (chr == '\n') //Count whenever new line is encountered

{

lines = lines + 1;

}

chr = getc(fp); //take next character from file.

}

fclose (fp); //close file.

printf ("There are %d lines in %s in a file\n", lines, file);

return (0);

}

Output:

There are 21 lines in file – s.txt

Program to count number of characters in the file:

#include<stdio.h>

#include<conio.h>

int main ()

{

char ch;

int count=0;

FILE *fp;

Clrscr ();

fp=fopen ("s.txt","r");

printf ("\nContents of the File is:");

while((ch=fgetc(fp))!=EOF)

{

count++;

printf ("%c", ch);

}

fclose (fp);

printf ("Number of characters present in file is: %d", count);

return(0);

}

Figure 8.5: Output of the above program

Program to count number of words in a file:

Include <stdio.h>

#include <stdlib.h>

Int main()

{

Char ch;

FILE*fp;

int count = 0;

fp = fopen("s.txt","r"); //Opens a file in read mode

while ((ch = fgetc(fp)) != EOF)

{

//Counts each word

if (ch ==' ' || ch == '\n')

{

count++;

}

}

printf ("Number of words present in given file: %d", count);

fclose(fp);

return (0);

}

Output:

Number of words present in given file: 114

Random access files

In C, random access files offer a potent method for non-sequential data storage and retrieval. Random access files (also known as seekable files) enable direct access to certain areas within the file, in contrast to sequential access files, which call for reading or writing data in a linear manner (Figure 8.5). Every record in a random-access file possesses a distinct identifier (like a key or record number) that may be used to find and access that particular record. Using this identifier, we may go to any record. In C, Random access files are generally implemented using file position indicators which allows us to traverse to a specific location within a file. Random data access enables flexibility and effectiveness in managing massive volumes of data. We will examine the idea, application, benefits, and examples of random-access files in C programming in this thorough investigation. Please refer to the following figure:

Figure 8.6: Random access files

Directly onto a disk or other secondary storage devices, structured data is stored using random access files, commonly known as binary files. Random access files offer direct read and write operations at any place inside the file, in contrast to sequential access files.

File position indicator

C offers a file position indicator, which is effectively a pointer that tracks the current place inside the file to enable random access. It shows the offset in bytes between the file’s start and the current location.

Opening random access files

We must open the file in the correct mode in order to work with random access files. A file can be opened in C using the fopen() function, which also returns a file reference. You can use the rb+ mode for reading and writing operations to open a file for random access. In this mode, binary data can be read from and written to the file.

Writing data

To write data to a specified location in a random access file, use the fseek() method first to set the file position indication. The file pointer, the offset (in bytes) from the origin, and the origin are the only inputs for the fseek() function. We may use the fwrite() method to write data thereafter specifying the position.

Reading data

When reading data from a random access file, you must first set the file position indication using the fseek() function to the appropriate location, just like when writing data. The fread() method can be used to read data from the designated place after the position has been defined.

Advantages of random-access files

The advantages of the random-access files have been discussed below:

Flexibility: Without executing the full file sequentially, random access files allow for immediate access to and modification of selected data. As a result, data may be stored and retrieved effectively.

Effective updates: Random access files make it simple to update or edit a single record or piece of data without having to rewrite the entire file. As a result, programs that require frequent updates or adjustments benefit from using random access files.

Effective search: Efficient search operations are made possible by random access files. You may instantly get the necessary data without browsing the full file by setting the file position indication to the correct point.

Support for complicated data structures: Random access files may quickly store and retrieve complicated data structures, including arrays, structures, and linked lists.

Examples of random-access files in C

Following are two day-to-day examples that demonstrate the usage of random-access files in C:

Management of student records: The application keeps track of and manages student records in a random-access file. The user may search for student records as well as add, amend, and remove student records. Based on the student ID, the file position indication is utilized to obtain the necessary record.

Inventory management: The application uses a random-access file to manage a product inventory. It enables the user to browse, search, and add, amend, and remove product information. Based on the product code, the file location indicator is utilized to retrieve the particular product record.

In C, random access files offer a flexible and effective mechanism to store and retrieve data in an asynchronous fashion. They provide you the freedom to directly access and alter a file’s contents, making it possible to update data quickly, conduct searches, and support intricate data structures. Programmers may use this potent capability to manage and modify vast volumes of data by mastering the ideas and procedures involved in dealing with random access files.

Programs on random access files in C

Some examples of programs in the random-access files in C are discussed below:

Program to demonstrate Create, Read & Write operations & on a random-access file:

#include <stdio.h>

struct std

{

int rn;

char nm[50];

float mks;

};

int main ()

{

FILE *fp;

struct std s;

fp = fopen("s.dat", "wb");

if (fp == NULL)

{

printf ("File cannot be opened.\n");

return (1);

}

s.rn = 1;

strcpy(s.nm, "Zain");

s.mks = 95.5;

fwrite (&s, sizeof (struct std), 1, fp);

fclose(fp);

printf ("File created.\n");

fclose(fp);

fp = fopen("s.dat", "rb");

fread (&s, sizeof (struct std), 1, fp);

printf ("Roll No: %d\n", s.rn);

printf ("Name: %s\n", s.nm);

printf ("Marks: %.2f\n", s.mks);

return (0);

}

Displaying all records from a random-access file:

#include <stdio.h>

struct std

{

int rn;

char nm[50];

float mks;

};

int main ()

{

FILE *fp;

struct std s;

fp = fopen("s.dat", "r+b");

if (fp == NULL)

{

printf ("File cannot be opened.\n");

return (1);

}

printf ("Roll No.\t\t Name\t\tMarks\n");

while (fread (&s, sizeof (struct std), 1, fp))

{

printf (" %d\t\t", s.rn);

printf (" %s\t\t", s.nm);

printf (" %.2f\n", s.mks);

}

fclose(fp);

return (0);

}

Updating a record in a random access file:

#include <stdio.h>

struct std

{

int rn;

char nm[50];

float mks;

};

int main ()

{

FILE *fp;

struct std s;

fp = fopen("s.dat", "r+b");

if (fp == NULL)

{

printf ("File cannot be opened.\n");

return (1);

}

fseek (fp, sizeof (struct std), SEEK_SET);

fread(&s, sizeof(struct std), 1, fp);

s.mks = 92.5;

fseek(fp, sizeof(struct std), SEEK_SET);

fwrite (&s, sizeof(struct std), 1, fp);

fclose(fp);

printf ("Record updated\n");

return (0);

}

Deleting a record from a random-access file:

#include <stdio.h>

struct std

{

int rn;

char nm[50];

float mks;

};

int main ()

{

FILE *fp;

int r;

struct std s;

fp = fopen("s.dat", "r+b");

if (fp == NULL)

{

printf ("File cannot be opened.\n");

return (1);

}

printf ("Input Roll Number to Delete: ");

scanf ("%d", &r);

while(fread (&s, sizeof (struct std), 1, fp))

{

if (s.rn == r)

{

fseek (fp, -sizeof (struct std), SEEK_CUR);

// -ive Roll No. Means Invalid Student Details

s.rn = -1;

fwrite (&s, sizeof (struct std), 1, fp);

printf ("Record deleted.\n");

break;

}

}

fclose(fp);

return (0);

}

Searching for a record in a random-access file:

#include <stdio.h>

struct std

{

int rn;

char nm[50];

float mks;

};

int main ()

{

FILE *fp;

int r;

struct std s;

fp = fopen("s.dat", "r+b");

if (fp == NULL)

{

printf ("File cannot be opened.\n");

return (1);

}

printf("Input search Roll No.\n ");

scanf ("%d", &r);

while (fread (&s, sizeof(struct std), 1, fp))

{

if (s.rn == r)

{

printf ("Roll No: %d\n", s.rn);

printf ("Name: %s\n", s.nm);

printf ("Marks: %.2f\n", s.mks);

exit(0);

}

}

printf ("Record not found.\n");

fclose(fp);

return (0);

}

Appending a record to a random-access file:

#include <stdio.h>

struct std

{

int rn;

char nm [50];

float mks;

};

int main ()

{

FILE *fp;

int r;

struct std s;

fp = fopen ("s.dat", "r+b");

if (fp == NULL)

{

printf ("File cannot be opened.\n");

return (1);

}

printf ("Enter Roll No: ");

scanf ("%d", &s.rn);

printf ("Enter Name: ");

scanf (" %[^\n]", s.nm);

printf ("Enter Marks: ");

scanf ("%f", &s.mks);

fwrite (&s, sizeof (struct std), 1, fp);

fclose(fp);

printf ("Record appended.\n");

return (0);

}

Counting the number of records in a random-access file:

#include <stdio.h>

struct std

{

int rn;

char nm[50];

float mks;

};

int main ()

{

FILE *fp;

int c;

struct std s;

fp = fopen("s.dat", "r+b");

if (fp == NULL)

{

printf ("File cannot be opened.\n");

return (1);

}

c=0;

while (fread(&s, sizeof(struct std), 1, fp))

{

c++;

}

fclose(fp);

printf ("No. of Records in the file =%d", c);

return (0);

}

Using of rewind() function:

#include <stdio.h>

int main()

{

char str [] = "This is a sample para";

FILE *fp;

int ch;

/* First let's write some content in the file */

fp = fopen ("file.txt", "w");

fwrite (str, 1, sizeof (str), fp);

fclose (fp);

fp = fopen ("file.txt", "r");

while (1)

{

ch = fgetc (fp);

if (feof (fp)) {

break;

}

printf ("%c", ch);

}

rewind (fp); // sets the offset to the beginning of the file

printf ("\n");

while (1)

{

ch = fgetc (fp);

if (feof (fp))

{

break;

}

printf ("%c", ch);

}

fclose (fp);

return (0);

}

Updating multiple records in a random-access file:

#include <stdio.h>

struct std

{

int rn;

char nm[50];

float mks;

};

int main ()

{

FILE *fp;

int r;

struct std s;

fp = fopen("s.dat", "r+b");

if (fp == NULL)

{

printf ("File cannot be opened.\n");

return (1);

}

printf ("Input Roll No. to update: ");

scanf ("%d", &r);

while (fread(&s, sizeof(struct std), 1, fp))

{

if (s.rn == r)

{

printf ("Enter New marks: ");

scanf ("%f", &s.mks);

fseek (fp, -sizeof (struct std), SEEK_CUR);

fwrite (&s, sizeof (struct std), 1, fp);

}

}

fclose(fp);

printf ("Records updated successfully.\n");

return (0);

}

Deleting all records from a random-access file:

#include <stdio.h>

struct std

{

int rn;

char nm[50];

float mks;

};

int main ()

{

FILE *fp;

int r;

struct std s;

fp = fopen("s.dat", "wb");

if (fp == NULL)

{

printf ("File cannot be opened.\n");

return (1);

}

fclose(fp); // since we opened the file for writing it ERASES all data

printf ("All records deleted successfully.\n");

return(0);

}

Usage of ftell() function:

#include <stdio.h>

int main ()

{

FILE *fp;

int len;

fp = fopen ("s.txt", "r");

if ( fp == NULL ) {

perror ("Error opening file");

return (-1);

}

fseek (fp, 0, SEEK_END); //goto end of file

len = ftell (fp);

fclose (fp);

printf ("Total size of file.txt = %d bytes\n", len);

return (0);

}

Program to count number of records in a file:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

struct student {

int rno;

char name [20];

};

int main () {

FILE *fp;

fp= fopen ("s.txt", "w");

if (fp == NULL) {

printf ("Error to open the file");

//exit (1);

}

struct student s;

int n;

do

{

printf("\nName\n");

scanf ("%s", &s.name);

printf ("Roll Number\n");

scanf ("%d", &s.rno);

fwrite (&s, sizeof (s), 1, fp);

printf ("Press y to Input another student's details\n");

}

while (getche () == 'y');

fclose (fp);

fp = fopen ("s.txt", "r");

if (fp == NULL) {

printf ("Error opening the file");

exit (1);

}

while (fread (&s, sizeof (s), 1, fp)) {

printf ("Roll No. = %d\t Name = %s\n", s.rno, s.name);

}

fseek (fp, 0, SEEK_END);

n=ftell (fp);

printf ("\nNo. Of records in the file = %d", n/sizeof (struct student));

fclose (fp);

return (0);

}

Figure 8.7: Output for the above program

These programs illustrate various operations on random access files in C, including creating, reading, updating, deleting, and appending records. They showcase the flexibility and efficiency of random-access files in managing and manipulating data.

Direct file access

Direct file access, a type of random-access file, also known as indexed files or relative files. Direct file access allows us to read and write data at a specific record or position within a file. They are organized with an index or relative record number that maps to specific records in the file. Table 8.1 lists some key differences between direct access files and random access files.

Feature

Direct access files

Random access files

Organization

Organized with an index or relative record number

No specific organization; allows direct access to any position.

Record Access

Access records based on a defined index or relative record number

Access records based on byte positions.

File Positioning

Typically use file positioning functions to move to specific records

Also use file positioning functions, but can access any position

Table 8.1: Difference between Direct Access and Random-Access Files

Programs on direct access files in C

Some examples of programs in the direct-access files in C are discussed below:

Create a direct access file and add some records:

#include <stdio.h>

struct std {

int rn;

char nm[50];

};

int main() {

FILE *fp = fopen("std.dat", "wb");

if (fp == NULL) {

perror("Could not open the file");

return(1);

}

struct std s;

int n;

printf("Enter number of records\n");

scanf("%d", &n);

for (int i = 0; i < n; i++) {

printf("Enter Roll No.\n");

scanf("%d", &s.rn);

printf("Enter Name\n");

scanf(" %d", s.nm);

fwrite(&s, sizeof(struct std), 1, fp);

}

fclose(fp);

return(0);

}

Read a specific record from a file:

#include <stdio.h>

struct std {

int rn;

char nm[50];

};

int main() {

FILE *fp = fopen("std.dat", "wb");

if (fp == NULL) {

perror("Could not open the file");

return(1);

}

struct std s;

int n;

printf("Enter record number \n");

scanf("%d", &n);

fseek(fp, (n - 1) * sizeof(struct std), SEEK_SET);

if (fread(&s, sizeof(struct std), 1, fp) == 1) {

printf("Student Roll No. %d\n", s.rn);

printf("Student Name: %s\n", s.nm);

} else {

printf("Record not found.\n");

}

fclose(fp);

return(0);

}

Update a specific record in the file:

#include <stdio.h>

struct std {

int rn;

char nm[50];

};

int main() {

FILE *fp = fopen("std.dat", "wb");

if (fp == NULL) {

perror("Could not open the file");

return(1);

}

struct std s;

int n;

printf("Enter record number to update \n");

scanf("%d", &n);

fseek(fp, (n - 1) * sizeof(struct std), SEEK_SET);

if (fread(&s, sizeof(struct std), 1, fp) == 1) {

printf("Enter new Student Name: ");

scanf(" %d", s.nm);

fseek(fp, -sizeof(struct std), SEEK_CUR);

fwrite(&s, sizeof(struct std), 1, fp);

} else {

printf("Record not found.\n");

}

fclose(fp);

return(0);

}

List all records in the file:

#include <stdio.h>

struct std {

int rn;

char nm[50];

};

int main() {

FILE *fp = fopen("std.dat", "wb");

if (fp == NULL) {

perror("Could not open the file");

return(1);

}

struct std s;

int n;

while (fread(&s, sizeof(struct std), 1, fp) == 1) {

printf("Student Roll No: %d\n", s.rn);

printf("Student Name: %s\n\n", s.nm);

}

fclose(fp);

return(0);

}

Delete a specific record from the file:

#include <stdio.h>

struct std {

int rn;

char nm[50];

};

int main() {

FILE *fp = fopen("std.dat", "wb");

if (fp == NULL) {

perror("Could not open the file");

return(1);

}

struct std s;

int n;

printf("Enter record number to delete \n");

scanf("%d", &n);

fseek(fp, (n - 1) * sizeof(struct std), SEEK_SET);

if (fread(&s, sizeof(struct std), 1, fp) == 1) {

fseek(fp, -sizeof(struct std), SEEK_CUR);

fwrite("\0", sizeof(struct std), 1, fp);

} else {

printf("Record not found.\n");

}

fclose(fp);

return(0);

}

The above programs illustrate how to create, write, read, update, view, and delete specific records in a binary file, offering direct access to file contents.

File descriptors

In C, a file descriptor is associated with every open file; it is an integer value that characterizes the file in the underlying operating system. They are used to recognize and access files during low-level I/O operations.

A file descriptor value of:

0 is reserved for standard input (stdin)

1 for standard output (stdout),

for standard error (stderr).

Opening files for direct access

The open() function opens a file and returns a file descriptor.

Syntax:

int open (const char *path, int flags, mode_t mode)

path specifies the file path, flags control the file access mode (for example, read-only, write-only, read-write), and mode sets the file permissions if the file is being created.

The function returns -1 if an error occurs while opening the file.

Reading from files

The read() function reads a specified number of bytes from a file into a buffer.

Syntax:

ssize_t read(int fd, void *buf, size_t count)

Example:

ssize_t read(int fd, void *buf, size_t count)

Where:

fd is the file descriptor

buf is the buffer to store the read data

count is the number of bytes to read.

The function returns the number of bytes read, or -1, when a read error occurs.

Writing to files

The write() function writes a specified number of bytes from a buffer to a file.

Syntax:

ssize_t write(int, void *, size_t)

Example:

ssize_t write(int fd, const void *buf, size_t count)

where:

fd is the file descriptor.

buf is the buffer containing the data to write.

count is the number of bytes to write.

The function returns the number of bytes written, or -1, when a write-error occurs.

File positioning

The fseek() function moves the file position indicator associated with a file descriptor to a specified location.

Syntax:

off_t fseek(int, off_t, int)

Example:

off_t fseek(int fd, off_t offset, int ref)

where:

fd is the file descriptor.

offset determines the position to move (positive or negative).

ref specifies the reference point which can be any of the following:

SEEK_SET

SEEK_CUR

SEEK_END

These set the position relative to the beginning of the file, relative to the current position, and relative to the end of the file, respectively.

The function returns the resulting file offset, or -1 on error.

Closing files

The close() function closes a file descriptor, releasing associated resources.

Syntax:

int close (int)

Example:

int close (int fd)

Here, fd is the file descriptor to close.

The function returns 0 on success or -1 when an error occurs.

Some program examples on file descriptors

Open and read a file using file descriptors

#include <stdio.h>

#include <fcntl.h>

#include <unistd.h>

int main() {

int fd = open("data.txt", O_RDONLY);

if (fd == -1) {

perror("Could not open file");

return(1);

}

char buffer[100];

ssize_t byts;

while ((byts = read(fd, buffer, sizeof(buffer))) > 0) {

write(STDOUT_FILENO, buffer, byts);

// Write to standard output (usually the console).

}

close(fd);

return(0);

}

Open and write to a file using file descriptors

#include <stdio.h>

#include <fcntl.h>

#include <unistd.h>

int main() {

int fd = open("data.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);

if (fd == -1) {

perror("Could not open file");

return (1);

}

char d[] = "Hello C Programming\n";

ssize_t byts = write(fd, d, sizeof(d));

if (byts == -1) {

perror("Cannot write to file");

}

else {

printf("%d bytes written to the file.\n", (int)byts);

}

close(fd);

return(0);

}

Redirect standard output using file descriptors:

#include <stdio.h>

#include <fcntl.h>

#include <unistd.h>

int main() {

int fd = open("data.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);

if (fd == -1) {

perror("Could not open file");

return (1);

}

int std_out = dup(STDOUT_FILENO);

dup2(fd, STDOUT_FILENO);

printf("writing text to the file.\n");

dup2(std_out, STDOUT_FILENO);

close(fd);

return (0);

}

Note: In these programs, the assumption is made that the direct access file (for example, data.txt) is present in the same directory as the programs. Modify the file name or path as per your requirement.

Stream file access

A high-level abstraction that makes it simple to read from or write to files is stream file access. To make file I/O operations simpler, streams handle buffering, conversion, and other activities. Important details regarding stream file access include:

Reading: Stream file reading requires opening a file using a function like fopen() and then reading data from the file using a function like fscanf() or fgets().

Writing: To write data to a stream file, use the fopen() function to open the file and then fprintf() or fputs() to write the data to the file.

Benefits

Accessing files using streams offers a greater degree of abstraction and streamlines I/O processes, making it simpler to manage text or structured data. It is frequently used for input/output redirection, configuration files, and text processing.

There are several ways to read from and write to files, including sequential, direct, and stream access. Direct access allows for random access to specific locations inside a file, sequential access is effective for processing data in a linear way, and stream access offers a high-level abstraction for managing file I/O activities. The application-specific requirements, such as the kind of data, access patterns, and performance concerns, will determine which file access technique is best.

In C, stream file access provides a higher-level abstraction for reading from and writing to files compared to direct access. It uses stream-oriented functions that operate on file streams, which are buffered I/O streams associated with files. This note provides a comprehensive overview of stream file access in C, covering file stream operations, techniques, and considerations.

Error handling in files

Although error management, commonly known as exception handling, is not directly supported by C programming, it may be accomplished in other ways.

Of course, the programmer must avoid mistakes while working, and they must constantly check the results of functions they call. Many C function calls return a -1 or NULL in the event of an error, making it simple to do rapid tests on these return values using, for example, an if statement.

For instance, a program’s return value is zero if it successfully exits. A number greater than zero is often returned if the application crashes due to an error (for instance, 1).

The only thing you should keep in mind is that managing errors is your responsibility as a programmer. You are the one who needs to ensure that software will end gracefully rather than crash abruptly. Depending on the return values from function calls, you must take the proper action.

Global variable errno

C functions use the global variable errno, and this integer is set if there is a problem with the function call. You must include errno.h and call extern int errno; in order to utilize errno.

Errno example

#include <stdio.h>

#include <errno.h>

extern int errno;

int main ()

{

FILE * fp;

fp = fopen("filedoesnotexist.txt", "rb");

if(fp == NULL)

{

fprintf (stderr, "Value of errno: %d\n", errno);

}

else

{

fclose (fp);

}

return (0);

}

The output of the program will be something like:

Value of errno is: 2

As can be seen, we also include the header files for stdio.h and errno.h. After that, extern int errno is invoked, giving us access to the integer errno. We attempt to create an error by opening a nonexistent file. We output the value of errno (which in this case will be 2) if the file pointer (fp) is equal to NULL. If we receive a file pointer, we close the file if it already exists using the perror() and strerror() functions.

The errno in the preceding instance had a value of 2. But what does the value of 2 mean? How can the user identify this error? Creating documentation that details each error code and what the user should do is, of course, a good practice. When a software problem occurs, it is also best practice to provide a nice descriptive error message. There are two functions in the C programming language that may be used to show a text message linked to an errno. Both strerror() and perror() are the functions.

A reference to the textual message of the current errno value is returned by the function strerror(). The text message of the current errno value is shown after a colon and the string that you supply to the method perror().

Conclusion

In conclusion, this chapter delved into the concept of file handling in C, providing a comprehensive understanding of data hierarchy from bits to databases, data warehouses, and data marts. We explored the significance of files, their types, and the importance of handling both binary and text files effectively in C.

The chapter further covered file modes and various file operations, including sequential, random, direct, and stream file access methods. We also examined error handling in files, with a focus on the global variable errno, illustrated through an example.

By grasping these concepts and techniques, you are now equipped with the knowledge and tools to efficiently manage files in C programming. Understanding file handling is crucial for any developer working with data and information, allowing you to create robust and efficient file-handling systems for a wide range of applications.

Exercise

What is a file in C programming?

How do you declare a file pointer in C?

What is the difference between text files and binary files?

Write a program to open and close a file in C.

What is the purpose of the FILE data type in C, and how is it used when working with files?

Write down the syntax for fopen() and fclose functions.

Explain the concept of file modes (“r,” “w,” “a,” and so on) in C and how they affect file opening and manipulation.

What is the mode parameter in the fopen() function used for?

What is the difference between feof() and ferror() functions?

How do you check if a file has been opened successfully?

How do you read a single character from a file using the fgetc function in C?

What will be the output of the following code?

#include <stdio.h>

int main() {

FILE *file = fopen("output.txt", "w");

fprintf(file, "Hello, World!");

fclose(file);

return 0;

}

How do you read data from a file in C?

Write a program to count the number of vowels in a file.

What is a binary file, and how do you work with binary data in C using functions like fwrite and fread?

What does the feof function in C do, and why is it important when reading from a file?

What is the syntax for fscanf() function?

What will be the output of the following code?

#include <stdio.h>

int main()

{

FILE *file = fopen("input.txt", "r");

if (file)

{

char ch;

while ((ch = fgetc(file)) != EOF) {

putchar(ch);

}

fclose(file);

}

return(0);

}

What is the syntax for fprintf() function?

Write a program to copy the contents of one file to another file in C.

What is the difference between fseek() and rewind() functions?

Write a program to delete the contents of a file in C.

Write a program to delete a file using remove() function.

Discuss the concept of file locking and how it can be implemented in C.

Join our book’s Discord space

Join the book’s Discord Workspace for Latest updates, Offers, Tech happenings around the world, New Release and Sessions with the Authors:

https://discord.bpbonline.com

CHAPTER 9C Preprocessors

Introduction

Welcome to the fascinating world of C programming preprocessors! In this chapter, we dive into the powerful features and functionalities offered by C preprocessors. Preprocessors play a crucial role in code manipulation and optimization, providing ways to customize and control the compilation process. Understanding preprocessors and their directives is essential for any C programmer looking to enhance their code’s efficiency and maintainability. So, let us embark on this journey to explore the ins and outs of C preprocessors!

Structure

The following topics are discussed in the chapter:

Preprocessors

Conditional compilation

Control structure macros

Types of preprocessor directives

Macro versus functions

Conditional compilation

Preprocessor directives

ANSI C predefined macros

Preprocessor operators

C-header files

Once-only headers

Using #ifdef for different computer types

Objectives

In this chapter, readers will delve into the realm of C preprocessors, exploring essential directives and operators that play a pivotal role in code optimization and organization. They will gain mastery over #define, #ifdef, #ifndef, #include, #undef, and conditional compilation using #if, #else, #elif, and #endif macros. Additionally, readers will learn the intricacies of preprocessor operators such as (\) for macro continuation, (#) for stringizing, (##) for token pasting, and defined() for conditional checks. The chapter will also cover C-header files, once-only headers, and the usage of #ifdef to adapt code for various computer types, enhancing code portability. By the end of this chapter, readers will emerge with advanced C programming skills, empowered to employ preprocessors adeptly and optimize code for flexibility and efficiency.

Introduction to preprocessors

Preprocessors allow the text processing in C programs to be completed before compilation. A preprocessor is used to modify a C program before compilation. The preprocessor searches the program for the preprocessor directives or instructions that it can understand. The # (hash) sign denotes a preprocessor directive. A preprocessor directive must start in the first column for readability and be the first non-blank character. Compilers for C++ employ the same C preprocessor. Refer to Figure 9.1:

Figure 9.1: Life cycle of C program

Before the compiler begins producing our code, the preprocessor does preparatory actions (conditional compiling and more.). The preprocessor’s output is likewise text, so these changes are lexical.

In simple terms, a C preprocessor is a text replacement tool that gives the compiler the necessary (pre) processing instructions before the compilation process takes over. The main preprocessor directives supported in C include:

#define

#include

#ifdef

#ifndef

#undef

#if

#else

#elif

#endif

#pragma

#error

#define directive

The #define directive is a preprocessor directive used in C programming to define macros. Before the code is compiled, symbolic names or constants are replaced with their corresponding values.

Syntax:

#define macro_name replacement_text

where:

macro_name is the name of the macro,

replacement_text is the text/value that the macro will be replaced with,

replacement_text can be any valid C expression, literal value, or macro.

For the macro to be accessible throughout the program, the #define directive is normally included at the beginning of the code before any function definitions. Here are some examples to illustrate the usage of the #define directive:

Defining a constant macro

Consider #define PIE 3.14

In this example, PIE is defined as a macro with the value 3.14, which means PIE will be replaced with 3.14 wherever it appears in the source code/ program.

Defining a function-like macro

Consider #define SQR (n) ((n) * (n))

In this example, SQR is defined as a macro that takes one argument n (a number) and returns the square of that number. The macro can be used like a function, and the argument will be substituted by the replacement_text.

Defining a conditional macro

Consider:

#define DEBUG_ENABLED

#ifdef DEBUG_ENABLED

// code here

#endif

The DEBUG_ENABLED macro is defined in this example without a value. The conditional block’s code will only be compiled if the DEBUG_ENABLED macro is specified, thanks to the #ifdef directive. This is frequently used to activate or disable debug code blocks selectively.

Redefining a macro

In the example shown below, the MARKS macro is redefined in this example. The most recent definition will supersede earlier versions. As a result, throughout the code, the variable MARKS will have the value 160.

#define MARKS 125

#define MARKS 160

The C preprocessor’s strong #define directive enables you to write macros for reusing code, constants, conditional compilation, and other purposes. To ensure readability and maintainability of the code, macros should be used carefully, and any side effects of their replacements should be considered.

#ifdef preprocessor

#ifdef preprocessor is a directive used for conditional compilation in C programming. Depending on whether a certain macro is specified or not, it enables you to include or omit particular code blocks from compilation. This might be helpful when you wish to have various code paths or configurations for various build settings or circumstances.

Syntax:

#ifdef macro

// control goes here if the macro is defined

#else

// else control goes here

#endif

If macro is defined using #define, the code between #ifdef and #else will be included during compilation.

If macro is not defined, the code between #else and #endif will be included during compilation.

Example:

#include <stdio.h>

#define DEBUG // Uncomment this line to enable debugging

int main ()

{

#ifdef DEBUG

printf ("Debugging is enabled!\n");

#else

printf ("Debugging is disabled!\n");

#endif

return(0);

}

Here, if you have #define DEBUG uncommented at the beginning of the program, the output will be:

Debugging is enabled!

If the #define DEBUG line is commented out or removed, the output will be:

Debugging is disabled!

By using #ifdef and related preprocessor directives, you can control which parts of your code are compiled based on the defined macros, allowing for more flexible and customizable code behavior.

#ifndef preprocessor

#ifndef preprocessor is a directive used in C programming to determine whether a specific macro has been defined or not. The word ifndef stands for if not defined. In order to manage conditional compilation, the #ifndef directive is often used in conjunction with the #ifdef directive (which stands for if defined).

Syntax:

#ifndef MACRO

// Code to compile when MACRO is NOT defined

#endif

In the above code:

The code between #ifndef and #endif will be included for compilation if the macro given by MACRO has not been declared using #define.

If the macro MACRO_NAME has already been created using the #define directive, the code between #ifndef and #endif will be skipped, and the preprocessor will advance to the code after the #endif directive.

In order to prevent repeated inclusions, header guards are frequently created in header files using this approach. Consider the header file example.h as an illustration:

#ifndef EXAMPLE_H

// Header guard to prevent multiple inclusions

#define EXAMPLE_H // Header file content

#endif

The header guard ensures that this header file’s contents are only included once when it is included in multiple source files, regardless of where it is included in the program.

C programmers may make their code more structured and effective by utilizing the #ifndef and #ifdef directives to control which sections of code are included during compilation based on the presence or absence of particular macros.

#include preprocessor

To include external files in your C program, use the #include preprocessor directive. It enables you to include code from other files, such as header files, and make it available in the source file you are now working with.

A C source file will normally begin with the #include directive before any function definitions or executable code.

Syntax:

#include <filename>

#include "filename"

The #include directive has two versions depending on whether angle brackets (‘>’) or double quotes (‘""’) are used:

Using angle brackets (‘>’)

The preprocessor looks for a file in the default system directories when you include a file using angle brackets. System-level header files are often found in these folders.

Example:

#include <stdio.h>

Using double quotes (`""`):

When you use double quotes to include a file, the preprocessor initially looks in the current directory for the file. The standard system directories are then searched if not found in the current directory.

Example:

#include "myhdrfile.h"

Please take note that the actual name of the file you wish to add should be substituted for the <filename> or "filename".

Include header files, which often include function prototypes, type definitions, and macros used in numerous source files, by using the #include directive. You may utilize the specified functions and symbols in your code without having to redefine them by adding the required header files.

Example:

main.c:

#include <stdio.h> // standard library header file

#include "myhdrfile.h" // user-defined header file

int main ()

{

// Code here...

return(0);

}

myhdrfile.h:

// Function declaration

void myFunction();

#endif

We may modularize our code, separate declarations from definitions, and make our code more reusable and manageable by utilizing the #include directive.

Note: Before utilizing needed declarations in our code, it is crucial to confirm that the appropriate header files are present.

#undef preprocessor

A previously declared macro can be removed or undefined in C by using the preprocessor command #undef. You may use this to make a macro declaration unusable in code by removing it from the preprocessor symbol table.

Syntax:

#undef MACRO

Where, MACRO is the name of the macro that we want to undefine.

Important points to keep in mind concerning the #undef directive are discussed below:

Undefining a macro:

The MACRO_NAME defined macro is deleted from the symbol table when the #undef command is encountered. The code will interpret further references to that macro as ordinary identifiers rather than macro invocations.

Removing macro definitions:

The #undef directive just eliminates the macro definition; it leaves all related code and statements in place. The program will still contain any code that is dependent on the macro.

Errors and warnings:

The preprocessor will alert you but still execute the code if you attempt to #undef a macro that has not already been declared. Before attempting to undefine a macro, it is typically wise to check to see if it is defined.

Example to illustrate the usage of #undef:

#include <stdio.h>

#define PI 3.14

int main ()

{

printf ("Value of PI: %f\n", PI);

#undef PI

printf ("After undefining PI\n");

// Uncommenting the line below will result in a compilation error

// printf ("Value of PI: %f\n", PI);

return (0);

}

In the above code, the macro PI is defined with a value of 3.14. However, after the #undef PI directive, the macro is no longer defined. An attempt to use PI in the code after the #undef directive shall result in a compilation error.

When you need to redefine a macro with a different value or behavior, temporarily delete or deactivate a macro definition, or both, the #undef directive might be helpful. It gives you freedom in how you manage macros in your codebase.

If a certain macro is defined, a code block can be conditionally compiled using the C preprocessor command #ifdef. On the basis of the presence of a certain macro definition, it enables you to include or exclude particular code blocks.

Syntax:

#ifdef MACRO

// Code to be compiled if the macro is defined

#else

// Code to be compiled if the macro is not defined

#endif

How #ifdef works:

If the macro MACRO is created earlier in the code using a #define directive or by giving the compiler a command-line option, the code block between #ifdef and #else (if present) will be compiled.

If the macro MACRO is not specified, the code block between #else (if present) and #endif will be compiled, or if there is no #else, that code block will be completely skipped.

Example:

#include <stdio.h>

#define DEBUG

int main()

{

#ifdef DEBUG

Printf ("Debugging is enabled.\n");

#else

printf ("Debugging is not enabled.\n");

#endif

return (0);

}

In this example, we used the #define command to define the macro DEBUG. The result, when compiling and running the code as is, will read Debug mode is enabled. However, if you leave the #define DEBUG line uncommented or delete it, the result will read Debug mode is disabled.

We can conditionally compile particular lines of code depending on whether a specified macro is present or not by using the #ifdef statement. This can be helpful for turning on or off debug statements, including platform-specific code, or for managing various behaviors based on configuration settings.

#if #else #elif #endif macros in C

The preprocessor directives #if, #else, #elif, and #endif in C programming allow conditional compilation. They are used to include or exclude particular code blocks depending on a set of criteria that is frequently created using preprocessor macros or constant expressions.

The syntax for if, endif and #else are as discussed below:

#if and #endif:

#if condition

// Code to be compiled if the condition/ expression evaluates to true

#endif

The directive #if determines if the condition is true. The code between #if and #endif will be compiled if the expression evaluates to a non-zero value.

#else:

When the condition stated in the previous directive evaluates to false, the #else directive in C programming is used in conjunction with the #ifdef, #ifndef, or #if directives to offer an alternative code block.

#if condition

// Code to compile if the condition/ expression evaluates to TRUE

#else

// Code to compile if the condition/ expression evaluates to FALSE

#endif

The #else directive is used in conjunction with the #if directive. If the condition in the #if directive evaluates to false (zero), the code between #else and #endif will be compiled.

#elif:

#elif is a preprocessor command that stands for else if. It is used with the #if and #else directives during the preprocessing stage to provide several conditional branching options. The #elif directive allows you to provide additional conditions that will only be considered if the previous #if or #elif condition is false.

#if expression1

// Code to compile if expression1 evaluates to true (non-zero)

#elif expression2

// Code to compile if expression1 is false and expression2 evaluates to true (non-zero)

#else

// Code to compile if both expression1 and expression2 are false (zero)

#endif

Use the #elif (short for else if) directive to add further criteria to the conditional compilation. #if and #else are used with it. The preprocessor will assess the constant_expression2 in the #elif directive if the constant_expression1 in the #if directive returns false. The code between #elif and #endif will be included for compilation if constant_expression2 is true.

Remember that the preprocessor evaluates these directives’ constant expressions at compilation time and has no bearing on the values at runtime.

Here is an example of using these directives:

#include <stdio.h>

#define DEBUGMODE 1

int main()

{

#if DEBUGMODE

printf ("Debugging is enabled.\n");

#else

printf ("Debugging is disabled.\n");

#endif

return (0);

}

In this case, the compilation process will display the message Debugging is enabled. If the variable DEBUGMODE is declared and set to a non-zero value (for example, #define DEBUG_MODE 1). The message Debug mode is disabled. will appear if DEBUG_MODE is not specified or set to zero.

Control structure macros

Control structure macros are preprocessor macros used in the C programming language to construct conditional statements and loops. By abstracting away the complexity of control structures, these macros give programmers a method to write code that is more understandable and expressive. When dealing with intricate or hierarchical control structures, they make the code easier to maintain and comprehend. Typical C control structure macros include the following:

#define IF (condition) if (condition)

The macro allows us to write an if statement in a natural language method.

For example:

IF (n > 1)

{

// Code to execute when n is greater than 1

}

#define ELSE else

It defines the identifier ELSE as a replacement for the keyword else.

IF (n > 1)

{

// Code to execute when n is greater than 1

}

ELSE

{

// Code to execute when n is not greater than 1

}

#define ELSE_IF(condition) else if (condition)

The macro offers a brief way to write an else if statement:

IF (n > 1)

{

// Code to execute when n is greater than 1

}

ELSE_IF (x < 0)

{

// Code to execute when n is less than 0

}

ELSE

{

// Code to execute when neither condition is met

}

#define WHILE (condition) while (condition)

The macro allows us to write a while loop in a more readable manner.

int n = 0;

WHILE (n < 3)

{

// Code to execute as long as n is less than 3

n++;

}

`#define DO while`

The macro lets us use a do-while loop more concisely.

int n = 0;

DO (n < 3)

{

// Code to execute as long as i is less than 3

n++;

}

#define FOR (init, condition, update) for (init; condition; update)

The macro streamlines the syntax of a for loop.

FOR (int n = 0; n < 10; n++)

{

// Code to execute as long as n is less than 10

}

Although these control structure macros might make your code easier to comprehend, you should only use them sparingly. Overusing macros can result in more difficult-to-maintain and --debug code. In C programming, it is crucial to achieve a balance between code readability and the proper application of macros.

Types of pre-processor directives

The following are the major types of pre-processor directives:

File inclusion

Macro definition

Conditional compilation

Other types

File inclusion directives

This directive, as its name implies, instructs the preprocessor to link a file to our program file. The two files would then be built together, and execution would only occur if the links between the two files were successful. The most frequent instruction we encounter in most applications is as follows:

#include <stdio.h> //tells preprocessor to include Header file stdio.h

#include <string.h>

#include <math.h>

Note: The header files contain (definitions of) functions which we (may) use in our programs.

Macro definition directives

Using this directive, we can replace some expressions in program with their pre-defined values.

For example:

#define PI 3.14 // replaces all instances of PI with 3.14 in our C Program

Program to show use of macro preprocessor directive:

#define PI 3.14

#include <stdio.h>

#include <stdlib.h>

Int main ()

{

float a, r;

printf ("Input Radius of a circle\n");

scanf ("%f", &r);

a = PI * r * r; // πr2

printf ("Area of the circle= %f\n", a);

printf ("Circumference of the circle= %f\n", 2*PI*r);

return (0);

}

So, PI will be replaced by value 3.14. This type of directive is also called a pre-processor macro.

Another way of using this directive is by having formula style macro definition as follows:

#define avg (a, b) (a + b)/2

Here, we are using a parametrized function-like macro where the macro takes 3 inputs and binds them into an expression to get a return value of the input parameters. We can later use the return value of the macro in our program.

Some program examples are as below:

Program to show use of macro preprocessor directive:

#include <stdio.h>

#include <stdlib.h>

#define AVG(n1, n2) (n1+n2)/2

int main()

{

float a, b, av;

printf ("Input 2 Numbers\n");

scanf ("%f%f", &a, &b);

av=AVG (a, b); // calling Macro AVG

printf ("Average of the numbers = %f\n", av);

return (0);

}

Macros with arguments:

#define AREA(r) (3.14 * r * r)

int main ()

{

float r, x;

printf ("Input Radius of a circle\n");

scanf ("%f", &r);

x = AREA (r);

printf ("\n Area of circle = %f", x);

}

Figure 9.2 Output of the above program

Macros with arguments:

#define MAX(n1, n2) ((n1)>(n2)?(n1):(n2))

// No space between MAX and Brackets

int main () { //Max between 20 and 10 is 20

printf ("Max between 20 and 10 is %d\n", MAX (10, 20));

return (0);

}

Figure 9.3 Output of the above program

Applications of macros

In comparison to other structures like functions, macros in the C language have certain distinct benefits. The following are some justifications for using C macros:

Constant definitions: In C, constant definitions are frequently done via macros. You may make the code easier to understand and maintain by using macros to give constant value names that make sense.

For example:

#define PIE 3.14

#define MAX 100

Code reusability: By using macros, it is possible to produce code fragments that may be applied in many places across the application. This can lessen code duplication and increase the maintainability of the code. However, if you use macros frequently or in large quantities, it might result in code bloat.

Conditional compilation: Using macros, particular sections of code can be included or removed from the finished executable depending on specific criteria. This is helpful for producing various builds for various platforms or turning on debug capabilities exclusively when developing.

#define DEBUG_ON

#ifdef DEBUG_ON

// Debug-oriented code

#endif

Inline code expansion: During preprocessing, macros are extended as inline code. Due to the lack of function call and return cost, performance may be improved. Code duplication and larger binary sizes, however, can balance out this advantage.

Variadic macros: You may define macros with various parameters, giving you additional freedom and versatility while writing code. Variadic macros are frequently employed for logging and debugging operations.

#define LOG(form, ...) printf(form, ARGS__)

Shorter code: By substituting understandable macro names for verbose expressions, macros make it easier to construct code that is succinct. This can improve code readability and lower the likelihood of mistakes brought on by repetitive code.

String concatenation: String concatenation, which is not directly feasible in C, may be done via macros. When working concisely with strings, this is advantageous.

#define STRCAT (s1, s2) s1 ## s2

printf ("%s\n", STRCAT ("ZAIN", " IMAD")); // Outputs "ZAIN IMAD"

Despite these benefits, macros must be utilized carefully. When macros are used incorrectly, unexpected behavior, debugging challenges, and significant code maintenance problems might result. Use functions instead of macros whenever you can since they offer type safety, better code structure, and a lower chance of mistakes. Use macros only when they are absolutely essential, such as for managing conditional compilation or declaring constants.

Macros versus functions

In C programming, macros, and functions are two essential building blocks that have various uses. Let us examine how macros and functions vary from one another (Table 9.1):

Parameter

Macros

Functions

Definition

Macros are preprocessor directives that are specified using the #define syntax and are frequently used to carry out quick text substitutions prior to the start of compilation. Throughout the program, macros can be utilized since they are globally specified.

A function is a designated section of code that carries out a certain action. Functions can be invoked from many places in the program and are defined using the function_name() syntax. They are called while the application is running and are compiled as distinct code units.

Invocation

During the preprocessing stage, macros are called by their names and their defined text is substituted. Before the code is compiled, the preprocessor does the substitution.

To call a function, you must first specify its name, followed by parentheses, and then include any necessary parameters. The resolution of function calls happens while the program is running.

Argument evaluation

Macro arguments are not examined before substitution for macro arguments. If an argument has unintended consequences or is used more than once in the macro, this may result in unexpected behavior.

Function parameters are assessed before the function is invoked. There are no problems with side effects, and each argument is examined just once.

Code expansion

The complete macro code is placed into the calling code when a macro is expanded as inline code. If the macro is big or used a lot, this could result in code bloat.

Functions are independent chunks of code that cause the control to move to its position when called. Since the function is just declared once and may be called from several locations throughout the program, there is less code duplication.

Return values

Macros are substituted with their defined content right away; they do not have a return value.

The return type in the function declaration is used to specify if a function has a return value. They can also carry out difficult tasks and deliver outcomes to the calling code.

Type safety

Because they execute direct text replacement, macros lack type safety. If types are not handled appropriately, this might result in unexpected outcomes.

Because the parameter types and return types are explicitly defined, functions offer type safety. Error risk can be decreased by the compiler enforcing type checks.

Table 9.1: Macro versus functions

Preprocessors employ macros, which are straightforward text substitutions, to accomplish conditional compilation and reuse code. Contrarily, functions are distinct units of code that offer superior type safety, reuse, and structure of the code. Due to the extra advantages they provide, employing functions instead of macros is typically chosen whenever it is practical. In other circumstances, however, direct text replacement is required, such as when establishing constants or short, repetitive code sequences, macros can still be helpful.

Conditional compilation

Conditional compilation is a feature provided by the C preprocessor. This enables the publication of several program executable code versions based on predetermined criteria. Some sections of the code are compiled, while others are disregarded because of the process of defining compiler directives. The approach allows us to identify sections of code that may be built especially for a certain platform, which is useful when executing code in cross-platform development. The processing of the conditional compilation differs from that of the conditional statement (if-else statements). The conditional directives are in the program before compilation, but the if-else statement is tested when it is being executed. Conditional compilation can be implemented using the following directives in C:

#ifdef

#if

#defined

#else

#elseif

These directives have already been discussed earlier in the chapter.

Conditions compilation with #if, #elif and more

#define N 0

#include <stdio.h>

int main()

{

#if N > 0

// Positive number.

printf ("+ve No.");

#elif N < 0

// Negative number

printf ("-ve No.");

#else

// 0

printf ("Zero");

#endif

return(0);

}

As already mentioned, one of the applications of the #ifdef directive is to enable platform-specific source code into a program.

#include <stdio.h>

#define UNIX 1

int main () {

#ifdef UNIX

printf ("UNIX is ON\n");

#endif

return (0);

}

Output:

UNIX is ON

In the above program, UNIX source code is enabled. To disable the UNIX source code, we may change the code:

#define UNIX 1 to #undef UNIX

#include <stdio.h>

#define DEBUG 1

int main()

{

#ifdef DEBUG

printf ("Debug is ON\n");

#else

printf ("Debug is OFF\n");

#endif

return(0);

}

Preprocessor directive #Pragma

This preprocessor directive enables us to offer additional information to the compiler in the C programming language.

#pragma token

Here are some of #pragma directives offered by C programming:

#pragma startup: The preprocessor directive #pragma startup is an implementation-specific, non-standard preprocessor directive in the C programming language. Prior to calling the main() function, it is used to identify functions that should run automatically at the start of a program’s execution. #pragma startup has inconsistent behavior between platforms and compilers, and its use is not portable between C implementations.

Syntax:

#pragma startup function

In the above code, function - refers to the function that we want to execute at the program’s startup.

The use of non-standard, implementation-specific features like #pragma startup should be avoided since different systems and compilers may interpret the directive differently or not at all, which might result in non-portable code.

The main() function is where a C program begins to run in standard C. The runtime environment or startup code given by the compiler and the underlying operating system typically handles everything run before the main() call. Therefore, it is often preferable to utilize standard approaches and procedures, such as constructor attributes in GCC or constructors and initializers in C++, if you need to run specialized code before main(). These techniques offer more portability and have better platform and compiler support.

For example, in GCC, we can use a constructor feature like this:

#include <stdio.h>

void startup_func (void) __attribute__((constructor));

void startup_func(void)

{

printf ("Function will get executed before main().\n");

}

int main()

{

printf ("This is our main function.\n");

return (0);

}

It is important to keep in mind that non-standard features like #pragma startup may restrict the portability of your code; as a result, it is typically a good idea to avoid them and use standard, well-supported alternatives instead.

#pragma exit directive: The #pragma exit directive, a compiler-specific pragma, enables you to define functions to be run after the program terminates. It is generally supported by particular compilers and is not a built-in feature of the C or C++ languages.

Because it is not standardized, the syntax for #pragma exit differs depending on the compiler. In general, you employ it as follows:

#pragma exit function: In the above code, function is the name of the function that we want to invoke when the program exits. The function should have neither arguments nor any return value (that is, void return type).

The #pragma exit directive is used to create a mechanism for resource deallocation or cleaning chores to be performed after the program stops. Before the application closes, you might wish to free up allocated memory, remove any open files, or take other cleanup measures.

The #pragma exit command, as was already indicated, is unique to some compilers and is not a part of the standard C or C++ language. It is crucial to examine the compiler’s documentation for precise information on how to use #pragma exit with that specific compiler, as different compilers may have their own syntax or restrictions.

Using #pragma exit may make your code less portable and more reliant on the particular compiler being used because it is not portable across multiple compilers. If portability is an issue, you can think about utilizing alternative methods, such the atexit function in C or smart pointers and Resource Acquisition Is Initialization (RAII) strategies in C++, to handle cleaning duties in a more standardized and portable manner.

#pragma warn: #pragma is a preprocessor directive used in C and C++ programming to provide the compiler with particular instructions or control. However, the C/C++ language does not include a standard #pragma warn command.

The #pragma directive and its consequences are compiler-specific, which means that various compilers may interpret #pragma directives differently or may support various sets of #pragma instructions.

In C/C++, #pragma directives are frequently used for:

Regulating warning messages: Some compilers include explicit #pragma directives to activate or suppress particular warning messages. Particular compilers, for instance, let you deliberately conceal particular warnings that might not apply to your code.

Optimizations: To manage compiler optimizations for certain code portions, compilers frequently include #pragma directives. These directives can be used to optimize the performance of crucial code segments.

Pack and alignment directives: #pragma directives can be used to manage the packing and alignment of structures or variables in memory, which is important for some hardware- or platform-specific needs.

Since the functionality of #pragma directives depends on the compiler you are using, it is crucial to read the documentation of that compiler to understand the #pragma choices that are available and their consequences.

For instance, you may manage warning messages using the #pragma warning command in Visual C++, the C++ compiler from Microsoft.

Keep in mind that utilizing compiler-specific directives may reduce the portability of your code. Use #pragma directives sparingly and only when necessary for compiler-specific optimizations or setups or, if feasible, attempt to use standard C/C++ capabilities.

#pragma GCC dependency: A compiler-specific directive used in the GNU Compiler Collection (GCC) is #pragma GCC dependency. In a multi-file C or C++ project, it enables you to express dependencies between translation units (source files). The compiler is informed by this pragma of the files that must be compiled prior to the current translation unit.

Syntax:

#pragma GCC dependency "file"

Where:

file denotes the name of the file that the current translation unit is dependent on.

The compiler examines if the supplied file (filename) has changed since the last compilation when it comes across this pragma. The current translation unit (source file with the pragma) will be recompiled if the file has been changed in order to keep it current with the dependencies.

When a project has numerous source files and modifications to one file might have an impact on the accuracy of other files, this pragma can be helpful. You may make sure that the appropriate files are automatically recompiled when their dependencies change by using #pragma GCC dependency, avoiding potential problems brought on by out-of-date object code.

#pragma GCC dependence is unique to the GCC compiler and might not be supported by other C/C++ compilers. It is advisable to stay away from utilizing compiler-specific pragmas if at all feasible to retain portability. Use alternative accepted techniques for managing dependencies instead, including build systems or Makefiles, which are supported by a wider range of platforms and compilers.

#pragma GCC system header: #pragma GCC system_header is a preprocessor directive used in C and C++ programming to tell the GCC compiler to consider the provided header file as a system header. It may not be recognized by other compilers since it is exclusive to GCC.

When a header file is designated as a system header, the compiler is informed that any constructions or declarations within that header file are a part of the system’s standard library or platform-specific implementation and should not trigger any warnings.

When compiling system headers with certain compiler settings, the #pragma GCC system_header directive is frequently used to conceal warnings.

For example:

#pragma GCC system_header

When some_system_header.h is included, and #pragma GCC system_header is used, the compiler will regard the header as a trusted system header and ignore any warnings that might normally be produced by its contents.

We must be cautious while using these instructions. If you use the #pragma GCC system_header command incorrectly or use it on non-standard headers, your code may have bugs or defects that are being concealed. Therefore, it is typically advised against using this pragma for your project’s customized headers and only to add it to a trustworthy system or standard library headers.

#pragma GCC poison: The preprocessor command #pragma GCC poison is peculiar to GCC and is used to designate certain identifiers (variables, functions, or macros) as poisoned. Any attempt to utilize a poisoned identifier in the code will result in a compilation error, essentially prohibiting its usage in the source code.

Syntax:

#pragma GCC poison identifier

The name of the variable, function, or macro that you want to label as poisoned is referred to here as the identifier.

By employing #pragma GCC poison, developers can prevent using particular names that might have been out-of-date, dangerous, or problematic in the source. The compiler makes sure that developers are informed of their limited usage and are urged to utilize substitute, safer solutions instead of poisoning these identifiers.

For example:

#include <stdio.h>

#pragma GCC poison poison_function

int main()

{

poison _function (); // This will trigger a compilation error

printf ("Learning Preprocessors\n");

return (0);

}

The poison _function in this example has been tagged as poisoned using the #pragma GCC poison, which indicates that using it will cause a compilation problem. The compiler will output a notice such as error: attempt to use poisoned poison _function.

We must be aware that the GCC feature known as #pragma GCC poison may not be supported by other compilers. In order to let other developers know the reasoning behind this limitation, it is also advisable to include the cause of a poisoned identifier in your code or project documentation.

#pragma directive example:

#include <stdio.h>

Void init ();

void end ();

#pragma startup init

#pragma exit end

Int main ()

{

printf ("main method\n");

return 0;

}

Void init ()

{

printf ("init method\n");

}

void end ()

{

printf ("end method\n");

}

Note: #pragma startup and #pragma exit are keywords.

#pragma startup followed by a ‘function-name’, makes the ‘function- name’ be executed before the main method. #pragma exit followed by a ‘function-name’, makes the ‘function-name’ be executed just before program termination.

So, in the above example, the first init method would be executed, followed by the main method, and the end method would be executed just before the termination of the program.

In the above example, c style string msg will be set to Zone information in use as Zone macro has been defined above it. Based on this setting, a program may take a different path of compilation.

ANSI C predefined macros

Several standard macros specified by ANSI C (C89/C90) offer details about the environment and the compiler being used. You may use these macros in your C code to help you create code that is cross-platform and portable. The following list of frequently used ANSI C predefined macros:

The macro __FILE__ expands to the name of the current source file as a string literal.

The macro __LINE__ expands to the line number that is now in the source code as an integer constant.

__DATE__ expands to a string literal reflecting the date when the source file was built in the format ‘Mmm dd yyyy’.

__TIME__ expands to a string literal reflecting the time when the source file was compiled in the format ‘hh:mm:ss’.

__STDC__ is defined when the compiler complies with ANSI C requirements in its entirety.

__STDC_VERSION__ expands to an integer constant that denotes the version of the C standard that the compiler can support.

By using these predefined macros, you can write more portable and flexible code that adapts to different environments and compilers.

__STDC_HOSTED__ is defined when the compiler is functioning in a hosted environment (such as one that has an operating system and standard library support).

__STDC_NO_ATOMICS__, __STDC_NO_THREADS__, __STDC_NO_COMPLEX__, __STDC_NO_VLA__ and __STDC_NO_VLA__ are defined if specific language features are not supported by the implementation.

It is crucial to remember that some of these macros are optional and cannot be included in all C environments or compilers. Furthermore, more recent iterations of the C standard (C99 and C11) added new predefined macros and improved the functionality of several already existing ones.

To check the accessibility of macros, we can use conditional compilation with #ifdef or #ifndef.

For example:

#ifdef __STDC__

printf ("Your compiler is ANSI C compatible");

#else

printf ("Your compiler is not ANSI C compatible");

#endif

By using these predefined macros, we can write more portable and flexible code that adapts to different environments and compilers. We may create more adaptable, portable code that works with multiple settings and compilers by utilizing these preset macros.

An example of inbuilt macros is as follows:

#include <stdio.h>

int main ()

{

printf ("File :%s\n", __FILE__ );

printf ("Date :%s\n", __DATE__ );

printf ("Time :%s\n", __TIME__ );

printf ("Line :%d\n", __LINE__ );

printf ("ANSI :%d\n", __STDC__ );

}

Output for the above code is shown in Figure 9.4:

Figure 9.4: ANSI inbuilt macros

Preprocessor operators

The C preprocessor offers the following operators to help create macros:

Macro continuation (\) operator

A macro often only occupies one line. A macro that is too big to fit on a single line can be continued using the macro continuation operator (\).

For example:

#define avg (a, b) \

((a + b) \ 2))

Stringize (#) operator

A macro parameter is converted into a string constant using the stringize command. It is also known as the operator, denoted by the numeral #. This operator may only be used by macros with a specified argument or parameter list.

Stringize (#) operator example

#include <stdio.h>

#define Msg(x, y, z) \

printf (#x "," #y " and " #z ": You are cute\n")

int main() {

Msg (Usman, Zain, Imad);

return 0;

}

Token pasting (##) operator

The token-pasting operator (##), used within macros, combines two arguments. As a result, it permits the creation of a single token in the macro specification from two separate tokens.

For example:

#include <stdio.h>

#define concat (a, b) a##b

int main ()

{

printf ("%d", concat (10,20));

return(0);

}

By using these predefined macros, we can write more portable and flexible code that acclimatizes to diverse environments and compilers.

Defined () operator

An identifier may be checked to see if #define has been used by using the preprocessor-defined operator. The value is true (non-zero) if the given identifier is defined; otherwise, it is false (zero). An example for the same is given below:

#include <stdio.h>

#if !defined (msg)

#define msg "How are You"

#endif

int main ()

{

printf ("Dear %s\n", msg);

return (0);

}

C-header files

A header file is a file with extension .h which contains C function declarations and macro definitions to be shared between several source files. There are two types of header files: the files that the programmer writes and the files that come with the compiler.

You request to use a header file in your program by including it with the C preprocessing directive #include, like you have seen the inclusion of stdio.h header file, which comes along with your compiler.

Including a header file is equivalent to copying the content of the header file. Still, we avoid doing so because it would be error-prone and not a good practice, especially when dealing with multiple source files in a program. A simple practice in C or C++ programs is that we keep all the constants, macros, system-wide global variables, and function prototypes in the header files and include that header file wherever it is required.

Include syntax

The user and the system header files are included using the preprocessing directive #include. It has the following two forms:

#include <file>

System header files are formatted in this manner. In a normal list of system folders, it looks for a file with the name file. When producing your source code, you can prepend folders to this list with the -I option.

#include "file"

For header files in your own programs, use this form. It looks in the directory where the current file is located for a file with the name file. When producing your source code, you can prepend folders to this list with the -I option.

Include operation

The C preprocessor is instructed to scan the given file as input before moving on to the rest of the current source file using the #include directive. The output from the preprocessor is composed of the output that has previously been produced, the output from the included file, the output from the text after the #include directive, and finally, the output.

If we have a file named hdr.h that contains a function definition, we can include it in our main program like this:

#include "hdr.h"

The contents of hdr.h will be inserted at the location of the #include directive during the preprocessing phase.

For example:

// File 1 hdr.h

#include <stdio.h>

void msg()

{

printf("Hello, Preprocessors\n");

}

//File 2 - hdr.c, file containing main()

#include <stdio.h>

#include "hdr.h"

void main()

{

msg();

}

In this example:

hdr.h contains the definition for the function msg(). The header file is included in hdr.c file. From hdr.c file (which contains main() function), we call msg() function, defined in hdr.h file. The output of the program will be:

Hello, Preprocessors

Once-only headers

If a header file happens to be included twice, the compiler will process its contents twice, and it will result in an error. The standard way to prevent this is to enclose the entire real contents of the file in a conditional, as shown below:

#ifndef HEADER_FILE

#define HEADER_FILE

…………. // the entire header file

#endif

This construct is commonly referred to as a wrapper #ifndef. If the header is included once more and HEADER_FILE is specified, the condition will be false. As a result, the full contents of the file will be skipped over by the preprocessor, and the compiler will not see it again.

Using #ifdef for different computer types (Computed includes)

Sometimes, you have to decide which of the several header files to include in your software. For instance, they may include configuration options to be applied to various operating systems. To do this, we may use the following chain of conditionals:

#if SYSTEM_1

# include "system_1.h"

#elif SYSTEM_2

# include "system_2.h"

#elif SYSTEM_3

...

#endif

However, when it expands, it gets laborious, thus the preprocessor instead provides the option to use a macro for the header name. It is known as a calculated include. We just enter a macro name there in place of a header name as the direct parameter of #include.

#define SYSTEM_H "system_1.h"

...

#include SYSTEM_H

SYSTEM_H will be expanded, and the preprocessor will look for system_1.h as if the #include had been written that way originally. SYSTEM_H could be defined by your Makefile with a -D option.

Conclusion

In this chapter, we explored various aspects of preprocessors in C programming. We discussed the #define directive, #ifdef and #ifndef preprocessor directives, and the #include preprocessor. Additionally, we delved into macro definition, conditional compilation, and ANSI C predefined macros. We also covered preprocessor operators like the macro continuation () operator, stringize (#) operator, token pasting (##) operator, and defined () operator. Furthermore, we examined the usage of C-header files, char*test();, and once-only headers, and how to use #ifdef for different computer types. Understanding preprocessors is essential for C programmers to enhance code modularity and manage cross-platform compatibility effectively.

Exercise

What is a preprocessor in C programming? What is the purpose of a preprocessor?

What are some common preprocessor directives?

How do you include a file using the preprocessor?

What is the #define directive used for?

How do you use the #ifdef and #ifndef directives?

What will be the output of the following code?

#include <stdio.h>

#define VALUE 42

int main()

{

printf("The value is: %d\n", VALUE);

return(0);

}

What is the difference between #ifdef and #if?

What is the #include directive used for?

Can you use variables in #define statements?

What is the purpose of #pragma?

What will be the output of the following code?

#include <stdio.h>

#define CUBE(x) x * x * x

int main()

{

int result = CUBE(3);

printf("Result: %d\n", result);

return(0);

}

How do you use the #error directive?

What is the #undef directive used for?

Can you use comments in #define statements?

What will be the output of the following code?

#include <stdio.h>

#define MAX(a, b) ((a > b) ? a : b)

int main()

{

int x = 5, y = 8;

int max = MAX(x, y);

printf("Maximum: %d\n", max);

return(0);

}

Write a program to demonstrate purpose of the #line directive.

What will be the output of the following code?

#include <stdio.h>

#define STR(x) #x

int main()

{

printf("Value of STR(Hello): %s\n", STR(Hello));

return(0);

}

Write a program to use the #warning directive.

What is the #ifdef() directive used for?

What is the #elif directive used for? Write a program to demonstrate it.

How do you use the #pragma warning directive?

Can you use preprocessor directives inside functions?

What is the purpose of the #pragma once directive?

What will be the output of the following code?

#include <stdio.h>

#define CONCAT(a, b) a ## b

int main()

{

int ab = 42;

printf("Value of ab: %d\n", CONCAT(a, b));

return(0);

}

Join our book’s Discord space

Join the book’s Discord Workspace for Latest updates, Offers, Tech happenings around the world, New Release and Sessions with the Authors:

https://discord.bpbonline.com

CHAPTER 10C Graphics

Introduction

Welcome to the world of C graphics programming, where you will discover the art of visualizing your code. In this chapter, we will delve into the exciting realm of C graphics and explore how to bring life to your programs through colors, animations, and shapes. From understanding the fundamentals of graphics to mastering the techniques of input handling and shape creation, this chapter will equip you with the skills to craft captivating visual experiences using the power of C.

Structure

The chapter covers the following topics:

C graphics

Colors in C graphics programming

Animations and delay

Input handling

Text mode graphics function

Program to draw various shapes

Objectives

By exploring the fundamentals of graphics programming, you will grasp how it seamlessly integrates with the broader scope of C programming. Delving into color manipulation, you will learn how to breathe life into your visuals through a rich spectrum of hues, adding depth and engagement to your graphical creations.

As you dive into animations and delays, you will gain the ability to inject movement and dynamic behavior into your graphical elements, creating visually compelling and interactive programs. Mastering input handling techniques will equip you to build user-friendly applications that seamlessly respond to user actions, enhancing the overall user experience.

Furthermore, you will harness the power of text mode graphics functions, enabling you to design graphical interfaces using text characters. This skill adds a layer of accessibility and creativity to your programming toolkit. By developing a program to draw various shapes, you will refine your understanding of shape creation and positioning, enhancing your capacity to produce aesthetically pleasing graphics.

C Graphics

Creating 2D and 3D graphics, visual effects, and animations in C requires the use of graphics libraries and APIs. These libraries offer the tools and features required to carry out operations, including establishing windows, illustrating shapes and pictures, and managing user input. It is important to have a fundamental grasp of computer graphics topics like geometry, transformations, and color models to utilize these libraries.

In C, developing a visual model by adding colors and other shapes is referred to as graphics. You may execute it in the C terminal by importing the graphics.h library into the GCC compiler. It is also possible to trace the circle, line, eclipse, and other geometric shapes. The use of object-oriented programming is one of the key strategies in this circumstance. There are no built-in debuggers in C since it relies on low-level programs.

We need to utilize C programming and a few functions to implement the visuals. Therefore, a window or canvas serves as the main component for displaying the outcome. In the chapter, the DevC++ IDE is used for that since we need a strong foundation to construct a useful drawing functionality.

We also require a particular package to work with graphics, which we can obtain and install using WinBGIm.rawing techniques; alternatively, we might use the API to produce visuals.

Games, projects, animation, and other things may all be created using graphics programming. It differs from standard C programming as there are not as many errors or warnings in the program since complicated reasoning is not used. In C, graphics programming enables us to draw curves, paint an object with various hues and patterns by using mathematical functions, and produce straightforward animations like balls that jump and cars that drive.

To complete a task in graphics programming, you must use common library functions. A function will carry out a certain task for us if you provide a specific set of parameters. The function initgraph, employed to initialize the graphics mode; since we are by default in the text-mode in C compilers, is the primary function we need to know to kick-start graphics programming in C. We utilize the initgraph function in our C application to set the graphics mode. Every graphics application should include graphics.h header file since it contains the definition of the initgraph () function.

C libraries for graphics

Although C is a powerful programming language, it does not come with built-in graphics capability. The essential methods and procedures to carry out activities linked to graphics are provided by a number of third-party graphics libraries, though. Several well-liked C graphics libraries are as follows:

OpenGL: High-performance rendering of 2D and 3D graphics is made possible by the robust cross-platform OpenGL 3D graphics library. It offers a low-level, hardware-accelerated interface and is frequently employed in simulations and game development.

Simple DirectMedia Layer: A multi-platform programming library called SDL is intended to give users low-level access to hardware for the keyboard, mouse, joystick, and graphics. Multimedia applications and the development of 2D games use this library frequently.

Allegretto: A game development toolkit called Allegro offers routines for handling input, graphics, and sound across various platforms.

Simple and Fast Multimedia Library: A contemporary multimedia library called SFML enables networking, 2D graphics, audio, and more. Both novice and seasoned developers may use it because it is designed to be simple.

Choosing the right graphics library

The graphics library you choose will rely on the needs and objectives of your project. OpenGL may be the ideal choice if you are aiming for 3D visuals and quick rendering. SDL or SFML may be appropriate for 2D games and multimedia applications because to their simplicity and adaptability.

You must have a working grasp of C programming as well as a fundamental understanding of data structures and algorithms before beginning to program visuals.

Configuring the graphics environment

Let us now talk about the graphics environment.

Initialization of the graphics mode

We must set up the appropriate parameters and establish the graphics mode before we can draw on the screen. Depending on the graphics library we use, the initialization procedure may change.

Setting up Borland Graphics Interface for Windows

There are a few steps we must take to initialize the WinBGIm library (Borland Graphics Interface for Windows) in C. For 2D graphics programming in C on Windows systems, the WinBGIm library is a straightforward graphics library that is used. Please keep in mind that this library may not function with more recent C compilers because it is only compatible with compilers like Turbo C++ and Dev-C++.

Get and set up WinBGIm

The WinBGIm library must first be downloaded and installed. The library may be found online by searching for WinBGIm or BGI Graphics for Windows. Download the version that works with your compiler.

Setting up your compiler

Typically, WinBGIm comes pre-configured for Dev-C++ and Turbo C++. If you experience any problems, be sure to set up your compiler to include the required directories and correctly link the library.

Add the necessary header file

Include the graphics.h header file from the WinBGIm library in your C code. All of the functions and declarations required for graphics programming are present in this header file.

#include <graphics.h>

Initialize the graphics mode

Before we can draw anything on the screen, we need to initialize the graphics mode. The initgraph function from the graphics.h header is used for this purpose.

The following code may be used to initialize the graphics drivers on a machine using the graphics.h library’s initgraph () method:

void initgraph (int *graphicsMode, char *driverDirectoryPath, int *graphicsDriver);

This switches the system into graphics mode after initializing the graphics system by loading the graphics driver that we pass as input to the initgraph function. Additionally, it resets all of the visual settings to the default values, including current location, color, palette, and more. The notion of the initgraph function’s input parameters is provided by the following:

graphicsDriver: This (pointer) variable identifies the chosen graphics driver. This command tells the compiler which graphics driver to use or allows the driver to be automatically detected. All of our applications will make use of the graphics.h library’s DETECT macro, which tells the compiler to detect the graphic’s driver automatically.

graphicsMode: This (pointer) variable identifies the desired graphics mode. Initgraph will set *graphmode to the highest resolution offered by the identified driver if *graphicsdriver is set to DETECT.driverDirectoryPath: This parameter defines the location of the BGI files used to store graphics driver files. But if the path is left blank, it will look for driver files in the current working directory.

In order to use the Turbo C compiler, we must modify the path of the BGI directory in graphics programs.

It is good practice to use the closegraph () method at the conclusion of our graphics application to unload the graphics drivers and return the screen to text mode.

Drawing on the screen

Once the initialization of graphics mode is done, we can use various functions provided by the WinBGIm library to draw various shapes like lines, shapes, points, and even text on the screen.

Example:

#include <graphics.h>

int main () {

int gdr, gmode;

gd = DETECT;

initgraph (&gdr, &gmode, "C:\\BGI");

// Draw a line

line (100, 100, 300, 200);

// Draw a rectangle

rectangle (150, 250, 250, 350);

getch (); // Wait for a key press before closing the window

closegraph ();

return (0);

}

Closing the graphics mode

When your graphics activities are finished, do not forget to free system resources by closing the graphics mode using the closegraph function.

After completing these steps, you ought to have successfully initialized WinBGIm in C and begun utilizing the library to create straightforward graphics programs for the Windows platform. Please remember that WinBGIm is out of date and might not be supported by contemporary compilers. You might want to think about utilizing alternative libraries like SDL or OpenGL, which are cross-platform and actively updated, for more up-to-date graphics development in C.

Optionally, we can set up other Graphics library modes like SDL.

Setting up graphics mode using SDL

#include <SDL2/SDL.h>

int main () {

// Initialize SDL

if (SDL_Init(SDL_INIT_VIDEO) != 0) {

printf ("SDL initialization Error: %s\n", SDL_GetError());

return 1;

}

// do the required operations…

SDL_Quit();

return (0);

}

Understanding coordinates and resolution

Coordinates and resolution are two important concepts when dealing with graphics programming. The positive x-axis and the positive y-axis both normally extend to the right and downward, respectively, from the origin (0,0) at the top-left corner of the screen. The amount of horizontally and vertically accessible pixels depends on the screen’s resolution. A resolution of 800x600, for instance, indicates that there are 800 pixels in width and 600 pixels in height.

Basics of color

Colors on a screen are typically made up of combinations of red, green, and blue elements. To assign a specific color, the setcolor (number) function can be used, where the number corresponds to a color code. For instance, the color yellow is represented by the number 14. Applying shading and coloring can enhance the visual appeal of an image.

The Red, Green, and Blue (RGB) model is widely used in graphics programming to represent colors. The intensity of each color in the output can range from 0 to 255 for each color component. Pure red, green, and blue, for instance, are represented by (255, 0, 0), (0, 255, 0), and (0, 0, 255), respectively. You may produce a huge variety of colors by combining various RGB values.

In C graphics programming, we can use various functions to draw shapes on the screen, besides adding text wherever needed.

Operations for simple drawings

After setting up our graphics environment, let us examine some fundamental drawing procedures.

The majority of graphics libraries provide routines for drawing dots, lines, and other forms on the screen.

WinBGIm provides various functions to draw basic graphics primitives. Here are some essential drawing functions:

line (): line function is used to draw a line from a coordinate point (x1,y1) to another coordinate point (x2,y2) that is:

void line (int x1, int y1, int x2, int y2);

where:

(x1, y1) and (x2, y2) are the starting and end points of the line.

lineto (): This function draws a line from current position (CP) to a specified point.

void lineto (int x, int y);

where:

(x, y) is the point up to which the line would be drawn.

circle (): circle function is used to draw a circle.

void circle (int x, int y, int radius);

where:

x and y represent the coordinates of the central point; radius specifies the radius of the circle. The code given draws a circle.

ellipse (): ellipse is used to draw an ellipse.

void ellipse (int x, int y, int a, int b, int c, int d);

We must specify the ellipse’s center coordinates (x, y) and semi-axis lengths (a, b) to draw it. Here is an example program that uses the graphics.h library’s ellipse function to create an ellipse:

ellipse (100, 100, 0, 360, 50, 80);

This code calls the ellipse function to draw an ellipse with center at (100, 100) and semi-axis lengths 50, 80.

rectangle: Draws a rectangle with the specified top-left and bottom-right corners.

void rectangle (int left, int top, int right, int bottom);

where:

(left, top) represents coordinates of the top-left corner point while (right, bottom) represents coordinates of the bottom-right corner point.

bar: Draws a filled rectangle (bar) with the specified top-left and bottom-right corners.

void bar (int left, int top, int right, int bottom);

where:

(left, top) represents coordinates of the top-left corner point while (right, bottom) represents coordinates of the bottom-right corner point.

arc: Draws a circular arc with the specified center, start angle, and end angle.

void arc (int x, int y, int s_angle, int e_angle, int r);

where:

(x, y) represent the coordinates of the center point of the circle, s_angle and e_angle are the start and end angles, respectively, and r represents radius.

graphresult (): It is a graphical function that returns the value 0 if the graphics are detected correctly, and the driver is correctly initialized to the correct graphics mode otherwise, it returns some error code than 0.

int graphresult (void);

It returns the error code for the last graphics operation that reported any of the following errors, as shown in Table 10.1:

Error code

String constant

Error message

0

grOk

-

-1

grNoInitGraph

Graphics not installed (use initgraph)

-2

grNotDetected

Graphics hardware missing

-3

grFileNotFound

Device driver file missing

-4

grInvalidDriver

Invalid device driver file

-5

grNoLoadMem

Insufficient memory to load driver

-6

grNoScanMem

Insufficient in scan fill

-7

grNoFloodMem

Insufficient in flood fill

-8

grFontNotFound

Font file missing

-9

grNoFontMem

Insufficient memory to load font

-10

grInvalidMode

graphics mode Invalid for selected driver

-11

grError

Graphics error

-12

grIOerror

Graphics I/O error

-13

grInvalidFont

font file invalid

-14

grInvalidFontNum

font number invalid

-15

grInvalidDeviceNum

device number invalid

-18

grInvalidVersion

version number invalid

Table 10.1: graphresult error codes

Note: We should store the value of graphresult into a variable and then test it as the variable maintained by graphresult is reset to 0 after graphresult has been invoked.

grapherrormsg: This function returns the message string corresponding to the errorcode returned by the graphresult () function.

char * grapherrormsg (int errcode);

Where:

errcode is the Error-code

The function returns a pointer to the error message string associated with errorcode, the value returned by graphresult.

cleardevice (): This function is used to clear the screen in the graphical mode as clrscr () in text mode.

void cleardevice ();

We can use various graphical functions available in the C library to draw geometrical objects.

moveto (): It is used to move cursor in display screen to the specified co-ordinates by the value x and y.

void moveto (int x, int y);

Where:

(x, y) represent new coordinates.

outtext (): Prints the text string in the current position of screen.

void outtext (char *);

Where:

(char * represents the string to be displayed on screen.

Example:

outtext ("Sample text")

would print Sample text to the output.

Outtextxy (): Function display text or string at a specified point (x,y) on the screen.

void outtextxy (int x, int y, char *string);

Where:

x, y are coordinates of the point and third argument contains the string to be displayed.

drawpoly (): Function is used to draw polygons like triangle, rectangle, pentagon and more.

void drawpoly (int n, int *polypoints);

where:

num indicates (n + 1) number of points, n is the number of vertices in a polygon. polypoints refers to a sequence of (n*2) integers.

Some functions add beauty to presentation of our graphics programs:

blink (): Helps to make a character blink on the screen window.

delay (): It pauses the execution of the program for some period of time.

void delay (unsigned int)

For example:

void delay (2000);

This would halt execution of the program for 2 seconds (2000 milliseconds).

Note: delay () is not defined in graphics.h header file (defined in dos.h). We can use it in text mode as well as graphics mode

getmaxx (): Returns the maximum X coordinate for the current graphics mode and driver.

int getmaxx ();

getmaxy (): Returns the maximum Y coordinate for the current graphics mode and driver.

int getmaxy ();

getx (): function returns the X coordinate of current position.

int getx ();

gety (): function returns the X coordinate of current position.

int gety ();

Note: graphics.h is a header file in C that provides access to a graphics library. We need to install the graphics library on your system before using it.

Colors in C graphics programming

In C graphics programming, colors can be specified using RGB values or color constants. To set a color in C graphics, you can use the setcolor function from the graphics library. Some functions, along with their uses are discussed as follows:

putpixel (): This function plots a pixel of specified color at coordinate (x, y).

putpixel (int x, int y, int color);

For example: to draw a RED color pixel at (5, 40) we will write putpixel (5, 50, RED);

In C programs, putpixel function can be used to draw circles, lines, and ellipses using various algorithms.

setcolor (): This function sets the current drawing color.

void setcolor (int color);

For example: void setcolor (RED) sets the current drawing color to RED, where RED is a color constant defined in the graphics library.

setgbcolor (): This function is used to set the current background color.

void setbgcolor (int color);

For example, setbgcolor (0) would set the current background color to black.

setbgcolor (255, 0, 0);

As RGB values of (255, 0, 0) denote the strongest red and no green or blue, this changes the current background color to pure red.

The 16 colors that C Graphics defines can be used to modify the background, text, and closed form colors as well as the current drawing color. Either color constants like setcolor (RED) or their equivalent integer codes like setcolor (4) can be used to specify a color. The color code is listed below in ascending order, as shown in Table 10.2:

Color

Code

Can it be used as background?

Can it be used as a foreground?

BLACK

0

Yes

Yes

BLUE

1

Yes

Yes

GREEN

2

Yes

Yes

CYAN

3

Yes

Yes

RED

4

Yes

Yes

LIGHTBLUE

9

No

Yes

LIGHTGREEN

10

No

Yes

LIGHTCYAN

11

No

Yes

LIGHTRED

12

No

Yes

MAGENTA

5

Yes

Yes

BROWN

6

Yes

Yes

LIGHTGRAY

7

Yes

Yes

DARKGRAY

8

No

Yes

WHITE

15

No

Yes

BLINK

128

No

*

LIGHTMAGENTA

13

No

Yes

YELLOW

14

No

Yes

Table 10.2: Color codes

For displaying blinking characters in text mode, we add BLINK (Defined in conio.h) to the foreground color.

floodfill (): To fill a shape, use this function. The area is filled using the current fill pattern and fill color. (X, Y) is any point on the screen; if (X, Y) is inside a form’s boundary, that shape will be filled; otherwise, it will be filled outside. Use setfillstyle to alter the fill color and pattern. The following code first creates a circle before filling it.

void floodfill (int x, int y, int border);

C Program on graphics colors

The following code is a C program on graphics colors:

#include<graphics.h>

#include<stdio.h>

#include<conio.h>

int main () {

int gd = DETECT, gm;

initgraph (&gd, &gm, "C:\\TURBOC3\\BGI");

setrgbcolor (255, 0, 0); // set background color

setcolor (BLUE); //Set forecolor and draw what you want to draw in BLUE color

closegraph ();

return (0);

}

Note: Numerous Borland compilers for the DOS operating system have incorporated the Borland Graphics Interface (BGI) graphics library since 1987. The library loads vector fonts (*.CHR) and graphic drivers (*.BGI) from disc to provide programmers with device-independent graphics capability. Using graphics.lib/graphics.h and BGI in C/C++.

Input handling

WinBGIm also provides functions to manage mouse and keyboard input. The functions used are discussed as follows:

ismouseclick (): This function checks if a mouse click has occurred.

int ismouseclick (int kind);

getmouseclick (): This function gets the coordinates and button status of the last mouse click.

void getmouseclick (int kind, int *x, int *y);

kbhit (): This function checks if a keyboard key has been pressed.

int kbhit ();

getch (): This function waits for a keyboard key to be pressed and returns the key’s ASCII value.

char getch ();

Animations and delay

We can use the delay function to introduce a pause between successive frames, to create effects of animations in C Graphics.

delay (): It pauses the execution of the program for some period of time.

void delay (unsigned int)

Double buffering

Double buffering helps to decrease animation flicker. It is necessary to build two graphics buffers: One for off-screen rendering and the other for on-screen display. The getbuffer and putbuffer methods may be used for this.

Example:

For simple animation:

#include <stdio.h>

#include <graphics.h>

int main () {

int gd = DETECT, gm;

initgraph (&gd, &gm, "");

int x = 50, y = 50;

int radius = 30;

int dx = 5, dy = 3;

while (!kbhit()) {

cleardevice();

setcolor(YELLOW);

circle(x, y, radius);

floodfill(x, y, YELLOW);

x += dx; // Update the circle’s position

y += dy;

if (x <= radius || x >= getmaxx() - radius) // Bounce off the walls

dx = -dx;

if (y <= radius || y >= getmaxy() - radius)

dy = -dy;

delay (50);

}

}

Text mode graphics functions

These graphics functions help place text in output/ area of the screen. These functions are defined in the header file <conio.h>. Some of those text mode graphics functions are:

window (): It sets particular area on the output screen and displays the output text on that area.

void window (a, b, c, d);

The co-ordinates (a, b) represents the upper left corner of window and (c, d) is the lower right corner of the display window.

clrscr (): It is used to clear the screen and locates the curser at the beginning of the screen.

void clrscr ();

gotoxy (): It moves the curser at specified co-ordinate position.

void gotoxy (x, y);

It places cursor at coordinate (x, y).

putch (): It writes a character given to function to the user defined area of window.

void putch ('i');

It writes character 'i' on the current cursor position.

cputs (): It writes a string to the user defined window screen.

void cputs ("Zain");

It prints Zain on the current cursor position.

Settextstyle ():

To control the text output size, direction, and font for graphics mode, utilize ‘settextstyle’ function. The text in graphics window may be made to look anyway we like. Here is how to use the ‘settextstyle’ function in syntax:

void settextstyle (int font, int direction, int charsize);

Where:

font: Specifies the font for the text. which can take any of the following constants:

#define DEFAULT_FONT 0 // Default font (8x8)

#define TRIPLEX_FONT 1 // Triplex font

#define SMALL_FONT 2 // Small font (6x8)

#define SANS_SERIF_FONT 3 // Sans-serif font

#define GOTHIC_FONT 4 // Gothic font

We can through use user-defined fonts with values greater than 4.

direction: Specifies the direction of the text. It can take one of the following constants:

#define HORIZ_DIR 0 // Horizontal text (left-to-right)

#define VERT_DIR 1 // Vertical text (bottom-to-top)

#define USER_CHAR_SIZE 2 // Text direction based on charsize

When using USER_CHAR_SIZE, the text will be displayed based on the character size (charsize) we provide.

charsize: Specifies the size of the characters in the text. It represents the height of the characters in pixels.

For example:

settextstyle (TRIPLEX_FONT, HORIZ_DIR, 3);

// Set text style to Triplex font, horizontal direction, and character size of 3

Remember that the font styles you may use may change depending on your compiler and the WinBGIm library version you are using. Additionally, the operating system and graphics resolution may have an impact on the text’s size and style.

C graphics program to draw shapes

The following is a C graphics program to draw different shapes:

#include<graphics.h>

#include<conio.h>

int main () {

int gd = DETECT, gm;

 int arr [] = {320, 150, 400, 250, 250, 350, 320, 150};

// for drawpoly ()

/* initialization of graphic mode */

initgraph (&gd, &gm, "C:\\TC\\BGI");

setcolor (BLUE); //Set the color BLUE and draw a line

arc(100, 100, 0, 135, 50); //arc

line (100,100,200, 200); //Line

rectangle (100,100,200,200); //Rectangle

circle (50,50,50); // Circle

drawpoly (4, arr); // polygon (Triangle) 4 points/ 3 vertices in this example

return (0);

closegraph ();

}

Drawing multiple concentric circles

#include <graphics.h>

int main () {

int gd = DETECT, gm;

int x = 300, y = 200, rad;

initgraph (&gd, &gm, "");

for (rad = 20; rad <= 120; rad = rad + 20)

circle (x, y, rad);

getche ();

closegraph ();

return (0);

}

#include <graphics.h>

int main () {

int gd = DETECT, gm;

initgraph (&gd, &gm, "C:\\TC\\BGI");

outtextxy (100, 100, "Hello World");

return (0);

closegraph ();

}

Setcolor (): In Turbo Graphics a number is assigned to each color. There are 16 colors which we can use. To put it in a more precise way, the number of available colors depends on current graphics mode and driver. For Example: - BLACK is assigned 0, RED is assigned 4 and so on. setcolor function is used to change the current drawing color, for example: setcolor (BLACK) or setcolor (0) changes the current drawing color to BLACK.

Note: The default drawing color is WHITE.

Table 10.3 depicts different color macros.

COLOR MACRO

INTEGER Assigned

WHITE

15

YELLOW

14

LIGHTMAGENTA

13

LIGHTRED

12

LIGHTCYAN

11

LIGHTGREEN

10

LIGHTBLUE

9

DARKGRAY

8

LIGHTGRAY

7

BROWN

6

MAGENTA

5

RED

4

CYAN

3

GREEN

2

BLUE

1

BLACK

0

Table 10.3: Color Macros

Setbkcolor (): Function sets the background color,

setbkcolor (int color_code);

Where:

color_code: Specifies the color code of the background color

For example, setbkcolor (BLUE) changes the current background color to BLUE.

You can get current position using getx and gety function.

C program for drawing a shape

#include<graphics.h>

int main () {

int gd = DETECT,gm, left= 100, top=100, right=200, bottom=200, x= 300, y=150, radius=50;

initgraph (&gd, &gm, "C:\\TC\\BGI");

rectangle (left, top, right, bottom);

circle (x, y, radius);

bar (left + 300, top, right + 300, bottom);

line (left - 10, top + 150, left + 410, top + 150);

ellipse (x, y + 200, 0, 360, 100, 50);

outtextxy (left + 100, top + 325, "My First C Graphics Program");

setcolor (RED);

circle (100,100,50);

floodfill (100, 100, RED); // fills circle with RED color

return (0);

closegraph ();

}

C program for the above graphics functions

#include<graphics.h>

#include<conio.h>

int main () {

int gd = DETECT, gm, mx, x, y;

char array [100];

initgraph (&gd, &gm, "C:\\TC\\BGI");

mx = getmaxx ();

x=getx ();

y=gety ();

sprintf (array, "Current X and Y coordinates are %d and %d.", x, y);

sprintf (array, "Maximum X coordinate for current graphics mode and driver = %d.", max_x);

outtext (array);

return (0);

closegraph ();

}

C graphics programs

Some C programs are as follows:

Drawing concentric circles

#include <graphics.h>

int main () {

int gd = DETECT, gm;

int x = 320, y = 240, radius;

initgraph (&gd, &gm, "C:\\TC\\BGI");

for (radius = 25; radius <= 125; radius = radius + 20)

circle (x, y, radius);

return (0);

closegraph ();

}

Program for moving car

#include <graphics.h>

#include <dos.h>

int main () {

int i, j = 0, gd = DETECT, gm;

initgraph (&gd,&gm, "C:\\TC\\BGI");

settextstyle (DEFAULT_FONT, HORIZ_DIR,2);

outtextxy (25,240,"Press any key to view the moving car");

return (0);

for (i = 0; i <= 420; i = i + 10, j++) {

rectangle (50+i,275,150+i,400);

rectangle (150+i,350,200+i,400);

circle (75+i,410,10);

circle (175+i,410,10);

setcolor (j);

delay (100);

if (i == 420)

break;

if (j == 15)

j = 2;

cleardevice ();

}

closegraph ();

return (0);

}

Program countdown code

#include <graphics.h>

#include <dos.h>

#include <conio.h>

int main () {

int gd = DETECT, gm, i;

char a[5];

initgraph (&gd, &gm, "C:\\TC\\BGI");

settextjustify ( CENTER_TEXT, CENTER_TEXT );

settextstyle (DEFAULT_FONT, HORIZ_DIR,3);

setcolor (RED);

for (i = 30; i >=0; i--) {

sprintf (a,"%d", i);

outtextxy (getmaxx ()/2, getmaxy ()/2, a); // Timer in middle of the window

delay (1000); // delay of 1000 Milliseconds ( =1 Sec)

if (i == 0)

break;

cleardevice ();

}

closegraph ();

return (0);

}

Program to draw a 3D bar chart

#include <graphics.h>

int main () {

int gd = DETECT, gm;

initgraph (&gd, &gm, "C:\\TC\\BGI");

setcolor (YELLOW);

rectangle (0,30,639,450);

settextstyle (SANS_SERIF_FONT, HORIZ_DIR,2);

setcolor (WHITE);

outtextxy (275,0,"Bar Chart");

setlinestyle (SOLID_LINE,0,2);

line (100,420,100,60);

line (100,420,600,420);

line (90,70,100,60);

line (110,70,100,60);

line (590,410,600,420);

line (590,430,600,420);

outtextxy (95,35,"Y");

outtextxy (610,405,"X");

outtextxy (85,415,"O");

setfillstyle (LINE_FILL,BLUE);

bar (150,100,200,419);

setfillstyle (XHATCH_FILL, RED);

bar (225,150,275,419);

setfillstyle (WIDE_DOT_FILL, GREEN);

bar (300,200,350,419);

setfillstyle (INTERLEAVE_FILL, MAGENTA);

bar (375,125,425,419);

setfillstyle (HATCH_FILL, BROWN);

bar (450,175,500,419);

return (0);

}

Program to draw a pie chart

#include <graphics.h>

int main () {

int gd = DETECT, gm, midx, midy;

initgraph (&gd, &gm, "C:\\TC\\BGI");

setcolor (MAGENTA);

rectangle (0,40,639,450);

settextstyle (SANS_SERIF_FONT, HORIZ_DIR,2);

setcolor (WHITE);

outtextxy (275,10,"Pie Chart");

midx = getmaxx ()/2;

midy = getmaxy ()/2;

setfillstyle (LINE_FILL,BLUE);

pieslice (midx, midy, 0, 75, 100);

outtextxy (midx+100, midy - 75, "20.83%");

setfillstyle (XHATCH_FILL,RED);

pieslice (midx, midy, 75, 225, 100);

outtextxy (midx-175, midy - 75, "41.67%");

setfillstyle (WIDE_DOT_FILL, GREEN);

pieslice (midx, midy, 225, 360, 100);

outtextxy (midx+75, midy + 75, "37.50%");

return (0);

}

Conclusion

In essence, this chapter has demystified the captivating world of C graphics programming. Through mastery of color, animation, and shape manipulation, you now possess the ability to weave captivating visual narratives with your code. With the skills acquired in input handling and text mode graphics, you are poised to develop applications that seamlessly fuse interactivity with visual appeal. Remember, the synergy between creativity and technical acumen opens the doors to endless possibilities in the realm of C graphics. Armed with these newfound abilities, you are ready to embark on a journey of crafting code that not only functions flawlessly but also enchant and engages users on a visual level.

Exercise

What is graphics programming in C?

What are some libraries available for graphics programming in C?

What is the role of graphics.h library in C programming?

How do you set up a graphics window in C?

How do you draw basic shapes in C graphics?

How do you fill shapes with color in C graphics?

How do you draw text in C graphics?

Write a program in C to draw various shapes like line, circle, rectangle, square, arc, and semi-circle.

How do you create animation in C graphics?

What are some common issues faced in C graphics programming?

How do you debug C graphics programs?

What is double buffering in C graphics?

How do you implement double buffering in C graphics?

What is the difference between direct and buffered graphics?

How do you handle user input in C graphics?

How do you create interactive graphics in C?

How do you create 3D graphics in C?

Write a program in C to draw a pie chart and bar chart using Switch statement.

What is OpenGL, and how is it used in C graphics programming?

How do you create a game in C graphics?

How do you optimize graphics performance in C?

What are some resources for learning more about graphics programming in C?

Join our book’s Discord space

Join the book’s Discord Workspace for Latest updates, Offers, Tech happenings around the world, New Release and Sessions with the Authors:

https://discord.bpbonline.com

Index

Symbols

2D strings 217

characters, accessing 219, 220

declaration 218

initialization 218

input reading 218

A

algorithm 20

algorithm time complexity cases 25, 26

features 20

implementation challenges 26

iterative statement algorithm 24

selective statement algorithm 23

writing 20-24

algorithm efficiency 25

benchmarking 26

importance 25

theoretical analysis 26

ANSI C predefined macros 390, 391

application of pointers

array manipulation 256-258

direct memory manipulation 246

dynamic memory allocation 246-249

memory releasing 249

memory resizing 249

parameters, passing to functions 254-256

pointer arithmetic 260-262

pointer comparisons 263-265

pointer to pointer 267-269

relational comparison 264, 265

return pointer, from functions 265-267

string manipulation 259, 260

string pointers 252-254

application software

custom-built software 11

packaged software 11

arithmetic operators 54

array-based programs 189

examples 184-196

array elements

accessing 181

modifying 182, 183

array manipulation 181

array elements, accessing 256

array elements, changing directly 257

array elements, swapping 258

modifying arrays 181

searching arrays 181

sorting arrays 181

traversing arrays 181

arrays 177

bounds and array size 180

declaring 178, 179

initializing 179, 180

one-dimensional array 178

parameter passing 182

passing, to functions 182

size 182

arrow (->) operator

for accessing structure members 285

assignment operators 58-61

B

base address 180

binary file handling 325

complex data structures, working with 328

considerations 328

data reading 326

data writing 326, 327

error handling 328

file closing 326

file opening 326

file pointer, moving 327

file size 327

bitwise operators 56

bottom-up modular programming 16, 17

break statement 136, 137

C

callback function 165

Central Processing Unit (CPU) 7

C graphics 399, 400, 401

animations 411

color 405

color, setting 409, 410

coordinates 404

double buffering, in animation 412

libraries 401

library, selecting 401

operations, for simple drawings 405-409

programs 417-420

program, to draw shapes 414-417

resolution 404

text mode graphics functions 412-414

character arrays 197

character constant 88

character constants 69

char data type 83, 84

C-header files 393, 394

include operation 394, 395

include syntax 394

circle() function 405

cleardevice () function 407

comma operator 61

Comma Separated Values (CSV) files

binary files 325

text files 325

working with 325

computers 6

computer system 8, 9

assembler 12

compiler 11, 12

debugger 12

hardware 9, 10

interpreter 13

language translators 13

software 10

computer system functionality

ALU 8

communication 8

control unit 8

execution cycle 8

feedback loop 8

input 7

memory 8

operating system 8

output 8

processing 7

software programs 8

storage 8

computing systems

cloud computing infrastructure 6

embedded systems 6

mobile devices 6

personal computers 6

servers 6

supercomputers 6

conditional compilation 383

conditional operator 61

condition-based flowchart 32

constant definitions 381

Constants 68

character constants 69

defining 89, 90

enumeration constants 69

floating-point constants 69

integer constants 69

string literals 69

versus literals 90, 91

continue statement 137

control statements 101, 102

decision making statements 103

control structure macros 376, 377

C preprocessors 365

C program 51

basic programs, writing 94

comments 53

compilation 42

execution 43

keywords 53

life cycle 41

linking 42

loading 42

preprocessing 42

semicolons 53

sequential logic 95-98

source code, writing 42

whitespace 53, 54

C program components 50

comments 51

functions 50

header files and libraries 51

preprocessor directives 50

statements 51

variables 50

C programming language 40, 41

data types 62

operator precedence 60

operators 54

tokens 52

uses 41

custom-built software 11

D

Dangling Pointer Problem (DPP) 270

data hierarchy

bit 309

byte 310

database 311

data mart 311

data warehouses 311

field 310

file 310, 311

layers of data 309

record 310

data types, C

derived data types 62-64

initialization 66, 67

primitive data types 62

decision making statements

if-else-if ladder 106-109

if...else statement 104, 105

if statement 103, 104

loop statements 126

nested conditions 114-118

nested if statements 109-113

switch statements 119-123

defined () operator 393

#define directive 367

conditional macro, defining 368

constant macro, defining 368

function-like macro, defining 368

redefining 368

delay function 411

direct file access 351

programs 351-355

discriminated union 295

double pointer 267-269

double variable 76

do-while loop 131

code block 131

condition 131

examples 132, 133

drawpoly() function 408

E

elif directive 375

ellipse() function 406

#else directive 374

embedded structure 293

#endif directive 375

enumeration constants 69

enumerations 279-281

errno 360

example 360, 361

error handling

in files 360

error handling and validation, in structures/unions 300

error reporting and exiting 302

graceful decline 302

graceful recovery 302, 303

input validation 301

member initialization 300

memory allocation errors, handling 300

union discriminant handling 301

exception handling 360

F

Factorial 129

factorial, of number 169

fclose() function 326

feof() function 328

ferror() function 328

fgetc() function 317

fgets() function 317

Fibonacci series 171

file access 328, 329

sequential file access 329

file descriptor 355

file closing 357

file positioning 356, 357

files, opening 355

files, reading 355, 356

files, writing 356

program examples 357, 358

file handling 307-309

data hierarchy 309

file inclusion directives 378

file I/O 312

file modes 312

append mode (a) 313

append mode (a+) read and append mode 313

read and write mode (w+ or truncate file mode) 313

read-only and read-write (r+) 313

read-only mode (r) 313

write mode (w) 313

file operations 313

error handling 321

file, closing 314, 315

file detection 321

file locking 321

file, opening 314

file positioning 319, 320

file, reading 317-319

file truncation 321

file, writing 315-317

files

importance 312

file types

binary files 322

input/output files 322

random access files 322

temporary files 322

text files 322

floating-point constant 69, 81

floating-point expressions 83

floating-point literal 87, 88

floating point number 81

floating-point types 81

double and float variables, printing 82

double/float variable, assigning to int 83

float variable 76

flowcharts 27

condition-based flowcharts 32

design guidelines 28

examples 29

finite and infinite loop 35, 36

history 27

loop-based flowchart 33

nested conditions 34

sequential flow-based flowcharts 29

symbols 27, 28

fopen() function 326

for loop 133

condition 133

examples 134, 135

initialization 133

update 133, 134

fprintf() function 316

fputc() function 315

fputs() function 316

fputw() function 315

fread() function 318, 319

fscanf() function 318

fseek() function 319, 327

ftell() function 319

function call 152

function declaration 151

function definition 152-155

functions 149-151

arguments 152

calling 155, 156

features 151

formal arguments 156

function prototypes 153

mismatch 161, 162

parameters 152

return scenarios 157, 158

return statement 156, 157

types 153

void functions 153

function signature 158-161

function variable scope 162, 163

callback function 165, 166

function as argument 166, 167

function parameters or variables 163

global variables 164

global variables, initializing 165

local variable, initializing 165

local variables 163

fundamental condition 168

fwrite() function 316, 317

G

getchar() function 77

getche() function 77

getch() function 77

global variable 165

global variable errno 360

GNU Assembler (GAS) 12

goto statement 138

grapherrormsg() function 407

graphicsDriver 403

graphics environment

Borland Graphics Interface, setting up for Windows 402

compiler, setting up 402

configuring 402

drawing, on screen 403

graphic mode, initializing 402, 403

graphics mode, closing 404

graphics mode, setting up with SDL 404

mode initialization 402

necessary header file, adding 402

WinBGIm 402

graphicsMode 403

graphresult() function 406

I

IDE, for C program 43

code compiling 49

code, executing 50

Dev-C++ 48

Eclipse 47

file, saving 49

GCC compiler 43

source code, creating 48

Turbo C++ for Windows 10+ 45-47

Visual Studio Code (VS Code) 44, 45

identifiers 67, 68

versus, variables 68

#ifdef preprocessor 369

#if directive 374

if-else-if ladder 106-109

if-else statement 23

#include preprocessor 370-372

ifndef preprocessor 370

if statement 103, 104

infinite loop 139

input handling 411

input-output, in C 71

control characters 73, 74

data reading, into float or double variable 76-78

data reading, supplied by user 74-76

fgets() method 79

fputs () function 79

integer, printing with field width 79, 80

output formatting 72

print () function 72

putchar () function 78

integer constants 69

integer data type 69, 70

integer expressions 70

precedence of operators 70, 71

integer literal 86

Integrated Development Environments (IDEs) 12

L

line() function 405

lineto() function 405

literals 86

character constants 88

floating-point literal 87, 88

integer literal 86, 87

string literals 88, 89

local variable 165

logical operators 56

loop-based flowchart 33

loop control statements 136

break statement 136, 137

continue statement 137

goto statement 138

infinite loop 139

nested loop 140-144

loops 127

do-while loop 131-133

for loop 133

while loop 127-130

loop statements

constituents 126, 127

Lvalue 85, 86

M

macro continuation (\) operator 392

macro definition directives 378, 379

program examples 379-381

macros

application 380

versus functions 382, 383

matrices 220, 221

2-D array (Matrix), declaring 221

2-D arrays elements access 224, 225

2-D arrays, initializing 222-224

2-D matrix, passing to function in C 225-235

Microsoft Assembler (MASM) 12

misplaced else 105, 106

modular programming 14, 15

module 15

moveto() function 407

N

nested condition statement 114-118

nested if statements 109-113

nested loop 140

examples 141-144

nested structures 292

accessing 292, 293

Netwide Assembler (NASM) 12

NULL pointer 269

O

Object-Oriented Programming (OOP) 19

features 19

once-only headers 395

#ifdef, using for different computer types 395, 396

operator precedence, C 60

left associativity 60

non-associativity 61

right associativity 60, 61

operators, C 54

arithmetic operators 54, 55

assignment operators 58, 59

bitwise operators 56-58

logical operators 56

relational operators 55

outtext () function 408

Outtextxy () function 408

P

packaged software 11

parsing 205

pointer comparisons

equality comparison 263, 264

inequality comparison 264

pointers 242

application 246

array of pointers 272, 273

dangling pointers 270

declaring 242, 243

dereferencing 244, 245

NULL pointers 269

references 244

using 242

value, accessing 243, 244

void pointers 270, 271

wild pointers 271

#pragma directives 385-389

pre-processor directives 378

conditional compilation 383, 384

file inclusion directives 378

macro definition directives 378, 379

#pragma directive 385-389

preprocessor operators

defined () operator 393

macro continuation (\) operator 392

stringize (#)operator 392

token-pasting operator (##) 393

preprocessors 365, 366, 367

control structure macros 376, 377

#define directive 367

#elif directive 374

#else directive 374

#endif directive 374, 375

#ifdef preprocessor 369, 370

#if directive 374

#include preprocessor 370-372

#undef preprocessor 372-374

procedural programming 14

programming methodology 2

Agile methodology 2

computer, as problem-solving tool 5

computer program 4, 5

DevOps methodology 3

problem-solving methodology 3

Scrum methodology 2

techniques 3, 4

Waterfall methodology 2

programming styles 13

bottom-up modular programming 16, 17

modular programming 14, 15

Object-Oriented Programming (OOP) 19

procedural programming 14

structured programming 18

top-down modular programming 15, 16

putchar () function 77

R

random access files 337, 338

advantages 339

data reading 338

data writing 338

examples 339

file position indicator 338

opening 338

programs 340-349

Random Access Memory (RAM) 9

Read-Only Memory (ROM) 9

recursion 168

memory usage 168-173

recursive functions

inbuilt functions 154

user-defined functions 154

relational operators 55

return type 152

rewind() function 320

Rvalue 85, 86

S

scanf 75

sequential file access

advantages 330

file, closing 330

file, opening 329

file pointer, moving 330

file, reading 330

file, writing 330

limitations 331

program examples 331-336

sequential flow-based flowcharts 29-31

Settextstyle () 413

static 92

stepwise refinement 15

storage classes 91

auto keyword 91

extern keyword 92, 94

register keyword 91

static 92, 93

strcat() function 207, 208

strchr() function 209

strcmp() function 208, 209

strcpy() function 207

stream file access 359

benefits 359

string concatenate 207

stringize (#)operator 392

string literal 88

string literals 69, 89

string manipulation

characters, accessing 259

characters, modifying 260

string concatenation 260

string pointers 252

strings 197, 198

case conversion 205

characters, accessing 198

concatenation 201

formatting 206

input and output 199, 200

iterating, through characters 198

length and manipulation functions 206-216

line of text, reading 200, 201

manipulation 201

replacing 203, 204

searching 203, 204

substring extraction 202

strlen() function 206

strncpy() function 210, 211

strtok() function 211, 212

struct keyword 279

structured programming 18

advantages 18

disadvantages 18

structures 278-282

array of structures 286, 287

array of structures, sorting 288

as function arguments 290, 291

declaring 282

embedded structure 293, 294

members, accessing 283

members, accessing through pointer 284, 285

members, accessing with arrow (->) operator 285

nested structures 292

versus union 298

switch statement 119, 120

characters 121

multiple cases, handling 123

nested switch statement 124, 126

using 121

T

tagged union 295

termination condition 168

text files

closing 324

error handling 324

opening 323

reading 323

reading line-by-line 324

text, appending to existing file 324

text file manipulation 324

working with 323

writing 323, 324

writing line-by-line 324

tokenization 205

token-pasting operator (##) 393

tokens, in C

constants 52

identifiers 52

keywords 52

operators 52

special symbols 52

strings 52

top-down modular programming 15, 16

Tower of Hanoi 170

two-dimensional array 220

two-dimensional array initialization

initializing with single value 223

inline initialization 222

loop-based initialization 223

partial initialization 223

types of functions, in C

built-in functions 153

function pointers 154

inline functions 154

library functions 153

recursive functions 154

user-defined functions 153

U

#undef preprocessor 372-374

union 278, 294

defining 296, 297

limitations 295, 296

members, accessing 298, 299

syntax 294, 295

usage 295

usage example 296

versus structure 298

user-defined data types 279

V

variables 64

declaring 64, 65

guidelines, for declaring variables 65, 66

void pointer 270

void type 84

function arguments 85

function return type 84

pointer declarations 85

W

while loop 127, 128

condition 128

examples 128-130

loop body 128

update 128

whitespace, in C 53

wild pointers

example 271